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

Diterbitkan: 2022-03-11

Saat memulai proyek React baru, Anda memiliki banyak template untuk dipilih: Create React App, react-boilerplate , dan React Starter Kit, untuk beberapa nama.

Template ini, yang diadopsi oleh ribuan pengembang, mampu mendukung pengembangan aplikasi dalam skala yang sangat besar. Tapi mereka meninggalkan pengalaman pengembang dan keluaran bundel dibebani dengan berbagai default, yang mungkin tidak ideal.

Jika Anda ingin mempertahankan tingkat kontrol yang lebih besar atas proses pembuatan Anda, maka Anda dapat memilih untuk berinvestasi dalam konfigurasi Webpack kustom. Seperti yang akan Anda pelajari dari tutorial Webpack ini, tugas ini tidak terlalu rumit, dan pengetahuannya bahkan mungkin berguna saat memecahkan masalah konfigurasi orang lain.

Paket Web: Memulai

Cara kita menulis JavaScript saat ini berbeda dengan kode yang dapat dijalankan oleh browser. Kami sering mengandalkan jenis sumber daya lain, bahasa yang ditranspilasikan, dan fitur eksperimental yang belum didukung di browser modern. Webpack adalah bundler modul untuk JavaScript yang dapat menjembatani kesenjangan ini dan menghasilkan kode yang kompatibel dengan lintas browser tanpa biaya dalam hal pengalaman pengembang.

Sebelum kita mulai, Anda harus ingat bahwa semua kode yang disajikan dalam tutorial Webpack ini juga tersedia dalam bentuk file konfigurasi contoh Webpack/React lengkap di GitHub. Silakan merujuk ke sana dan kembali ke artikel ini jika Anda memiliki pertanyaan.

Konfigurasi Dasar

Sejak Legato (versi 4), Webpack tidak memerlukan konfigurasi apa pun untuk dijalankan. Memilih mode build akan menerapkan serangkaian default yang lebih sesuai dengan lingkungan target. Dalam semangat artikel ini, kita akan mengesampingkan default tersebut dan menerapkan konfigurasi yang masuk akal untuk setiap lingkungan target sendiri.

Pertama, kita perlu menginstal webpack dan webpack-cli :

 npm install -D webpack webpack-cli

Kemudian kita perlu mengisi webpack.config.js dengan konfigurasi yang menampilkan opsi berikut:

  • devtool : Mengaktifkan pembuatan peta sumber dalam mode pengembangan.
  • entry : File utama dari aplikasi React kita.
  • output.path : Direktori root untuk menyimpan file output.
  • output.filename : Pola nama file yang digunakan untuk file yang dihasilkan.
  • output.publicPath : Jalur ke direktori root tempat file akan disebarkan di server web.
 const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };

Konfigurasi di atas berfungsi dengan baik untuk file JavaScript biasa. Tetapi saat menggunakan Webpack dan React, kita perlu melakukan transformasi tambahan sebelum mengirimkan kode ke pengguna kita. Di bagian selanjutnya, kita akan menggunakan Babel untuk mengubah cara Webpack memuat file JavaScript.

Pemuat JS

Babel adalah kompiler JavaScript dengan banyak plugin untuk transformasi kode. Di bagian ini, kami akan memperkenalkannya sebagai pemuat ke dalam konfigurasi Webpack kami dan mengonfigurasinya untuk mengubah kode JavaScript modern menjadi seperti yang dipahami oleh browser umum.

Pertama, kita perlu menginstal babel-loader dan @babel/core :

 npm install -D @babel/core babel-loader

Kemudian kita akan menambahkan bagian module ke konfigurasi Webpack kita, membuat babel-loader bertanggung jawab untuk memuat file JavaScript:

 @@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };

Kita akan mengkonfigurasi Babel menggunakan file konfigurasi terpisah, babel.config.js . Ini akan menggunakan fitur-fitur berikut:

  • @babel/preset-env : Mengubah fitur JavaScript modern menjadi kode yang kompatibel dengan versi sebelumnya.
  • @babel/preset-react : Mengubah sintaks JSX menjadi panggilan fungsi JavaScript vanilla biasa.
  • @babel/plugin-transform-runtime : Mengurangi duplikasi kode dengan mengekstrak pembantu Babel ke dalam modul bersama.
  • @babel/plugin-syntax-dynamic-import : Mengaktifkan sintaks dinamis import() di browser yang tidak memiliki dukungan Promise asli.
  • @babel/plugin-proposal-class-properties : Mengaktifkan dukungan untuk proposal sintaks bidang instance publik, untuk menulis komponen React berbasis kelas.

Kami juga akan mengaktifkan beberapa pengoptimalan produksi khusus React:

  • babel-plugin-transform-react-remove-prop-types menghapus prop-types yang tidak perlu dari kode produksi.
  • @babel/plugin-transform-react-inline-elements mengevaluasi React.createElement selama kompilasi dan menyelaraskan hasilnya.
  • @babel/plugin-transform-react-constant-elements mengekstrak elemen React statis sebagai konstanta.

Perintah di bawah ini akan menginstal semua dependensi yang diperlukan:

 npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements

Kemudian kita akan mengisi babel.config.js dengan pengaturan ini:

 module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };

Konfigurasi ini memungkinkan kita untuk menulis JavaScript modern dengan cara yang kompatibel dengan semua browser yang relevan. Ada jenis sumber daya lain yang mungkin kita perlukan dalam aplikasi React, yang akan kita bahas di bagian berikut.

Pemuat CSS

Ketika datang untuk menata aplikasi React, paling tidak, kita harus bisa menyertakan file CSS biasa. Kami akan melakukan ini di Webpack menggunakan loader berikut:

  • css-loader : Mem-parsing file CSS, menyelesaikan sumber daya eksternal, seperti gambar, font, dan impor gaya tambahan.
  • style-loader : Selama pengembangan, menyuntikkan gaya yang dimuat ke dalam dokumen saat runtime.
  • mini-css-extract-plugin : Mengekstrak gaya yang dimuat ke dalam file terpisah untuk penggunaan produksi guna memanfaatkan cache browser.

Mari kita instal pemuat CSS di atas:

 npm install -D css-loader style-loader mini-css-extract-plugin

Kemudian kita akan menambahkan aturan baru ke bagian module.rules dari konfigurasi Webpack kita:

 @@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },

Kami juga akan menambahkan MiniCssExtractPlugin ke bagian plugins , yang hanya akan kami aktifkan dalam mode produksi:

 @@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };

Konfigurasi ini berfungsi untuk file CSS biasa dan dapat diperluas untuk bekerja dengan berbagai prosesor CSS, seperti Sass dan PostCSS, yang akan kita bahas di artikel berikutnya.

Pemuat Gambar

Webpack juga dapat digunakan untuk memuat sumber daya statis seperti gambar, video, dan file biner lainnya. Cara paling umum untuk menangani jenis file tersebut adalah dengan menggunakan file-loader atau url-loader , yang akan memberikan referensi URL untuk sumber daya yang diperlukan kepada konsumennya.

Di bagian ini, kami akan menambahkan url-loader untuk menangani format gambar umum. Apa yang membedakan url-loader dari file-loader adalah jika ukuran file asli lebih kecil dari ambang batas yang diberikan, itu akan menyematkan seluruh file di URL sebagai konten yang disandikan base64, sehingga menghilangkan kebutuhan akan permintaan tambahan.

Pertama kita install url-loader :

 npm install -D url-loader

Kemudian kami menambahkan aturan baru ke bagian module.rules dari konfigurasi Webpack kami:

 @@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },

SVG

Untuk gambar SVG, kita akan menggunakan @svgr/webpack loader, yang mengubah file yang diimpor menjadi komponen React.

