Saat CRA tidak cukup. Laporan Yandex

Di balik antarmuka pencarian Yandex terdapat proyek besar dengan infrastruktur yang kompleks. Kami memiliki puluhan megabyte kode yang seharusnya bekerja dengan cepat dan dibangun dengan cepat. Ketika kami perlu menerjemahkan proyek ke React dan TypeScript, kami mulai dengan Create React App, CRA. Dan mereka segera menyadari bahwa banyak yang perlu ditingkatkan.





Dalam ceramah saya di Y.Subbotnik Pro, saya ingat apa dan bagaimana kami menyelesaikan perakitan dan arsitektur "proyek modern standar" dan hasil apa yang kami dapatkan.



- Selama satu setengah tahun terakhir saya telah bekerja di tim arsitektur Serp. Kami mengembangkan runtime dan perakitan kode baru di React dan TypeScript di sana.



Mari kita bicara tentang rasa sakit umum kita yang akan dibahas pembicaraan ini. Saat Anda ingin membuat proyek kecil di React, Anda hanya perlu menggunakan seperangkat alat standar yang disebut tiga huruf - CRA. Ini termasuk skrip build, skrip untuk menjalankan pengujian, menyiapkan lingkungan dev, dan semuanya telah dilakukan untuk produksi. Semuanya dilakukan dengan sangat sederhana melalui skrip NPM, dan semua orang mungkin mengetahuinya yang memiliki pengalaman dengan React.



Tetapi misalkan proyek menjadi besar, ia memiliki banyak kode, banyak pengembang, fitur produksi muncul, seperti terjemahan, yang tidak diketahui oleh Aplikasi Buat React. Atau Anda memiliki semacam pipeline CI / CD yang rumit. Kemudian pikiran mulai membuat eject untuk menggunakan Create React App sebagai dasar dan menyesuaikannya untuk proyek Anda sendiri. Tapi sama sekali tidak jelas apa yang menunggu di sana, di balik pelontaran ini. Karena ketika Anda melakukan eject, dikatakan bahwa ini adalah operasi yang sangat berbahaya, tidak mungkin mengembalikannya dan seterusnya, sangat menakutkan. Mereka yang menekan eject tahu bahwa banyak konfigurasi yang dibuang di luar sana, yang perlu Anda pahami. Secara umum, ada banyak risiko, dan tidak jelas apa yang harus dilakukan.



Saya akan memberi tahu Anda bagaimana hal itu dengan kami. Pertama, tentang proyek kami. Proyek front-end kami adalah Serp, Halaman Hasil Mesin Pencari, halaman hasil pencarian Yandex yang telah dilihat semua orang. Sejak 2018 kami tidak memindahkan React dan TypeScript. Sekitar 12 megabyte kode telah ditulis di Serpa tahun lalu. Ada beberapa gaya dan banyak kode TS dan SCSS. Berapa di awal, tahun 2018, ada, saya tidak menulis, sangat sedikit, ada lompatan yang sangat tajam.







Mari kita lihat apakah ini banyak kode atau tidak. Dibandingkan dengan kode sumber webpack-4, kode di webpack-4 jauh lebih sedikit. Bahkan repositori TypeScript memiliki lebih sedikit kode.







Tetapi vs-code memiliki lebih banyak kode, proyek yang bagus dengan kode TypeScript sebanyak 30 megabyte. Ya, itu juga ditulis dalam TypeScript, dan Sickle tampaknya lebih kecil. Pada 2018 kami mulai, pada 2019 ada 12 megabyte, dan 70 pengembang kami bekerja, membuat 100 permintaan tuang per minggu. Dalam satu tahun, mereka melipatgandakan ukuran ini, dan menerima tepat 30 megabyte. Saya melakukan pengukuran bulan ini, kami memiliki total 30 megabyte kode sekarang, dan ini sudah lebih dari pada kode vs.







Kira-kira sama, tapi sedikit lebih. Ini adalah urutan proyek kami.







Dan kami memang mengeluarkan di awal, karena kami segera tahu bahwa kami akan memiliki banyak kode dan, kemungkinan besar, konfigurasi awal yang ada di Aplikasi Buat Reaksi tidak akan berfungsi untuk kami. Tapi kami memulai dengan cara yang sama, dengan Create React App.



