Menggunakan modul JavaScript dalam produksi: keadaan saat ini. Bagian 1

Dua tahun lalu, saya menulis tentang teknik yang sekarang biasa disebut modul / pola nomodule. Aplikasinya memungkinkan Anda untuk menulis kode JavaScript menggunakan kemampuan ES2015 +, dan kemudian menggunakan bundler dan transpiler untuk membuat dua versi basis kode. Salah satunya mengandung sintaksis modern (ini dimuat menggunakan struktur seperti <script type="module"> , dan yang kedua adalah sintaksis ES5 (ini dimuat menggunakan <script nomodule> ). Pola modul / nomodule memungkinkan pengiriman ke browser yang mendukung modul, kode jauh lebih sedikit daripada browser yang tidak mendukung fitur ini, sekarang pola ini didukung oleh sebagian besar kerangka kerja web dan alat baris perintah.



Sebelumnya, walaupun mempertimbangkan kemampuan untuk mengirim kode JavaScript modern ke produksi, dan meskipun sebagian besar browser mendukung modul, saya merekomendasikan pengumpulan kode dalam bundel.

Mengapa Terutama karena saya merasa memuat modul ke browser lambat. Meskipun protokol terbaru, seperti HTTP / 2, secara teoritis mendukung pemuatan yang efisien dari banyak file, semua studi kinerja pada saat itu menyimpulkan bahwa menggunakan bundler masih lebih efisien daripada menggunakan modul.

Tetapi harus diakui bahwa studi tersebut tidak lengkap. Kasus uji menggunakan modul yang dipelajari di dalamnya terdiri dari file kode sumber yang tidak dioptimalkan dan tidak diperkecil yang digunakan dalam produksi. Tidak ada perbandingan bundel yang dioptimalkan dengan modul dengan skrip klasik yang dioptimalkan.

Namun, jujur ​​saja, tidak ada cara optimal untuk menyebarkan modul pada saat itu. Tetapi sekarang, berkat beberapa peningkatan modern dalam teknologi bundler, dimungkinkan untuk menyebarkan kode produksi dalam bentuk modul ES2015 menggunakan perintah impor statis dan dinamis, dan pada saat yang sama menerima kinerja lebih tinggi daripada yang dapat dicapai dengan menggunakan opsi yang tersedia, di mana modul tidak digunakan.

Perlu dicatat bahwa pada situs di mana materi asli diterbitkan, bagian pertama dari terjemahan yang kami terbitkan hari ini, modul-modul tersebut telah digunakan dalam produksi selama beberapa bulan.

Kesalahpahaman tentang modul


Banyak orang yang saya ajak bicara menolak modul sama sekali, bahkan tidak menganggapnya sebagai salah satu opsi untuk aplikasi produksi skala besar. Banyak dari mereka mengutip pelajaran yang telah saya sebutkan. Yaitu, bagian itu, yang menyatakan bahwa modul tidak boleh digunakan dalam produksi, kecuali itu adalah pertanyaan tentang "aplikasi web kecil yang mencakup kurang dari 100 modul yang berbeda dalam pohon ketergantungan yang relatif" kecil "(yaitu - yang kedalamannya tidak melebihi 5 level). "

Jika Anda pernah melihat ke direktori node_modules dari salah satu proyek Anda, maka Anda mungkin tahu bahwa bahkan sebuah aplikasi kecil dapat dengan mudah memiliki lebih dari 100 modul dependensi. Saya ingin menawarkan Anda melihat berapa banyak modul yang tersedia di beberapa paket npm paling populer.
Paket
Jumlah modul
date-fns
729
lodash-es
643
rxjs
226

Di sinilah kesalahpahaman utama tentang modul di-root. Programmer percaya bahwa ketika menggunakan modul dalam produksi, mereka hanya memiliki dua opsi. Yang pertama adalah untuk menyebarkan semua kode sumber dalam bentuk yang sudah ada (termasuk direktori node_modules ). Yang kedua adalah tidak menggunakan modul sama sekali.

Namun, jika Anda memperhatikan rekomendasi dari studi yang dikutip di atas, Anda akan menemukan bahwa tidak ada yang mengatakan bahwa memuat modul lebih lambat daripada memuat skrip biasa. Tidak disebutkan bahwa modul tidak boleh digunakan sama sekali. Itu hanya berbicara tentang fakta bahwa jika seseorang menyebarkan ratusan file modul yang tidak terinfeksi dalam produksi, Chrome tidak akan dapat memuatnya secepat satu bundel yang diperkecil. Akibatnya, penelitian ini menyarankan untuk terus menggunakan bundler, kompiler, dan minifiers.

Tapi tahukah Anda? Faktanya adalah bahwa Anda dapat menggunakan semua ini dan menggunakan modul dalam produksi.

Sebenarnya, modul adalah format yang harus kita perjuangkan untuk mengonversi kode, karena browser sudah tahu cara memuat modul (dan browser yang tidak bisa melakukan ini dapat memuat fallback menggunakan mekanisme nomodule). Jika Anda melihat kode yang dihasilkan bundler paling populer, maka Anda akan menemukan banyak fragmen templat yang tujuannya hanya untuk memuat kode lain secara dinamis dan mengelola dependensi. Tetapi semua ini tidak diperlukan jika kita hanya menggunakan modul dan ekspresi, import dan export .

Untungnya, setidaknya salah satu bundler modern populer ( Rollup ) mendukung modul dalam bentuk data keluaran . Ini berarti Anda dapat memproses kode dengan bundler dan menggunakan modul dalam produksi (tanpa menggunakan fragmen templat untuk memuat kode). Dan, karena Rollup memiliki implementasi yang luar biasa dari algoritma tree-shaking (yang terbaik yang pernah saya lihat di bundler), membangun program dalam bentuk modul menggunakan Rollup memungkinkan Anda untuk mendapatkan kode yang lebih kecil dari ukuran kode yang diperoleh saat menerapkan mekanisme lain yang tersedia saat ini.

Perlu dicatat bahwa mereka berencana untuk menambahkan dukungan untuk modul di versi Parcel berikutnya. Webpack belum mendukung modul sebagai format output, tetapi ini dia - diskusi yang berfokus pada masalah ini.

Kesalahpahaman lain mengenai modul adalah bahwa beberapa orang percaya bahwa modul hanya dapat digunakan jika 100% dari ketergantungan proyek menggunakan modul. Sayangnya (saya menganggapnya sangat menyesal), sebagian besar paket npm masih disiapkan untuk publikasi menggunakan format CommonJS (beberapa modul, bahkan yang ditulis menggunakan fitur ES2015, diterjemahkan ke dalam format CommonJS sebelum dipublikasikan ke npm)!

Di sini, sekali lagi, saya ingin mencatat bahwa Rollup memiliki plugin ( rollup-plugin-commonjs ) yang mengambil kode sumber input yang ditulis menggunakan CommonJS dan mengubahnya menjadi kode ES2015. Jelas, akan lebih baik jika format ketergantungan yang digunakan dari awal menggunakan format modul ES2015. Tetapi jika beberapa dependensi tidak seperti itu, ini tidak mencegah Anda dari menyebarkan proyek menggunakan modul dalam produksi.

Di bagian berikut artikel ini, saya akan menunjukkan kepada Anda bagaimana saya mengumpulkan proyek dalam bundel yang menggunakan modul (termasuk penggunaan impor dinamis dan pemisahan kode), saya akan berbicara tentang mengapa solusi seperti itu biasanya lebih produktif daripada skrip klasik, dan menunjukkan cara kerjanya dengan browser yang tidak mendukung modul.

Strategi Membangun Kode Optimal


Membangun kode untuk produksi selalu merupakan upaya untuk menyeimbangkan pro dan kontra dari berbagai solusi. Di satu sisi, pengembang ingin kode-nya dimuat dan dieksekusi secepat mungkin. Di sisi lain, dia tidak ingin mengunduh kode yang tidak akan digunakan oleh pengguna proyek.

Selain itu, pengembang perlu percaya bahwa kode mereka paling cocok untuk caching. Masalah besar dari bundling kode adalah bahwa setiap perubahan dalam kode, bahkan satu baris yang diubah, menyebabkan pembatalan cache seluruh bundel. Jika Anda menggunakan aplikasi yang terdiri dari ribuan modul kecil (disajikan persis dalam bentuk di mana mereka hadir dalam kode sumber), maka Anda dapat dengan aman membuat perubahan kecil pada kode dan pada saat yang sama tahu bahwa sebagian besar kode aplikasi akan di-cache . Tetapi, seperti yang telah saya katakan, pendekatan pengembangan seperti itu mungkin dapat berarti bahwa memuat kode saat pertama kali Anda mengunjungi sumber daya mungkin memerlukan waktu lebih lama daripada menggunakan pendekatan yang lebih tradisional.

Akibatnya, kami menghadapi tugas yang sulit, yaitu menemukan pendekatan yang tepat untuk memecah bundel menjadi beberapa bagian. Kita perlu mencapai keseimbangan yang tepat antara kecepatan pemuatan material dan caching jangka panjangnya.

Sebagian besar bundler, secara default, menggunakan teknik pemecahan kode berdasarkan pada perintah impor dinamis. Tapi saya akan mengatakan bahwa membagi kode hanya dengan fokus pada impor dinamis tidak memungkinkan memecahnya menjadi fragmen yang cukup kecil. Ini terutama berlaku untuk situs dengan banyak pengguna yang kembali (yaitu, dalam situasi di mana caching penting).

Saya percaya bahwa kode harus dipecah menjadi fragmen sekecil mungkin. Sebaiknya kurangi ukuran fragmen hingga jumlahnya bertambah banyak sehingga akan mempengaruhi kecepatan pengunduhan proyek. Dan walaupun saya merekomendasikan kepada semua orang untuk melakukan analisis mereka sendiri tentang situasi tersebut, jika Anda yakin perhitungan perkiraan yang dibuat dalam penelitian yang saya sebutkan, saat memuat kurang dari 100 modul, tidak ada kelambatan yang terlihat dalam memuat. Studi terpisah tentang kinerja HTTP / 2 tidak mengungkapkan perlambatan proyek yang nyata saat mengunduh kurang dari 50 file. Namun, mereka hanya menguji opsi di mana jumlah file adalah 1, 6, 50, dan 1000. Akibatnya, mungkin 100 file adalah nilai yang dapat Anda navigasikan dengan mudah tanpa takut kehilangan kecepatan unduh.

Jadi, apa cara terbaik untuk secara agresif, tetapi tidak terlalu agresif membagi kode menjadi beberapa bagian? Selain kode pemisahan berdasarkan perintah impor dinamis, saya akan menyarankan Anda untuk melihat lebih dekat pada kode pemisahan ke dalam paket npm. Dengan pendekatan ini, apa yang diimpor ke proyek dari folder node_modules jatuh ke dalam fragmen terpisah dari kode selesai berdasarkan pada nama paket.

Pemisahan Paket


Saya katakan di atas bahwa beberapa kemampuan modern dari bundler memungkinkan untuk mengatur skema kinerja tinggi untuk penggelaran proyek berbasis modul. Apa yang saya bicarakan diwakili oleh dua fitur Rollup baru. Yang pertama adalah pemisahan kode otomatis melalui perintah import() dinamis import() (ditambahkan dalam v1.0.0 ). Opsi kedua adalah pemisahan kode manual yang dilakukan oleh program berdasarkan pilihan manualChunks (ditambahkan pada v1.11.0 ).

Berkat kedua fitur ini, sekarang sangat mudah untuk mengkonfigurasi proses build, di mana kode dibagi pada tingkat paket.

Berikut ini adalah contoh konfigurasi yang menggunakan opsi manualChunks , berkat setiap modul yang diimpor dari node_modules masuk ke dalam bagian kode yang terpisah yang namanya sesuai dengan nama paket (secara teknis, nama direktori paket di folder node_modules ):

 export default {  input: {    main: 'src/main.mjs',  },  output: {    dir: 'build',    format: 'esm',    entryFileNames: '[name].[hash].mjs',  },  manualChunks(id) {    if (id.includes('node_modules')) {      //   ,    `node_modules`.      //   - ,       .      const dirs = id.split(path.sep);      return dirs[dirs.lastIndexOf('node_modules') + 1];    }  }, } 

Opsi manualChunk menerima fungsi yang menerima, sebagai argumen tunggal, jalur ke file modul. Fungsi ini dapat mengembalikan nama string. Apa yang dikembalikan akan menunjuk ke sebuah fragmen rakitan di mana modul saat ini harus ditambahkan. Jika fungsi tidak mengembalikan apa pun, maka modul akan ditambahkan ke fragmen default.

Pertimbangkan aplikasi yang mengimpor cloneDeep() , debounce() dan find() dari paket lodash-es . Jika Anda menerapkan konfigurasi di atas saat membuat aplikasi ini, maka masing-masing modul ini (serta setiap modul lodash diimpor oleh modul-modul ini) akan ditempatkan dalam file output tunggal dengan nama seperti npm.lodash-es.XXXX.mjs (di sini XXXX unik modul file hash dalam fragmen lodash-es ).

Di akhir file, Anda akan melihat ekspresi ekspor seperti berikut ini. Harap dicatat bahwa ungkapan ini hanya berisi perintah ekspor untuk modul yang ditambahkan ke fragmen, dan tidak semua modul lodash .

 export {cloneDeep, debounce, find}; 

Kemudian, jika kode di salah satu fragmen lain menggunakan modul lodash ini (mungkin hanya metode debounce() ), dalam fragmen ini, di bagian atas mereka, akan ada ekspresi impor yang terlihat seperti ini:

 import {debounce} from './npm.lodash.XXXX.mjs'; 

Semoga contoh ini menjelaskan pertanyaan tentang cara pemisahan kode manual di Rollup. Selain itu, saya berpikir bahwa hasil pemisahan kode menggunakan ekspresi import dan export jauh lebih mudah dibaca dan dipahami daripada kode fragmen, yang pembentukannya menggunakan mekanisme non-standar yang hanya digunakan dalam bundler tertentu.

Misalnya, sangat sulit untuk mencari tahu apa yang sedang terjadi di file berikutnya. Ini adalah output dari salah satu proyek lama saya yang menggunakan webpack untuk membagi kode. Hampir semua hal dalam kode ini tidak diperlukan di browser yang mendukung modul.

 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["import1"],{ /***/ "tLzr": /*!*********************************!*\  !*** ./app/scripts/import-1.js ***!  \*********************************/ /*! exports provided: import1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); /* harmony import */ var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./dep-1 */ "6xPP"); const import1 = "imported: " + _dep_1__WEBPACK_IMPORTED_MODULE_0__["dep1"]; /***/ }) }]); 

