Panduan untuk Mengelola Dependensi Webpack

Diterbitkan: 2022-03-11

Konsep modularisasi adalah bagian yang tidak terpisahkan dari sebagian besar bahasa pemrograman modern. JavaScript, bagaimanapun, tidak memiliki pendekatan formal untuk modularisasi sampai kedatangan versi terbaru dari ECMAScript ES6.

Di Node.js, salah satu kerangka kerja JavaScript paling populer saat ini, pembundel modul memungkinkan memuat modul NPM di browser web, dan pustaka berorientasi komponen (seperti React) mendorong dan memfasilitasi modularisasi kode JavaScript.

Webpack adalah salah satu bundler modul yang tersedia yang memproses kode JavaScript, serta semua aset statis, seperti stylesheet, gambar, dan font, ke dalam file yang dibundel. Pemrosesan dapat mencakup semua tugas yang diperlukan untuk mengelola dan mengoptimalkan dependensi kode, seperti kompilasi, penggabungan, minifikasi, dan kompresi.

Webpack: Tutorial Pemula

Namun, mengonfigurasi Webpack dan dependensinya bisa membuat stres dan tidak selalu merupakan proses yang mudah, terutama untuk pemula.

Posting blog ini memberikan panduan, dengan contoh, tentang cara mengkonfigurasi Webpack untuk skenario yang berbeda, dan menunjukkan perangkap paling umum yang terkait dengan bundling dependensi proyek menggunakan Webpack.

Bagian pertama dari posting blog ini menjelaskan cara menyederhanakan definisi dependensi dalam sebuah proyek. Selanjutnya, kita membahas dan mendemonstrasikan konfigurasi untuk pemecahan kode aplikasi halaman ganda dan tunggal. Terakhir, kami membahas cara mengkonfigurasi Webpack, jika kami ingin menyertakan perpustakaan pihak ketiga dalam proyek kami.

Mengonfigurasi Alias ​​​​dan Jalur Relatif

Jalur relatif tidak terkait langsung dengan dependensi, tetapi kami menggunakannya saat kami mendefinisikan dependensi. Jika struktur file proyek rumit, mungkin sulit untuk menyelesaikan jalur modul yang relevan. Salah satu manfaat paling mendasar dari konfigurasi Webpack adalah membantu menyederhanakan definisi jalur relatif dalam sebuah proyek.

Katakanlah kita memiliki struktur proyek berikut:

 - Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js

Kami dapat mereferensikan dependensi dengan jalur relatif ke file yang kami butuhkan, dan jika kami ingin mengimpor komponen ke dalam wadah dalam kode sumber kami, tampilannya seperti berikut:


Home.js

 Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';


Modal.js

 import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

Setiap kali kita ingin mengimpor skrip atau modul, kita perlu mengetahui lokasi direktori saat ini dan menemukan jalur relatif ke apa yang ingin kita impor. Kita dapat membayangkan bagaimana masalah ini dapat meningkat dalam kompleksitas jika kita memiliki proyek besar dengan struktur file bersarang, atau kita ingin refactor beberapa bagian dari struktur proyek yang kompleks.

Kami dapat dengan mudah menangani masalah ini dengan opsi resolve.alias Webpack. Kami dapat mendeklarasikan apa yang disebut alias – nama direktori atau modul dengan lokasinya, dan kami tidak bergantung pada jalur relatif dalam kode sumber proyek.


webpack.config.js

 resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }

Dalam file Modal.js , kita sekarang dapat mengimpor datepicker dengan lebih sederhana:

 import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

Pemisahan Kode

Kami dapat memiliki skenario di mana kami perlu menambahkan skrip ke dalam bundel terakhir, atau membagi bundel terakhir, atau kami ingin memuat bundel terpisah sesuai permintaan. Menyiapkan proyek dan konfigurasi Webpack kami untuk skenario ini mungkin tidak mudah.

Dalam konfigurasi Webpack, opsi Entry memberi tahu Webpack di mana titik awal adalah untuk bundel terakhir. Titik masuk dapat memiliki tiga tipe data yang berbeda: String, Array, atau Object.

Jika kita memiliki satu titik awal, kita dapat menggunakan salah satu format ini dan mendapatkan hasil yang sama.

Jika kita ingin menambahkan banyak file, dan mereka tidak saling bergantung, kita dapat menggunakan format Array. Misalnya, kita dapat menambahkan analytics.js ke akhir bundle.js :


webpack.config.js

 module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };

Mengelola Beberapa Titik Masuk

Katakanlah kita memiliki aplikasi multi-halaman dengan beberapa file HTML, seperti index.html dan admin.html . Kami dapat menghasilkan beberapa bundel dengan menggunakan titik masuk sebagai tipe Objek. Konfigurasi di bawah ini menghasilkan dua bundel JavaScript:


webpack.config.js

 module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };


index.html

 <script src=”build/index.js”></script>


admin.html

 <script src=”build/admin.js”></script>

Kedua bundel JavaScript dapat berbagi pustaka dan komponen umum. Untuk itu, kita dapat menggunakan CommonsChunkPlugin , yang menemukan modul yang muncul di beberapa potongan entri dan membuat bundel bersama yang dapat di-cache di antara beberapa halaman.


webpack.config.js

 var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };

Sekarang, kita tidak boleh lupa untuk menambahkan <script src="build/common.js"></script> sebelum skrip yang dibundel.

Mengaktifkan Pemuatan Malas

Webpack dapat membagi aset statis menjadi bagian yang lebih kecil, dan pendekatan ini lebih fleksibel daripada penggabungan standar. Jika kita memiliki aplikasi satu halaman besar (SPA), penggabungan sederhana menjadi satu bundel bukanlah pendekatan yang baik karena memuat satu bundel besar bisa lambat, dan pengguna biasanya tidak memerlukan semua dependensi pada setiap tampilan.

Kami menjelaskan sebelumnya cara membagi aplikasi menjadi beberapa bundel, menggabungkan dependensi umum, dan memanfaatkan perilaku cache browser. Pendekatan ini bekerja sangat baik untuk aplikasi multi-halaman, tetapi tidak untuk aplikasi satu halaman.

Untuk SPA, kami hanya menyediakan aset statis yang diperlukan untuk merender tampilan saat ini. Router sisi klien dalam arsitektur SPA adalah tempat yang sempurna untuk menangani pemecahan kode. Saat pengguna memasuki rute, kami hanya dapat memuat dependensi yang diperlukan untuk tampilan yang dihasilkan. Atau, kita dapat memuat dependensi saat pengguna menggulir halaman ke bawah.

Untuk tujuan ini, kita dapat menggunakan fungsi require.ensure atau System.import , yang dapat dideteksi secara statis oleh Webpack. Webpack dapat membuat bundel terpisah berdasarkan titik pemisahan ini dan menyebutnya sesuai permintaan.

Dalam contoh ini, kami memiliki dua kontainer React; tampilan admin dan tampilan dasbor.


admin.jsx

 import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }


dashboard.jsx

 import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }

Jika pengguna memasukkan URL /dashboard atau /admin , hanya bundel JavaScript yang diperlukan yang akan dimuat. Di bawah ini kita dapat melihat contoh dengan dan tanpa router sisi klien.


index.jsx

 if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }


index.jsx

 ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );

Mengekstrak Gaya Menjadi Bundel Terpisah

Di Webpack, loader, seperti style-loader dan css-loader , melakukan pra-proses stylesheet dan menyematkannya ke dalam bundel JavaScript keluaran, tetapi dalam beberapa kasus, mereka dapat menyebabkan Flash of unstyled content (FOUC).

Kita dapat menghindari FOUC dengan ExtractTextWebpackPlugin yang memungkinkan pembuatan semua gaya ke dalam bundel CSS terpisah alih-alih menyematkannya di bundel JavaScript akhir.


webpack.config.js

 var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }

Menangani Pustaka dan Plugin Pihak Ketiga

Sering kali, kita perlu menggunakan perpustakaan pihak ketiga, berbagai plugin, atau skrip tambahan, karena kita tidak ingin menghabiskan waktu untuk mengembangkan komponen yang sama dari awal. Ada banyak pustaka dan plugin lama yang tersedia yang tidak dipelihara secara aktif, tidak memahami modul JavaScript, dan menganggap keberadaan dependensi secara global dengan nama yang telah ditentukan sebelumnya.

Di bawah ini adalah beberapa contoh dengan plugin jQuery, dengan penjelasan tentang cara mengkonfigurasi Webpack dengan benar untuk dapat menghasilkan bundel akhir.

Menyediakan Plugin

Sebagian besar plugin pihak ketiga mengandalkan keberadaan dependensi global tertentu. Dalam kasus jQuery, plugin bergantung pada variabel $ atau jQuery yang didefinisikan, dan kita dapat menggunakan plugin jQuery dengan memanggil $('div.content').pluginFunc() dalam kode kita.

Kita dapat menggunakan plugin Webpack ProvidePlugin untuk menambahkan var $ = require("jquery") setiap kali menemukan pengenal $ global.


webpack.config.js

 webpack.ProvidePlugin({ '$': 'jquery', })

Saat Webpack memproses kode, ia mencari keberadaan $ , dan memberikan referensi ke dependensi global tanpa mengimpor modul yang ditentukan oleh fungsi require .

Impor-loader

Beberapa plugin jQuery mengasumsikan $ di namespace global atau mengandalkan this sebagai objek window . Untuk tujuan ini, kita dapat menggunakan imports-loader yang menginjeksi variabel global ke dalam modul.