Jadi begitulah ceritanya nanti. Kami ingin membagikan pengalaman kami, memberi tahu Anda apa yang harus kami lakukan dengan Buat Aplikasi React agar Yandex Serp berfungsi dengan baik. Yaitu, bagaimana kami mendapatkan pemuatan dan inisialisasi yang cepat di browser, dan bagaimana kami mencoba untuk tidak memperlambat pembuatan, pengaturan apa, plugin, dan hal-hal lain yang kami gunakan untuk ini. Dan tentu saja, hasil yang telah kita capai akan datang pada akhirnya.







Bagaimana alasan kami? Ide awalnya adalah Sickle kami adalah halaman yang perlu dirender dengan sangat cepat, karena pada dasarnya, terdapat hasil teks yang sangat sederhana, jadi kami memerlukan template sisi server, karena ini adalah satu-satunya cara untuk mendapatkan rendering yang cepat. Artinya, kita harus menggambar sesuatu bahkan sebelum sesuatu mulai diinisialisasi pada klien.



Pada saat yang sama, saya ingin membuat ukuran statika minimum, agar tidak memuat apa pun yang berlebihan dan inisialisasi juga cepat. Artinya, kami ingin render pertama cepat dan inisialisasi cepat.







Apa yang ditawarkan Create React App kepada kami? Sayangnya, itu tidak menawarkan apa pun kepada kita tentang rendering server.



Ini secara langsung mengatakan bahwa rendering server tidak didukung di Create React App. Selain itu, Create React App hanya memiliki satu entri untuk seluruh aplikasi. Artinya, secara default, satu bundel besar dikumpulkan untuk semua variasi halaman Anda yang sangat banyak. Ini banyak sekali. Jelas bahwa dari 30 megabyte, sekitar setengahnya adalah tipe TS, tetapi masih banyak kode yang akan langsung masuk ke browser.



Pada saat yang sama, Create React App memiliki beberapa pengaturan yang baik, misalnya, waktu proses webpack berada di sana dalam bagian yang terpisah. Itu dimuat secara terpisah, dapat di-cache karena tidak berubah secara normal.



Selain itu, modul dari node_modules juga dikumpulkan dalam potongan terpisah. Mereka juga jarang berubah, dan oleh karena itu mereka juga di-cache oleh browser, ini bagus, ini harus disimpan. Tetapi pada saat yang sama, tidak ada tentang terjemahan di Create React App.



Mari kumpulkan daftar kami tentang bagaimana dalam kasus kami daftar kemampuan platform kami akan terlihat. Pertama, kami ingin rendering utara, seperti yang saya katakan, untuk melakukan render cepat. Selain itu, kami ingin memiliki file entri terpisah untuk setiap hasil pencarian.







Jika, misalnya, Serpa memiliki kalkulator, maka kami ingin paket dengan kalkulator dikirimkan, dan paket dengan penerjemah tidak perlu dikirimkan dengan cepat. Jika semua ini dikumpulkan dalam satu bundel besar, maka semuanya akan selalu berjalan, bahkan jika setengah dari hal-hal ini tidak terkait dengan masalah tertentu.



Selanjutnya, saya ingin menyediakan modul umum dalam potongan terpisah agar tidak memuat apa yang telah dimuat.







Berikut contoh lain dengan Sickle. Ada kalkulator, ada bundel kalkulator. Ada komponen umum. Mereka dikirim ke klien. Kemudian fitur lain muncul - peta. Mengendarai sekumpulan peta, dan mengendarai komponen umum lainnya, kecuali yang telah dikirimkan.



Jika komponen umum dirakit secara terpisah, maka ada peluang besar untuk pengoptimalan dan hanya yang dibutuhkan yang dikirim, hanya diff. Dan modul paling populer yang selalu ada di halaman, misalnya, waktu proses webpack, yang selalu dibutuhkan oleh seluruh infrastruktur ini, harus selalu dimuat.







Oleh karena itu, masuk akal untuk mengumpulkan dalam potongan terpisah. Artinya, komponen umum tersebut juga dapat diuraikan menjadi komponen yang tidak selalu dibutuhkan, dan komponen yang selalu dibutuhkan. Mereka dapat dikumpulkan dalam file terpisah dan selalu dimuat, dan juga di-cache, karena komponen umum ini, seperti tombol / tautan, tidak terlalu sering berubah, secara umum mendapat untung dari caching.







Dan pada saat yang sama, Anda perlu membuat keputusan tentang merakit terjemahan.







