Modul JavaScript Bahagian 2: Penggabungan Modul

Di Bahagian I catatan ini, saya membincangkan modul apa, mengapa pembangun menggunakannya, dan pelbagai cara untuk memasukkannya ke dalam program anda.

Pada bahagian kedua ini, saya akan membahas apa sebenarnya maksud "menggabungkan" modul: mengapa kita menggabungkan modul, cara yang berbeza untuk melakukannya, dan masa depan modul dalam pembangunan web.

Apa itu penggabungan modul?

Pada tahap yang tinggi, penggabungan modul hanyalah proses menggabungkan sekumpulan modul (dan kebergantungannya) ke dalam satu fail (atau sekumpulan fail) dalam urutan yang betul.

Seperti semua aspek pembangunan web, syaitan ada dalam perinciannya. :)

Mengapa menggabungkan modul sama sekali?

Apabila anda membahagikan program anda ke dalam modul, anda biasanya menyusun modul tersebut ke dalam fail dan folder yang berbeza. Kemungkinannya, anda juga akan mempunyai sekumpulan modul untuk perpustakaan yang anda gunakan, seperti Underscore atau React.

Akibatnya, setiap fail tersebut harus dimasukkan ke dalam fail HTML utama Anda di tag pt>, yang kemudian dimuat oleh penyemak imbas ketika pengguna mengunjungi laman utama anda. Memiliki tag separa & lt ; script> untuk setiap fail bermaksud penyemak imbas harus memuatkan setiap fail secara berasingan: satu… demi… satu.

... Yang merupakan berita buruk untuk masa muat halaman.

Untuk mengatasi masalah ini, kami menggabungkan, atau "menggabungkan" semua fail kami ke dalam satu file besar (atau beberapa file yang sesuai) untuk mengurangkan jumlah permintaan. Apabila anda mendengar pembangun bercakap mengenai "langkah membina" atau "proses membina", inilah yang mereka bicarakan.

Pendekatan umum lain untuk mempercepat operasi bundling adalah untuk "meminimumkan" kod yang dibundel. Minifikasi adalah proses membuang watak yang tidak diperlukan dari kod sumber (mis. Ruang kosong, komen, watak baris baru, dll.), Untuk mengurangkan ukuran keseluruhan kandungan tanpa mengubah fungsi kod tersebut.

Kurang data bermaksud lebih sedikit masa pemprosesan penyemak imbas, yang seterusnya mengurangkan masa yang diperlukan untuk memuat turun fail. Sekiranya anda pernah melihat fail yang mempunyai pelanjutan "min" seperti "underscore-min.js", anda mungkin menyedari bahawa versi yang diperkecil itu cukup kecil (dan tidak dapat dibaca) dibandingkan dengan versi penuh.

Pelari tugas seperti Gulp dan Grunt membuat penggabungan dan pengurangan langsung untuk pembangun, memastikan bahawa kod yang dapat dibaca manusia tetap terdedah untuk pembangun sementara kod yang dioptimumkan untuk mesin digabungkan untuk penyemak imbas.

Apakah cara berbeza untuk menggabungkan modul?

Menyatukan dan meminimumkan fail anda berfungsi dengan baik apabila anda menggunakan salah satu corak modul standard (dibincangkan dalam catatan sebelumnya) untuk menentukan modul anda. Apa yang sebenarnya anda lakukan adalah mengumpulkan sekumpulan kod JavaScript vanila biasa.

Walau bagaimanapun, jika anda mematuhi sistem modul bukan asli yang tidak dapat ditafsirkan oleh penyemak imbas seperti CommonJS atau AMD (atau bahkan format modul ES6 asli ), anda perlu menggunakan alat khusus untuk menukar modul anda menjadi penyemak imbas yang disusun dengan betul -kod mesra. Di situlah Browserify, RequireJS, Webpack, dan "modul bundlers" atau "module loader" lain dimainkan.

