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.
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')) {
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": (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, "import1", function() { return import1; }); var _dep_1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( "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?