Semuanya cukup jelas di sini. Jika kami pergi ke Ular Turki, kami hanya ingin mengunduh terjemahan bahasa Turki, dan tidak mengunduh semua terjemahan lainnya, karena ini adalah kode tambahan.



Apa yang telah kita lakukan? Pertama, tentang kode server. Berkaitan dengan itu, kami akan memiliki dua arah - membangun untuk produksi dan meluncurkan untuk pengembang.







Secara umum, Anda perlu membuat pernyataan terpisah tentang TypeScript terlebih dahulu. Biasanya proyek, seperti yang saya dengar, menggunakan babel. Tetapi kami segera memutuskan untuk menggunakan kompiler TypeScript standar, karena kami yakin bahwa fitur TypeScript baru akan mencapainya lebih cepat. Oleh karena itu, kami segera meninggalkan babel dan menggunakan tsc.



Jadi, ini ukuran kode kami saat ini, 30 megabyte, dikompilasi di laptop dalam tiga menit. Cukup banyak. Jika Anda menolak untuk mengetik pemeriksaan dan menggunakan garpu tsc selama setiap kompilasi (sayangnya, TSC tidak memiliki pengaturan yang akan menonaktifkan pemeriksaan jenis, Anda harus bercabang), maka Anda dapat menghemat waktu dua kali. Kompilasi kode kami hanya membutuhkan satu setengah menit.



Mengapa kita tidak bisa memeriksa tipe waktu kompilasi? Karena kita, misalnya, dapat memeriksanya di hook pra-komit. Buat linter yang hanya akan menjalankan pemeriksaan jenis, dan perakitan itu sendiri dapat dilakukan tanpa pemeriksaan jenis. Kami membuat keputusan ini.



Bagaimana kita menjalankan dev? Dev biasanya menggunakan bundel babel dengan webpack, tapi kami menggunakan alat seperti ts-node.







Ini adalah alat yang sangat sederhana. Untuk menjalankannya, cukup menulis require ini (ts-node) di input file JavaScript, dan ini akan menimpa require-s dari seluruh kode TS nanti dalam proses ini. Dan jika kode TS dimuat ke dalam proses ini di sepanjang jalan, kode tersebut akan dikompilasi dengan cepat. Suatu hal yang sangat sederhana.



Biasanya, ada sedikit biaya tambahan yang terkait dengan fakta bahwa jika file belum dimuat dalam proses ini, maka file tersebut harus dikompilasi ulang. Namun dalam praktiknya, biaya tambahan ini minimal dan dapat diterima secara umum.



Selain itu, ada beberapa baris yang lebih menarik dalam daftar ini. Yang pertama adalah mengabaikan gaya, karena kita tidak memerlukan gaya untuk pembuatan template sisi server. Kami hanya perlu mendapatkan HTML. Oleh karena itu, kami juga menggunakan modul seperti itu - ignore-styles. Dan selain itu, kami mematikan pemeriksaan tipe yang sama (hanya transpile), seperti yang kami lakukan di TSC, untuk mempercepat kerja ts-node.



Pindah ke kode klien. Bagaimana cara kami mengumpulkan kode ts di webpack? Kami menggunakan ts-loader dan opsi transpileOnly, yang kira-kira bundel yang sama. Selain babel-loader, ada lebih banyak atau lebih sedikit alat ts-loader dan transpileOnly standar.



Sayangnya, build inkremental tidak berfungsi di ts-loader. Artinya, bagaimanapun juga, ts-loader bukanlah alat standar, dan tidak dibuat oleh orang yang sama yang melakukan TypeScript. Oleh karena itu, tidak semua opsi kompilator didukung di sana. Misalnya, build inkremental tidak didukung.



Build inkremental adalah satu hal yang bisa sangat berguna dalam pengembangan. Selain itu, Anda dapat menambahkan cache ini ke pipeline. Secara umum, ketika perubahan Anda kecil, Anda tidak dapat sepenuhnya mengkompilasi ulang semuanya, semua TypeScript, tetapi hanya apa yang telah berubah. Ini bekerja dengan cukup efektif.



Secara umum, untuk melakukannya tanpa build inkremental, kami menggunakan cache-loader. Ini adalah solusi standar dari webpack. Semuanya cukup jelas. Ketika kode TypeScript mencoba untuk terhubung selama pembuatan webpack, itu diproses oleh kompiler, ditambahkan ke cache, dan lain kali, jika tidak ada perubahan dalam file sumber, cache-loader tidak akan menjalankan ts-loader dan akan mengambilnya dari cache. Artinya, semuanya cukup sederhana di sini.