Selain menggabungkan dan / atau memuatkan modul anda, bundler modul menawarkan banyak ciri tambahan seperti kod penyusun semula automatik ketika anda membuat perubahan atau menghasilkan peta sumber untuk debug.

Mari kita ikuti beberapa kaedah gabungan modul yang biasa:

Menggabungkan BiasaJS

Seperti yang anda ketahui dari Bahagian 1, CommonJS memuat modul secara serentak, yang akan baik kecuali bahawa ia tidak praktikal untuk penyemak imbas. Saya menyatakan bahawa ada jalan keluar untuk ini - salah satunya adalah bundler modul yang disebut Browserify. Browserify adalah alat yang menyusun modul CommonJS untuk penyemak imbas.

Sebagai contoh, katakan anda mempunyai fail main.js ini yang mengimport modul untuk mengira purata sebilangan nombor:

Jadi dalam kes ini, kita mempunyai satu ketergantungan (myDependency). Dengan menggunakan perintah di bawah ini, Browserify secara berkumpulan menggabungkan semua modul yang diperlukan bermula dari main.js ke dalam satu fail yang dipanggil bundle.js:

Browserify melakukan ini dengan melompat di dalam menghuraikan AST untuk setiap memerlukan panggilan untuk merentasi seluruh pergantungan graf projek anda. Setelah mengetahui bagaimana kebergantungan anda disusun, ia menggabungkan semuanya dengan urutan yang betul ke dalam satu fail. Pada ketika itu, yang perlu anda buat hanyalah memasukkan satu pt> tag dengan fail "bund le.js" anda ke dalam html anda untuk memastikan bahawa semua kod sumber anda dimuat dalam satu permintaan HTTP. Bam! Dikumpulkan untuk pergi.

Begitu juga, jika anda mempunyai banyak fail dengan pelbagai kebergantungan, anda hanya memberitahu Browserify apa fail kemasukan anda dan duduk sementara ia melakukan keajaiban.

Produk akhir: fail berkas yang disiapkan dan siap untuk alat seperti Minify-JS untuk meminimumkan kod yang digabungkan.

Menggabungkan AMD

Sekiranya anda menggunakan AMD, anda boleh menggunakan pemuat AMD seperti RequireJS atau Curl. Pemuat modul (berbanding pengikat) memuat modul secara dinamik yang perlu dijalankan oleh program anda.

Sebagai peringatan, salah satu perbezaan utama AMD berbanding CommonJS ialah memuatkan modul secara tidak segerak. Dalam pengertian ini, dengan AMD, secara teknikal anda tidak benar-benar memerlukan langkah membina di mana anda menggabungkan modul anda ke dalam satu fail kerana anda memuatkan modul anda secara tidak segerak - yang bermaksud anda memuat turun secara berperingkat hanya fail yang sangat diperlukan untuk melaksanakan atur cara dan bukannya memuat turun semua fail sekaligus semasa pengguna pertama kali mengunjungi halaman.

Namun, dalam kenyataannya, overhead permintaan volume tinggi dari masa ke masa untuk setiap tindakan pengguna tidak masuk akal dalam pengeluaran. Sebilangan besar pembangun web masih menggunakan alat binaan untuk menggabungkan dan meminimumkan modul AMD mereka untuk prestasi optimum, menggunakan alat seperti pengoptimum RequireJS, r.js, misalnya.

Secara keseluruhan, perbezaan antara AMD dan CommonJS ketika membundel adalah seperti ini: semasa pembangunan, aplikasi AMD dapat lolos tanpa langkah membangun. Sekurang-kurangnya, sehingga anda mengeluarkan kod tersebut secara langsung, pada tahap mana pengoptimum seperti r.js boleh masuk untuk mengatasinya.

Untuk perbincangan yang menarik mengenai CommonJS vs AMD, lihat catatan ini di blog Tom Dale :)

Beg Web

Sejauh pengikat, Webpack adalah anak baru di blok ini. Ia dirancang untuk menjadi agnostik pada sistem modul yang anda gunakan, yang membolehkan pembangun menggunakan CommonJS, AMD, atau ES6 yang sesuai.