Kami menginstal @svgr/webpack :

 npm install -D @svgr/webpack

Kemudian kami menambahkan aturan baru ke bagian module.rules dari konfigurasi Webpack kami:

 @@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },

Gambar SVG sebagai komponen React bisa menjadi nyaman, dan @svgr/webpack melakukan pengoptimalan menggunakan SVGO.

Catatan: Untuk animasi tertentu atau bahkan efek mouseover, Anda mungkin perlu memanipulasi SVG menggunakan JavaScript. Untungnya, @svgr/webpack menyematkan konten SVG ke dalam bundel JavaScript secara default, memungkinkan Anda untuk melewati batasan keamanan yang diperlukan untuk ini.

Pemuat file

Ketika kita perlu mereferensikan jenis file lain, file-loader generik akan melakukan pekerjaan itu. Ini bekerja mirip dengan url-loader , memberikan URL aset ke kode yang membutuhkannya, tetapi tidak berusaha untuk mengoptimalkannya.

Seperti biasa, pertama kita install modul Node.js. Dalam hal ini, file-loader :

 npm install -D file-loader

Kemudian kami menambahkan aturan baru ke bagian module.rules dari konfigurasi Webpack kami. Sebagai contoh:

 @@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },

Di sini kami menambahkan file-loader untuk memuat font, yang dapat Anda rujuk dari file CSS Anda. Anda dapat memperluas contoh ini untuk memuat jenis file lain yang Anda butuhkan.

Plugin Lingkungan

Kita dapat menggunakan DefinePlugin() Webpack untuk mengekspos variabel lingkungan dari lingkungan build ke kode aplikasi kita. Sebagai contoh:

 @@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };

Di sini kami mengganti process.env.NODE_ENV dengan string yang mewakili mode build: "development" atau "production" .

Plugin HTML

Dengan tidak adanya file index.html , bundel JavaScript kami tidak berguna, hanya duduk di sana tanpa ada yang dapat menemukannya. Di bagian ini, kami akan memperkenalkan html-webpack-plugin untuk menghasilkan file HTML untuk kami.

Kami menginstal html-webpack-plugin :

 npm install -D html-webpack-plugin

Kemudian kami menambahkan html-webpack-plugin ke bagian plugins dari konfigurasi Webpack kami:

 @@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };

File public/index.html yang dihasilkan akan memuat bundel kami dan mem-bootstrap aplikasi kami.

Optimasi

Ada beberapa teknik optimasi yang bisa kita gunakan dalam proses build kita. Kita akan mulai dengan minifikasi kode, sebuah proses di mana kita dapat mengurangi ukuran bundel kita tanpa biaya dalam hal fungsionalitas. Kami akan menggunakan dua plugin untuk meminimalkan kode kami: terser-webpack-plugin untuk kode JavaScript, dan optimize-css-assets-webpack-plugin untuk CSS.

Mari kita instal:

 npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin

Kemudian kami akan menambahkan bagian optimization ke konfigurasi kami:

 @@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };

Pengaturan di atas akan memastikan kompatibilitas kode dengan semua browser modern.

Pemisahan Kode

Pemisahan kode adalah teknik lain yang dapat kita gunakan untuk meningkatkan kinerja aplikasi kita. Pemisahan kode dapat mengacu pada dua pendekatan berbeda:

  1. Dengan menggunakan pernyataan import() dinamis, kita dapat mengekstrak bagian aplikasi yang membentuk porsi signifikan dari ukuran bundel kita, dan memuatnya sesuai permintaan.
  2. Kami dapat mengekstrak kode yang lebih jarang berubah, untuk memanfaatkan cache browser dan meningkatkan kinerja untuk pengunjung berulang.

Kami akan mengisi bagian optimization.splitChunks dari konfigurasi Webpack kami dengan pengaturan untuk mengekstrak dependensi pihak ketiga dan potongan umum ke dalam file terpisah:

 @@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };

Mari kita lihat lebih dalam opsi yang kami gunakan di sini:

  • chunks: "all" : Secara default, ekstraksi chunk umum hanya mempengaruhi modul yang dimuat dengan dynamic import() . Pengaturan ini juga memungkinkan pengoptimalan untuk pemuatan titik masuk.
  • minSize: 0 : Secara default, hanya potongan di atas ambang ukuran tertentu yang memenuhi syarat untuk ekstraksi. Pengaturan ini memungkinkan pengoptimalan untuk semua kode umum terlepas dari ukurannya.
  • maxInitialRequests: 20 dan maxAsyncChunks: 20 : Pengaturan ini meningkatkan jumlah maksimum file sumber yang dapat dimuat secara paralel masing-masing untuk impor titik masuk dan impor titik terpisah.

Selain itu, kami menetapkan konfigurasi cacheGroups berikut:

  • vendors : Mengonfigurasi ekstraksi untuk modul pihak ketiga.
    • test: /[\\/]node_modules[\\/]/ : Pola nama file untuk mencocokkan dependensi pihak ketiga.
    • name(module, chunks, cacheGroupKey) : Mengelompokkan potongan-potongan terpisah dari modul yang sama bersama-sama dengan memberi mereka nama yang sama.
  • common : Mengonfigurasi ekstraksi potongan umum dari kode aplikasi.
    • minChunks: 2 : Sebuah chunk akan dianggap umum jika direferensikan dari setidaknya dua modul.
    • priority: -10 : Menetapkan prioritas negatif ke grup cache common sehingga potongan untuk grup cache vendors akan dipertimbangkan terlebih dahulu.

Kami juga mengekstrak kode runtime Webpack dalam satu potongan yang dapat dibagi di antara beberapa titik masuk, dengan menentukan runtimeChunk: "single" .

Server Pengembang

Sejauh ini, kami telah berfokus pada pembuatan dan pengoptimalan pembuatan produksi aplikasi kami, tetapi Webpack juga memiliki server web sendiri dengan pemuatan ulang langsung dan pelaporan kesalahan, yang akan membantu kami dalam proses pengembangan. Ini disebut webpack-dev-server , dan kita perlu menginstalnya secara terpisah:

 npm install -D webpack-dev-server

Dalam cuplikan ini kami memperkenalkan bagian devServer ke dalam konfigurasi Webpack kami:

 @@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };

Di sini kami telah menggunakan opsi berikut:

  • compress: true : Mengaktifkan kompresi aset untuk memuat ulang lebih cepat.
  • historyApiFallback: true : Mengaktifkan fallback ke index.html untuk perutean berbasis riwayat.
  • open: true : Membuka browser setelah meluncurkan server dev.
  • overlay: true : Menampilkan kesalahan Webpack di jendela browser.

Anda mungkin juga perlu mengonfigurasi setelan proxy untuk meneruskan permintaan API ke server backend.

Webpack dan React: Performa dioptimalkan dan Siap!

Kami baru saja mempelajari cara memuat berbagai jenis sumber daya dengan Webpack, cara menggunakan Webpack di lingkungan pengembangan, dan beberapa teknik untuk mengoptimalkan build produksi. Jika perlu, Anda selalu dapat meninjau file konfigurasi lengkap sebagai inspirasi untuk pengaturan React/Webpack Anda sendiri. Mengasah keterampilan seperti itu adalah tarif standar bagi siapa pun yang menawarkan layanan pengembangan React.

Di bagian selanjutnya dari seri ini, kami akan memperluas konfigurasi ini dengan petunjuk untuk kasus penggunaan yang lebih spesifik, termasuk penggunaan TypeScript, praprosesor CSS, dan teknik pengoptimalan lanjutan yang melibatkan rendering sisi server dan ServiceWorkers. Tetap disini untuk mempelajari semua yang perlu Anda ketahui tentang Webpack untuk membawa aplikasi React Anda ke produksi.