Ini dapat digunakan untuk apa saja, tetapi khusus untuk TypeScript itu adalah hal yang berguna, karena ts-loader adalah loader yang agak berat, jadi cache-loader sangat cocok di sini.







Tetapi cache-loader memiliki satu kelemahan - ini bekerja dengan waktu modifikasi file. Berikut ini potongan kode sumber. Dan itu tidak berhasil untuk kami.







Kami harus bercabang dan mengulang algoritme caching berdasarkan hash dari konten file, karena algoritme tersebut tidak cocok untuk kami menggunakan cache-loader dalam pipeline.



Faktanya adalah saat Anda ingin menggunakan kembali hasil build di antara beberapa permintaan tarik, mekanisme ini tidak akan berfungsi. Karena jika perakitannya, misalnya, sudah lama sekali. Kemudian Anda mencoba membuat permintaan penarikan baru, yang tidak mengubah file yang dikumpulkan sebelumnya.



Tapi mtime mereka lebih baru. Dengan demikian, cache-loader akan berpikir bahwa file-file tersebut telah diperbarui, tetapi sebenarnya bukan, karena ini bukan waktu modifikasi, tetapi waktu checkout. Dan jika Anda melakukannya seperti ini, maka hash dari konten akan dibandingkan. Konten tidak berubah, hasil lama akan digunakan.



Perlu dicatat di sini bahwa jika kita menggunakan babel, babel-loader memiliki mekanisme caching di dalamnya secara default, dan ini telah dilakukan pada hash dari konten, bukan pada mtime. Oleh karena itu, mungkin kita akan berpikir lebih banyak dan melihat ke arah babel.



Sekarang tentang perakitan bongkahan.







Mari kita bicara sedikit tentang apa yang dilakukan webpack secara default. Jika kami memiliki file indeks masukan, komponen terhubung dengannya. Mereka juga memiliki komponen, dll. Sebagai tambahan, modul umum dihubungkan: React, React-dom dan lodash, misalnya.



Jadi, secara default, webpack, seperti yang mungkin diketahui semua orang, tetapi untuk berjaga-jaga, saya ulangi, mengumpulkan semua dependensi ke dalam satu bundel besar.







Pada saat yang sama, segala sesuatu yang terhubung melalui node_modules dapat dirakit baik sebagai eksternal, dimuat dengan skrip terpisah, atau dalam bagian terpisah dengan menyiapkan pengaturan optimasi.splitChunks khusus di webpack. Menurut pendapat saya, bahkan secara default, modul vendor ini dikumpulkan dalam potongan terpisah. CRA memiliki versi yang sedikit diubah dari splitChunks ini.







Mari kita ingat apa itu runtimeChunks. Saya menyebutkan dia. Ini adalah jenis kode yang berisi "header" memuat skrip dan fungsi yang memastikan pengoperasian sistem modular pada klien. Dan kemudian sebuah array (atau cache), yang sebenarnya berisi modul.







Mengapa saya memberi tahu Anda tentang ini? Karena Create React App masih menggunakan pengaturan yang mengumpulkan runtimeChunks ini ke dalam file terpisah. File ini tidak akan dimasukkan ke dalam paket sehat asli, tetapi menjadi file terpisah. Itu bisa di-cache di browser dan semua itu.



Jadi apa yang tidak berhasil untuk kami di Buat Aplikasi React?







SplitChunks ini, yang digunakan di sana secara default, hanya mengumpulkan node_modules ke dalam potongan terpisah. Namun, pada kenyataannya, ada komponen umum, pustaka umum, yang berada di level proyek. Saya juga ingin mengumpulkannya dalam potongan terpisah, karena mungkin juga jarang berubah. Mengapa kita membatasi diri kita hanya pada apa yang ada di node_modules?



Selain itu, tentang runtimeChunks, kita juga dapat mengatakan bahwa akan sangat bagus, seperti yang telah kita bahas sebelumnya, selain runtime itu sendiri, juga mengumpulkan modul di sana, dalam potongan yang sama, yang selalu dibutuhkan. Tombol / tautan yang sama. Selalu ada link di Serp. Saya selalu ingin mengumpulkan tautan. Artinya, tidak hanya runtime webpack, tetapi juga beberapa komponen yang sangat populer.