Anda mungkin tertanya-tanya mengapa kita memerlukan Webpack ketika kita sudah mempunyai bundler lain seperti Browserify dan RequireJS yang menyelesaikan tugas dan melakukan pekerjaan yang cukup baik. Nah, untuk satu, Webpack menyediakan beberapa ciri berguna seperti "pemisahan kod" - cara untuk memisahkan pangkalan kod anda menjadi "potongan" yang dimuat berdasarkan permintaan.

Sebagai contoh, jika anda mempunyai aplikasi web dengan blok kod yang hanya diperlukan dalam keadaan tertentu, mungkin tidak efisyen untuk memasukkan keseluruhan pangkalan data ke dalam satu fail berkumpulan besar. Dalam kes ini, anda dapat menggunakan pemisahan kod untuk mengekstrak kod ke dalam potongan yang dapat dimuat berdasarkan permintaan, mengelakkan masalah dengan muatan di muka yang besar ketika kebanyakan pengguna hanya memerlukan inti aplikasi anda.

Pemisahan kod adalah salah satu daripada banyak ciri menarik yang ditawarkan Webpack, dan Internet penuh dengan pendapat yang baik mengenai sama ada Webpack atau Browserify lebih baik. Berikut adalah beberapa perbincangan yang lebih penting yang saya anggap berguna untuk mengatasi masalah ini:

  • //gist.github.com/substack/68f8d502be42d5cd4942
  • //mattdesl.svbtle.com/browserify-vs-webpack
  • //blog.namangoel.com/browserify-vs-webpack-js-drama

Modul ES6

Kembali sudah? Baik! Kerana seterusnya saya ingin membincangkan modul ES6, yang dalam beberapa cara dapat mengurangkan keperluan untuk bundler pada masa akan datang. (Anda akan melihat maksud saya sebentar.) Pertama, mari kita fahami bagaimana modul ES6 dimuat.

Perbezaan yang paling penting antara format Modul JS semasa (CommonJS, AMD) dan modul ES6 adalah bahawa modul ES6 dirancang dengan mempertimbangkan analisis statik. Maksudnya ialah apabila anda mengimport modul, import diselesaikan pada waktu kompilasi - sebelum skrip mula dijalankan. Ini membolehkan kita membuang eksport yang tidak digunakan oleh modul lain sebelum kita menjalankan program. Mengeluarkan eksport yang tidak digunakan boleh menyebabkan penjimatan ruang yang besar, mengurangkan tekanan pada penyemak imbas.

Satu persoalan umum yang timbul ialah: bagaimana ini berbeza dengan penghapusan kod mati yang berlaku apabila anda menggunakan sesuatu seperti UglifyJS untuk meminimumkan kod anda? Jawapannya, seperti biasa, "itu bergantung."

(CATATAN: Penghapusan kod mati adalah langkah pengoptimuman yang menghilangkan kod dan pemboleh ubah yang tidak terpakai - anggap ia sebagai membuang lebihan bagasi yang tidak perlu dijalankan oleh program yang digabungkan, * setelah * ia dibundel).

Kadang-kadang, penghapusan kod mati boleh berlaku sama antara modul UglifyJS dan ES6, dan pada masa yang lain tidak. Terdapat contoh menarik di Rollup's wiki) jika anda ingin melihatnya.

Apa yang membuat modul ES6 berbeza adalah pendekatan yang berbeza untuk penghapusan kod mati, yang disebut "getaran pohon". Gegaran pokok pada dasarnya penghapusan kod mati dibalikkan. Ini hanya merangkumi kod yang perlu dijalankan bundel anda, dan bukannya mengecualikan kod yang tidak diperlukan bundle anda. Mari lihat contoh gegaran pokok:

Katakan kita mempunyai fail utils.js dengan fungsi di bawah, yang masing-masing kita eksport menggunakan sintaks ES6:

Seterusnya, katakan kita tidak tahu fungsi utiliti apa yang ingin kita gunakan dalam program kita, jadi kita teruskan dan mengimport semua modul di main.js seperti:

Dan kemudian kita akhirnya hanya menggunakan setiap fungsi:

Versi "tree shaken" dari fail main.js kami akan kelihatan seperti ini setelah modul dimuat:

Perhatikan bagaimana satu-satunya eksport termasuk yang kita gunakan: masing-masing .

Sementara itu, jika kita memutuskan untuk menggunakan fungsi penapis dan bukannya setiap fungsi, kita akhirnya melihat sesuatu seperti ini:

Versi pokok yang digoncang kelihatan seperti:

Perhatikan bagaimana kali ini masing - masing dan penapis disertakan. Ini kerana penapis didefinisikan untuk menggunakan masing-masing , jadi kami memerlukan kedua-dua eksport agar modul berfungsi.

Cukup licin, ya?

Saya mencabar anda untuk bermain-main dan meneroka gegaran pokok dalam demo dan editor langsung Rollup.js.

Membina modul ES6

Baiklah, jadi kami tahu bahawa modul ES6 dimuat secara berbeza daripada format modul lain, tetapi kami masih belum membincangkan langkah membina ketika anda menggunakan modul ES6.

Malangnya, modul ES6 masih memerlukan kerja tambahan, kerana belum ada implementasi asli untuk bagaimana penyemak imbas memuat modul ES6.

Berikut adalah beberapa pilihan untuk membina / menukar modul ES6 untuk berfungsi di penyemak imbas, dengan # 1 adalah pendekatan yang paling biasa hari ini:

  1. Gunakan transpiler (mis. Babel atau Traceur) untuk menukar kod ES6 anda ke kod ES5 dalam format CommonJS, AMD, atau UMD. Kemudian masukkan kod yang dihantar melalui bundler modul seperti Browserify atau Webpack untuk membuat satu atau lebih fail yang dibundel.
  2. Gunakan Rollup.js, yang sangat mirip dengan pilihan # 1 kecuali Rollup piggybacks pada kekuatan modul ES6 untuk menganalisis secara statik kod ES6 dan kebergantungan anda sebelum menggabungkan. Ia menggunakan "getaran pohon" untuk memasukkan minimum kosong dalam kumpulan anda. Secara keseluruhan, faedah utama Rollup.js melalui Browserify atau Webpack ketika anda menggunakan modul ES6 adalah bahawa gegaran pokok dapat membuat kumpulan anda lebih kecil. Peringatannya ialah Rollup menyediakan beberapa format untuk menggabungkan kod anda, termasuk ES6, CommonJS, AMD, UMD, atau IIFE. Bundel IIFE dan UMD akan berfungsi di penyemak imbas anda sebagaimana adanya, tetapi jika anda memilih untuk menggabungkan ke AMD, CommonJS, atau ES6, anda perlu mencari kaedah lain untuk menukar kod tersebut ke dalam format yang difahami oleh penyemak imbas (contohnya dengan menggunakan Browserify, Webpack, RequireJS, dll.).

Melompat melalui gelung

Sebagai pembangun laman web, kita harus melalui banyak peluang. Tidak selalunya mudah untuk menukar modul ES6 indah kami menjadi sesuatu yang dapat ditafsirkan oleh penyemak imbas.

Persoalannya, bilakah modul ES6 akan berjalan di penyemak imbas tanpa semua overhead ini?

Jawapannya, syukurlah, "lebih cepat daripada kemudian."

ECMAScript kini mempunyai spesifikasi untuk penyelesaian yang disebut ECMAScript 6 modul loader API. Ringkasnya, ini adalah API berdasarkan janji yang terprogram yang seharusnya memuat modul anda secara dinamis dan menyimpannya secara cache sehingga import berikutnya tidak memuatkan versi baru modul.

Ia akan kelihatan seperti ini:

myModule.js

utama.js

Sebagai alternatif, anda juga dapat menentukan modul dengan menentukan "type = module" secara langsung dalam tag skrip, seperti:

Sekiranya anda belum memeriksa repo untuk polyfill API loader modul, saya sangat menggalakkan anda sekurang-kurangnya mengintip.

Lebih-lebih lagi, jika anda ingin menguji pendekatan ini, periksa SystemJS, yang dibina di atas ES6 Module Loader polyfill. SystemJS memuat secara dinamis format modul apa pun (modul ES6, AMD, CommonJS dan / atau skrip global) di penyemak imbas dan di Node. Ini melacak semua modul yang dimuat dalam "modul pendaftaran" untuk mengelakkan memuatkan kembali modul yang sebelumnya dimuat. Belum lagi bahawa ia juga secara automatik melancarkan modul ES6 (jika anda hanya menetapkan pilihan) dan mempunyai kemampuan untuk memuat jenis modul dari jenis lain! Cukup kemas.

Adakah kita masih memerlukan bundler sekarang kerana kita mempunyai modul ES6 asli?

Peningkatan populariti modul ES6 mempunyai beberapa akibat yang menarik:

Adakah HTTP / 2 akan menjadikan pengikat modul menjadi usang?

Dengan HTTP / 1, kami hanya dibenarkan satu permintaan per sambungan TCP. Itulah sebabnya dalam memuatkan pelbagai sumber memerlukan banyak permintaan. Dengan HTTP / 2, semuanya berubah. HTTP / 2 dilipatgandakan sepenuhnya, yang bermaksud pelbagai permintaan dan respons boleh berlaku secara selari. Hasilnya, kami dapat melayani beberapa permintaan secara serentak dengan satu sambungan.

Oleh kerana kos per permintaan HTTP jauh lebih rendah daripada HTTP / 1, memuat sekumpulan modul tidak akan menjadi masalah prestasi besar dalam jangka panjang. Ada yang berpendapat bahawa ini bermaksud penggabungan modul tidak diperlukan lagi. Sudah tentu mungkin, tetapi ia bergantung kepada keadaan.

Pertama, gabungan modul menawarkan faedah yang tidak diambil kira HTTP / 2, seperti membuang eksport yang tidak digunakan untuk menjimatkan ruang. Sekiranya anda membina laman web di mana setiap prestasi penting, penggabungan boleh memberi anda keuntungan tambahan dalam jangka masa panjang. Walaupun begitu, jika keperluan prestasi anda tidak terlalu melampau, anda berpotensi menjimatkan masa dengan kos minimum dengan melewatkan langkah membangun sama sekali.

Secara keseluruhan, kami masih jauh dari kebanyakan laman web yang menyediakan kod mereka melalui HTTP / 2. Saya cenderung untuk meramalkan bahawa proses membina berada di sini untuk bertahan sekurang-kurangnya untuk jangka masa terdekat.

PS: Terdapat perbezaan lain dengan HTTP / 2 juga, dan jika anda ingin tahu, inilah sumber yang hebat.

Adakah CommonJS, AMD, dan UMD akan usang?

Sekali ES6 menjadi yang standard modul, adakah kita benar-benar memerlukan format modul bukan asli yang lain?

Saya meraguinya.

Pembangunan web mendapat banyak manfaat daripada mengikuti satu kaedah standard untuk mengimport dan mengeksport modul dalam JavaScript, tanpa langkah perantaraan. Berapa lama masa yang diperlukan untuk mencapai titik di mana ES6 adalah standard modul?

Kemungkinan, agak lama;)

Selain itu, ada banyak orang yang suka memilih "rasa", jadi "pendekatan yang benar" mungkin tidak pernah menjadi kenyataan.

Kesimpulannya

Saya harap siaran dua bahagian ini dapat membantu membersihkan beberapa pemakaian jargon yang digunakan ketika membincangkan modul dan penggabungan modul. Teruskan dan periksa bahagian I jika anda mendapati salah satu syarat di atas membingungkan.

Seperti biasa, berbincang dengan saya dalam komen dan jangan ragu untuk bertanya!

Selamat bergabung :)