example.js

 $('div.content').pluginFunc();

Kemudian, kita dapat menyuntikkan variabel $ ke dalam modul dengan mengonfigurasi imports-loader :

 require("imports?$=jquery!./example.js");

Ini hanya menambahkan var $ = require("jquery"); untuk example.js .

Dalam kasus penggunaan kedua:


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }

Dengan menggunakan simbol => (jangan bingung dengan fungsi ES6 Arrow), kita dapat mengatur variabel arbitrer. Nilai terakhir mendefinisikan ulang variabel global this untuk menunjuk ke objek window . Ini sama dengan membungkus seluruh isi file dengan (function () { ... }).call(window); dan memanggil fungsi this dengan window sebagai argumen.

Kami juga dapat meminta perpustakaan menggunakan format modul CommonJS atau AMD:

 // CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });

Beberapa perpustakaan dan modul dapat mendukung format modul yang berbeda.

Pada contoh berikutnya, kami memiliki plugin jQuery yang menggunakan format modul AMD dan CommonJS dan memiliki ketergantungan jQuery:


jquery-plugin.js

 (function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }

Kita dapat memilih format modul apa yang ingin kita gunakan untuk perpustakaan tertentu. Jika kita mendeklarasikan define menjadi equal false , Webpack tidak mengurai modul dalam format modul AMD, dan jika kita mendeklarasikan exports variabel menjadi equal false , Webpack tidak mengurai modul dalam format modul CommonJS.

Expose-loader

Jika kita perlu mengekspos modul ke konteks global, kita dapat menggunakan expose-loader . Ini dapat membantu, misalnya, jika kita memiliki skrip eksternal yang bukan bagian dari konfigurasi Webpack dan bergantung pada simbol di namespace global, atau kita menggunakan plugin browser yang perlu mengakses simbol di konsol browser.


webpack.config.js

 module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }

Pustaka jQuery sekarang tersedia di namespace global untuk skrip lain di halaman web.

 window.$ window.jQuery

Mengonfigurasi Dependensi Eksternal

Jika kita ingin memasukkan modul dari skrip yang dihosting secara eksternal, kita perlu mendefinisikannya dalam konfigurasi. Jika tidak, Webpack tidak dapat menghasilkan bundel terakhir.

Kita dapat mengkonfigurasi skrip eksternal dengan menggunakan opsi externals dalam konfigurasi Webpack. Misalnya, kita dapat menggunakan perpustakaan dari CDN melalui tag <script> terpisah, sambil tetap secara eksplisit mendeklarasikannya sebagai dependensi modul dalam proyek kita.


webpack.config.js

 externals: { react: 'React', 'react-dom': 'ReactDOM' }

Mendukung Beberapa Instance Perpustakaan

Sangat bagus untuk menggunakan manajer paket NPM dalam pengembangan front-end untuk mengelola perpustakaan dan dependensi pihak ketiga. Namun, terkadang kita dapat memiliki beberapa instance dari library yang sama dengan versi yang berbeda, dan mereka tidak bermain bersama dengan baik dalam satu lingkungan.

Ini bisa terjadi, misalnya, dengan perpustakaan React, di mana kita dapat menginstal React dari NPM dan kemudian versi React yang berbeda dapat tersedia dengan beberapa paket atau plugin tambahan. Struktur proyek kami dapat terlihat seperti berikut:

 project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react

Komponen yang berasal dari react-plugin memiliki instance React yang berbeda dari komponen lainnya dalam proyek. Sekarang kami memiliki dua salinan React yang terpisah, dan keduanya dapat menjadi versi yang berbeda. Dalam aplikasi kami, skenario ini dapat mengacaukan DOM global kami yang dapat diubah, dan kami dapat melihat pesan kesalahan di log konsol web. Solusi untuk masalah ini adalah memiliki versi React yang sama di seluruh proyek. Kita bisa menyelesaikannya dengan alias Webpack.


webpack.config.js

 module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }

Ketika react-plugin mencoba meminta React, ia menggunakan versi di node_modules proyek. Jika kita ingin mengetahui versi React yang kita gunakan, kita dapat menambahkan console.log(React.version) pada kode sumbernya.

Fokus pada Pengembangan, Bukan Konfigurasi Webpack

Posting ini hanya menggores permukaan kekuatan dan utilitas Webpack.

Ada banyak pemuat dan plugin Webpack lain yang akan membantu Anda mengoptimalkan dan merampingkan bundling JavaScript.

Bahkan jika Anda seorang pemula, panduan ini memberi Anda dasar yang kuat untuk mulai menggunakan Webpack, yang akan memungkinkan Anda untuk lebih fokus pada pengembangan lebih sedikit pada konfigurasi bundling.

Terkait: Pertahankan Kontrol: Panduan untuk Webpack dan React, Pt. 1