Ini tidak ada di Buat Aplikasi React. Bagaimana kita melakukannya dengan kita?







Kami mengubah splitChunks sedemikian rupa sehingga kami menonaktifkan semua perilaku standar dan meminta untuk mengumpulkan menjadi kode umum tidak hanya yang ada di node_modules, tetapi juga apa komponen umum dari proyek kami dan kode perpustakaan proyek kami, apa yang ada di src / lib , src / komponen berisi.



Selain itu, kami mengumpulkan menjadi potongan-potongan terpisah apa yang terhubung melalui impor dinamis, dan apa yang biasa disebut potongan asynchronous.



Di sini Anda perlu memperhatikan dua opsi. Yang satu menegakkan dan yang lainnya adalah inisial. Secara umum, menegakkan adalah pengaturan yang cukup nyaman sehingga menonaktifkan heuristik kompleks apa pun di splitChunks.



Secara default, splitChunks mencoba memahami berapa banyak modul yang dibutuhkan dan memperhitungkan statistik ini dalam pemisahan tersebut. Tetapi sulit untuk mengikuti ini, dan permintaan untuk modul dapat berubah dari waktu ke waktu, dan modul akan "melompat" di antara potongan-potongan. Dari bagian umum hingga bundel fitur dan kembali. Artinya, ini adalah perilaku yang sangat tidak terduga, jadi kami menonaktifkannya.



Artinya, kami selalu mengatakan semua yang memenuhi kondisi di lapangan uji, kami masuk ke bagian umum. Kami tidak menginginkan heuristik apa pun.



Tapi chunks: initial juga merupakan hal yang baik, ini tentang fakta bahwa modul sinkron ini, modul yang dihubungkan melalui impor dinamis, dapat dihubungkan di tempat berbeda dengan cara berbeda. Artinya, Anda dapat menghubungkan modul yang sama baik dengan impor dinamis atau dengan impor biasa.



Dan nilai awal memungkinkan modul yang sama dibangun dalam dua varian. Artinya, itu dirakit, baik asynchronous dan synchronous, sehingga memungkinkannya untuk digunakan dua arah. Cukup nyaman. Ini sedikit memperbesar ukuran statika yang dikumpulkan, tetapi memungkinkan Anda untuk menggunakan impor apa pun.



Ngomong-ngomong, dari dokumentasinya, ini cukup sulit untuk dipahami. Saya baru-baru ini membaca kembali dokumentasi webpack dan tidak ada hal normal yang ditulis tentang inisial.







Inilah yang kami lakukan dengan splitChunks. Sekarang apa yang telah kita lakukan dengan runtimeChunks. Alih-alih hanya mengumpulkan runtime di runtimeChunks, kami ingin menambahkan lebih banyak komponen yang sangat populer di sana.



Jadi kami membuat plugin kami sendiri yang disebut MainChunkPlugin. Dan itu memiliki pengaturan yang sangat sepele. Hanya ada daftar modul yang perlu dikumpulkan di sana, yang kami anggap populer.



Hanya dengan menggunakan alat pengujian A / B kami, berbagai alat offline, kami menyadari komponen mana yang paling sering muncul dalam hasil pencarian. Di situlah mereka ditulis hanya dalam daftar datar seperti itu. Dan pada akhirnya, plugin kami mengumpulkan komponen-komponen ini dari daftar, serta pustaka, serta waktu proses webpack, yang mengumpulkan optimasi.splitChunks standar ini.







Omong-omong, di sini ada potongan kode yang merekatkan waktu proses. Juga tidak terlalu sepele untuk menunjukkan bahwa tidak mudah untuk menulis plugin, tapi kemudian mari kita lihat apa yang diberikannya.







Perlu juga dicatat bahwa secara umum, webpack memiliki mekanisme standar untuk melakukan ini, yang disebut DLLPlugin. Ini juga memungkinkan Anda untuk mengumpulkan potongan terpisah sesuai dengan daftar dependensi. Tetapi ini memiliki sejumlah kelemahan. Misalnya, ini tidak menyertakan runtimeChunks. Artinya, runtimeChunks Anda akan selalu memiliki chunk terpisah, dan akan ada chunk yang dirakit oleh DLLPlugin. Ini sangat tidak nyaman.