Bagaimana jika ada ratusan dependensi npm?


Seperti yang saya katakan, saya percaya bahwa pemisahan tingkat kode pada tingkat paket biasanya memungkinkan pengembang untuk masuk ke posisi yang baik ketika pemisahan kode agresif, tetapi tidak terlalu agresif.

Tentu saja, jika aplikasi Anda mengimpor modul dari ratusan paket npm yang berbeda, Anda masih bisa berada dalam situasi di mana browser tidak dapat memuat semuanya secara efektif.

Namun, jika Anda benar-benar memiliki banyak dependensi npm, Anda tidak boleh sepenuhnya meninggalkan strategi ini untuk saat ini. Ingatlah bahwa Anda mungkin tidak akan mengunduh semua dependensi npm di setiap halaman. Karena itu, penting untuk mengetahui berapa banyak sebenarnya dependensi yang dimuat.

Namun demikian, saya yakin bahwa ada beberapa aplikasi nyata yang memiliki banyak dependensi npm sehingga dependensi ini tidak dapat direpresentasikan sebagai fragmen yang terpisah. Jika proyek Anda hanya itu - Saya akan merekomendasikan Anda mencari cara untuk mengelompokkan paket di mana kode di mana dengan probabilitas tinggi dapat berubah pada saat yang sama (seperti react dan react-dom ) karena react-dom cache fragmen dengan paket-paket ini juga akan dieksekusi pada saat bersamaan. Kemudian saya akan menunjukkan contoh di mana semua dependensi Bereaksi dikelompokkan dalam fragmen yang sama.

Dilanjutkan ...

Pembaca yang budiman! Bagaimana Anda mendekati masalah pemisahan kode dalam proyek Anda?

Source: https://habr.com/ru/post/id466537/


All Articles