DLLPlugin juga membutuhkan perakitan terpisah. Artinya, jika kita ingin membangun potongan terpisah ini dengan komponen yang paling perkusi menggunakan DLLPlugin, kita harus menjalankan dua rakitan.



Artinya, seseorang merakit potongan terpisah ini dengan file manifes, dan sisa perakitan akan mengumpulkan yang lainnya, hanya dengan mengurangkan melalui file manifes, ia tidak akan mengumpulkan apa yang telah masuk ke dalam potongan dengan komponen populer. Dan ini memperlambat build, karena implementasi DLLPlugin membutuhkan waktu tujuh detik secara lokal. Itu banyak. Dan itu tidak dapat dioptimalkan karena memiliki eksekusi sekuensial yang ketat.



Selain itu, pada titik tertentu kami perlu membangun bagian utama kami ini dengan komponen populer tanpa CSS, hanya JS. DLLPlugin tidak melakukan itu. Itu selalu mengumpulkan apa pun yang tersedia melalui kebutuhan melalui impor. Artinya, jika Anda memasukkan CSS, itu selalu hits juga. Itu tidak nyaman bagi kami. Tetapi jika ini bukan masalah bagi Anda, dan Anda tidak ingin menulis kode rumit seperti itu, DLLPlugin adalah solusi yang cukup normal. Dia memecahkan masalah utama. Artinya, ini memberikan komponen paling populer dalam file terpisah. Itu bisa digunakan.







Jadi apa yang kita dapat? Fitur kami dapat menggunakan komponen super populer dari MainChunk kami, yang dirakit oleh plugin khusus dengan nama yang sama. Selain itu, ada potongan umum, yang mencakup semua jenis komponen umum, dan ada potongan asinkron, yang dimuat melalui impor dinamis.



Kode lainnya ada dalam bundel fitur. Pada prinsipnya, ini adalah struktur potongan Anda.







Tentang merakit terjemahan. Terjemahan kami hanyalah file ts yang berada di sebelah komponen yang perlu diterjemahkan. Di sini kami memiliki sembilan bahasa, ini sembilan file.







Terjemahan terlihat seperti ini. Ini hanyalah sebuah objek yang berisi frase kunci dan arti dari frase yang diterjemahkan.







Ini adalah bagaimana terjemahan dihubungkan ke komponen, dan kemudian bantuan khusus digunakan.







Bagaimana terjemahan ini dikumpulkan? Kami berpikir: kami perlu mengumpulkan terjemahan, mencari di Internet, apa yang mereka tulis, bagaimana kami dapat melakukannya.



Kata orang di internet: gunakan multikompilasi. Artinya, daripada menjalankan satu build webpack, jalankan saja build webpack untuk setiap bahasa. Tapi, kata mereka, semuanya akan baik-baik saja, karena ada cache-loader, semua pekerjaan umum ini dengan TypeScript, atau apa pun yang Anda miliki, akan di-cache, dan oleh karena itu tidak akan lama.



Jangan berkecil hati, jangan berpikir bahwa ini akan menjadi sembilan webpack yang dijalankan. Tidak akan begitu, itu akan bagus.



Satu-satunya hal yang perlu diperbaiki adalah menambahkan modul ReplacementPlugin, yang, sebagai ganti file indeks yang menghubungkan semua bahasa, akan menggantinya dengan bahasa tertentu. Semuanya cukup sepele, dan ya, keluarannya perlu diperbaiki. Sekarang kami, ternyata, perlu mengumpulkan paket terpisah untuk setiap bahasa.







Diagram untuk resep ini adalah sebagai berikut. Ada seorang penerjemah. Dia menghubungkan terjemahan penerjemah. Dia menghubungkan bahasa, dan kami, alih-alih mengumpulkan satu struktur ini, kami menggandakannya untuk setiap bahasa, mendapatkan bahasa yang terpisah, dan mengumpulkan masing-masing sebagai kompilasi terpisah.







Sayangnya, itu tidak berhasil. Saya mencoba menjalankan opsi multikompilasi ini untuk kode 30MB kami saat ini, dan menunggu satu setengah jam, dan mendapatkan kesalahan ini.















Ini sangat panjang dan tidak mungkin. Apa yang telah kita lakukan dengan ini? Kami telah membuat plugin lain. Kami mengambil struktur yang sama dan menggabungkan diri ke dalam pekerjaan webpack ketika akan menyimpan file keluaran ke disk. Kami menyalin struktur ini sebanyak yang kami miliki, dan merekatkan satu bahasa untuk masing-masing bahasa. Dan baru setelah itu kami membuat file.







Pada saat yang sama, pekerjaan utama yang dilakukan webpack untuk melewati dependensi kompilasi tidak diulangi. Artinya, kita masuk pada tahap paling akhir, dan oleh karena itu kita bisa berharap ini akan cepat.







Tetapi kode plugin ternyata rumit. Ini secara harfiah adalah seperdelapan dari plugin kami. Saya hanya menunjukkan betapa sulitnya itu. Dan di sana kami secara teratur menemukan serangga kecil dan jahat di sana. Tapi itu tidak mudah untuk diterapkan. Tapi itu bekerja dengan sangat baik.







Artinya, alih-alih satu setengah jam dengan kesalahan, kami mendapatkan lima menit perakitan dengan plugin kami ini.



Sekarang pengiriman dan inisialisasi.







Pengiriman dan inisialisasi sederhana. Apa yang kami muat di resource terpisah, kami menggunakan preload, sama seperti orang lain, saya rasa. Kemudian kami menyertakan CSS, JS, sebenarnya HTML untuk komponen kami, dan memuat ini sumber daya kami, tetapi tanpa asinkron.



Kami bereksperimen. Jika kita menggunakan async, maka waktu dimulainya interaktivitas tertunda, yang tidak kita inginkan. Jadi gunakan saja preload dan muat di akhir halaman. Secara umum, tidak ada yang istimewa.







Pada saat yang sama, kami menyejajarkan yang lainnya. Artinya, ini MainChunk kami, kami menyebariskan CSS-nya. Komponen umum, gaya, secara umum, semua yang tertulis di slide, kita akan sebaris. Ini juga merupakan serangkaian eksperimen yang menunjukkan bahwa "sebaris" memberikan hasil terbaik untuk render pertama dan awal interaktivitas.



Dan sekarang ke angka-angka. Untuk berbicara tentang angka, Anda perlu mengatakan dua kata tentang metrik.







Kami memiliki tim kecepatan khusus yang bertujuan membuat semua kode front-end bekerja secara efisien. Ini menyangkut pembuatan template sisi server, dan memuat sumber daya, dan inisialisasi pada klien, secara umum, semua ini.



Kami memiliki sejumlah besar metrik yang dikirim dari produksi ke sistem logging khusus kami. Kami dapat mengontrol ini dalam eksperimen A / B. Kami memiliki alat offline, secara umum, kami sangat aktif mengikuti semua ini.



Dan kami menggunakan alat ini saat kami menerapkan kode baru kami ini di React dan TypeScript.







Sekarang mari kita lacak dengan bantuan alat luring (karena saya tidak dapat mengumpulkan percobaan daring yang jujur ​​yang akan menggunakan semua metrik kita). Mari kita lihat apa yang terjadi jika kita melakukan rollback dari solusi kita saat ini ke Create React App pada metrik utama ini.



Alat ini bekerja dengan sangat sederhana. Sebagian permintaan diambil, dalam hal ini permintaan dengan fitur di React diambil, karena belum semua Serp telah ditulis ulang di React. Kemudian template kami diaktifkan, pengukuran dikumpulkan, dimasukkan ke dalam utilitas khusus yang membandingkan dan menemukan hasil dan metrik ini. Dalam kasus ini, hanya hasil yang signifikan secara statistik yang tersisa. Secara umum, semuanya masuk akal di sana.



Mari lihat apa yang terjadi.







Menonaktifkan MultiPlugin, yang sebenarnya mengumpulkan semua terjemahan, bukan hanya terjemahan yang diperlukan, tidak menunjukkan perubahan yang signifikan secara statistik.



Awalnya saya sedikit kesal, kemudian saya menyadari bahwa sebenarnya ini bukan masalah, karena sekarang kami tidak memiliki banyak fitur yang memiliki banyak terjemahan yang diterjemahkan ke dalam React. Oleh karena itu, ketika ada lebih banyak fitur seperti itu, perubahan signifikan ini pasti akan muncul. Hanya saja sekarang ada fitur yang sebagian besar ditampilkan di Rusia dan belum ada terjemahannya. Dan jumlah kode yang terkandung di dalam komponen jauh melebihi jumlah terjemahan. Oleh karena itu, tidak terlihat bahwa semua terjemahan sedang dalam proses.



Mungkin itu akan terlihat dalam eksperimen yang lebih jujur, jika eksperimen yang jujur ​​dilakukan. Tetapi alat offline tidak menunjukkan perubahan ini.







Jika kita menonaktifkan MainChunkPlugin, maka waktu dimulainya interaktivitas melambat, dan pemuatan HTML juga sangat melambat. Oleh karena itu, hal tersebut sangat diperlukan.



Mengapa pemuatan HTML melambat, karena semua kode yang dulunya dimuat dalam potongan terpisah ini oleh sumber yang terpisah sekarang menjadi sebaris dalam HTML. Ini seperti kita menyejajarkan semuanya, tetapi interaktivitasnya juga melambat. Pada prinsipnya, cukup diharapkan.



Dan sekarang pertanyaannya: apa yang akan terjadi jika Anda meletakkan semuanya dalam satu bundel, tidak menggunakan potongan apapun dengan komponen umum? Ternyata ini sama sekali bukan gambaran yang membahagiakan.







Render pertama melambat secara dramatis. Interaktivitas juga hampir dua kali lipat. Ini membuat HTML lebih kecil karena semua kode mulai dikirimkan di sumber daya terpisah. Tetapi interaktivitas, seperti yang Anda lihat, itu tidak membantu.



Dan perakitan. Slide terakhir.











Waktu pembuatan Aplikasi Buat React untuk proyek saat ini membutuhkan waktu tiga menit di laptop. Dan dengan semua lonceng dan peluit kami - lima menit. Panjang?







Namun, nyatanya, jika digabung menjadi satu bundel, ternyata bisa menjadi tiga menit. Membangun tanpa MultiPlugin bahkan lebih cepat daripada Create React App. Tetapi seperti yang saya tunjukkan di slide sebelumnya, kami tidak dapat menolak modifikasi ini pada skrip build asli, karena tanpanya, metrik kecepatan akan menjadi sangat buruk.



Sekarang mari kita bahas apa yang berguna untuk dipelajari dari laporan ini.







Babel bukan satu-satunya cara untuk bekerja dengan TypeScript. TSC, ts-node dan ts-loader dapat digunakan. Ini bekerja dengan cukup baik.



Namun, pemeriksaan TypeScript, pemeriksaan jenis, tidak harus dilakukan setiap kali Anda membuat. Ini sangat melambat - seperti yang Anda ingat, dua kali. Oleh karena itu, lebih baik menempatkan hal-hal seperti itu dalam pemeriksaan terpisah, kait pra-komitmen, misalnya.



Lebih baik mengumpulkan komponen yang sering digunakan dalam potongan terpisah. Juga diinginkan untuk mengumpulkan komponen umum dalam potongan terpisah, karena ini memungkinkan Anda untuk memuat hanya apa yang diperlukan, hanya diff.



Yang paling penting adalah jika Anda tidak memiliki semua kode yang digunakan di semua halaman, Anda perlu memecahnya menjadi entri terpisah, mengumpulkan bundel terpisah, dan mengunduh saat pengguna melihat jenis yang sesuai dari hasil penelusuran. Unduh hanya file yang Anda butuhkan. Ini, seperti yang telah Anda lihat, memberikan hasil terbesar. Hal yang cukup jelas, tetapi saya tidak yakin apakah semua orang melakukan ini, karena mereka masih menggunakan Aplikasi Create React.



Multikompilasi sangat panjang. Jangan percaya jika seseorang mengatakan bahwa multikompilasi tidak apa-apa dan cache di suatu tempat di dalam dapat menangani semua ini. Menggunakan preload dan inline juga memberikan hasil.



Beberapa tautan tentang Sickle:



  • clck.ru/PdRdh dan clck.ru/PdRjb - dua laporan yang membahas tentang penulisan ulang Serp di React, ini adalah tahap pertama, tentang bagaimana kami sampai pada hal ini dan mengapa kami mulai melakukannya. Laporan kedua adalah tentang bagaimana kami merencanakan dan melakukan semua ini dari sudut pandang manajerial, apa tahapannya.
  • clck.ru/PdRnr - melaporkan tentang metrik kecepatan kami. Ini untuk mereka yang tiba-tiba bertanya-tanya apa lagi yang ada di sana, bagaimana alat online bekerja.


Terimakasih untuk semua.



All Articles