Webpack · Part 4 — Plugins
Where loaders transform individual files, plugins hook into the whole build — HtmlWebpackPlugin, DefinePlugin, MiniCssExtractPlugin, CopyWebpackPlugin — and how the tapable hook system works. With an interactive plugin lifecycle visualizer.
Loaders work on one file at a time {Loader làm việc trên một file mỗi lần}. Plugins are far more powerful: they tap into the entire compilation lifecycle and can do anything — generate HTML, extract CSS, define globals, copy files, inject banners {Plugin mạnh hơn nhiều: chúng móc vào toàn bộ vòng đời biên dịch và làm được mọi thứ — tạo HTML, trích CSS, định nghĩa global, copy file, tiêm banner}. If loaders are about transforming, plugins are about everything else {Nếu loader là về biến đổi, thì plugin là về mọi thứ còn lại}.
Toggle plugins below, run the build, and watch each fire at its hook {Bật/tắt plugin bên dưới, chạy build, và xem mỗi cái kích hoạt ở hook của nó}:
1. How plugins work {Plugin hoạt động thế nào}
A plugin is a class with an apply(compiler) method {Plugin là một class có method apply(compiler)}. Inside, it taps into one or more hooks that Webpack fires at specific points in the build {Bên trong, nó tap vào một hoặc nhiều hook mà Webpack kích hoạt ở các điểm cụ thể trong build}:
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap("MyPlugin", (stats) => {
console.log("Build finished in", stats.endTime - stats.startTime, "ms");
});
}
}
Webpack’s whole architecture is built on the tapable hook system — compile, make, emit, afterEmit, done, and many more, in a fixed order {Toàn bộ kiến trúc Webpack dựng trên hệ thống hook tapable — compile, make, emit, afterEmit, done, và nhiều nữa, theo thứ tự cố định}. The demo above shows the major ones and which plugin taps each {Demo trên hiện các hook chính và plugin nào tap cái nào}. We write our own plugin in Part 10 {Ta tự viết plugin ở Phần 10}.
2. Using plugins {Dùng plugin}
Plugins go in the plugins array — instances, created with new {Plugin nằm trong mảng plugins — là instance, tạo bằng new}:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: "./src/index.html" }),
new webpack.DefinePlugin({ "process.env.API": JSON.stringify("https://api.example.com") }),
],
};
The most common beginner mistake is forgetting new {Lỗi người mới phổ biến nhất là quên new}.
3. HtmlWebpackPlugin — the must-have {HtmlWebpackPlugin — cái phải có}
It generates an index.html and automatically injects <script> (and <link>) tags for your bundles — crucial once filenames have content hashes {Nó tạo index.html và tự động tiêm thẻ <script> (và <link>) cho bundle — cực kỳ quan trọng một khi filename có content hash}:
npm install --save-dev html-webpack-plugin
new HtmlWebpackPlugin({
template: "./src/index.html", // your HTML shell
title: "My App",
favicon: "./src/favicon.ico",
})
Without it you’d hand-edit <script src="main.abc123.js"> after every build — impossible with hashed names {Không có nó bạn phải sửa tay <script src="main.abc123.js"> sau mỗi build — bất khả với tên băm}.
4. DefinePlugin — compile-time constants {DefinePlugin — hằng số lúc biên dịch}
It replaces expressions with values at build time, before minification {Nó thay biểu thức bằng giá trị lúc build, trước khi minify}:
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("production"),
__VERSION__: JSON.stringify(require("./package.json").version),
})
This is how process.env.NODE_ENV === "production" checks get eliminated, letting tree shaking drop dev-only code (Part 7) {Đây là cách các kiểm tra process.env.NODE_ENV === "production" bị loại bỏ, để tree shaking bỏ code chỉ-dành-cho-dev (Phần 7)}. Note the JSON.stringify — values are inlined as literal code, so a string needs its quotes {Lưu ý JSON.stringify — giá trị được nội tuyến như code literal, nên chuỗi cần dấu nháy}.
5. MiniCssExtractPlugin — real CSS files {MiniCssExtractPlugin — file CSS thật}
In Part 3 style-loader injected CSS via JS at runtime {Ở Phần 3 style-loader tiêm CSS qua JS lúc chạy}. For production you want a separate cacheable .css file instead {Cho production bạn muốn một file .css riêng, cache được}:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }],
},
plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
};
Notice it’s both a loader (replacing style-loader) and a plugin (to emit the file) — a common pattern {Để ý nó vừa là loader (thay style-loader) vừa là plugin (để phát file) — mẫu phổ biến}.
6. Other plugins you’ll meet {Plugin khác bạn sẽ gặp}
| Plugin | Does {Làm} |
|---|---|
CopyWebpackPlugin | copies static files (favicon, robots.txt) into dist/ {copy file tĩnh vào dist/} |
BannerPlugin (built-in) | prepends a license/comment banner {thêm banner license/comment} |
ProgressPlugin (built-in) | reports build progress {báo tiến độ build} |
BundleAnalyzerPlugin | visualizes bundle size (Part 9) {trực quan kích thước bundle (Phần 9)} |
DefinePlugin / EnvironmentPlugin | inject build-time constants {tiêm hằng số lúc build} |
Built-in plugins live on the webpack object (new webpack.BannerPlugin(...)); others are separate npm packages {Plugin tích hợp nằm trên object webpack; cái khác là package npm riêng}.
7. Loaders vs plugins {Loader vs plugin}
The distinction in one line {Phân biệt trong một dòng}: loaders transform individual files as they enter the graph; plugins act on the whole build and its output {loader biến đổi từng file khi chúng vào đồ thị; plugin tác động lên cả build và output của nó}. Need to handle a file type? Loader {Cần xử lý một loại file? Loader}. Need to generate, copy, inject, or optimize across the build? Plugin {Cần tạo, copy, tiêm, hay tối ưu xuyên build? Plugin}.
8. Exercises {Bài tập}
1. You add HtmlWebpackPlugin but Webpack throws “is not a constructor”. What did you forget? {Bạn thêm HtmlWebpackPlugin nhưng Webpack ném “is not a constructor”. Bạn quên gì?}
Solution {Lời giải}
new — plugins must be instantiated: new HtmlWebpackPlugin({...}) {new — plugin phải được khởi tạo: new HtmlWebpackPlugin({...})}.
2. DefinePlugin injects your API URL but the build fails with a syntax error where the value is used. Why? {DefinePlugin tiêm URL API nhưng build lỗi cú pháp ở nơi dùng giá trị. Vì sao?}
Solution {Lời giải}
You passed the raw string instead of JSON.stringify(...), so it was inlined as an identifier, not a string literal {Bạn truyền chuỗi thô thay vì JSON.stringify(...), nên nó bị nội tuyến như định danh, không phải string literal}.
3. You want a cacheable styles.css file in production instead of JS-injected styles. Which plugin + loader change? {Bạn muốn một file styles.css cache được ở production thay vì style tiêm bằng JS. Đổi plugin + loader nào?}
Solution {Lời giải}
Use MiniCssExtractPlugin and replace style-loader with MiniCssExtractPlugin.loader {Dùng MiniCssExtractPlugin và thay style-loader bằng MiniCssExtractPlugin.loader}.
Stretch {Nâng cao}: in the visualizer, enable MiniCssExtractPlugin and CopyWebpackPlugin, run the build, and note the extra main.css, favicon.ico, and robots.txt in the output {trong trình trực quan, bật MiniCssExtractPlugin và CopyWebpackPlugin, chạy build, và để ý thêm main.css, favicon.ico, robots.txt ở output}.
Key takeaways {Điểm chính}
- Plugins tap into build hooks (
compile,emit,done…) and act on the whole compilation {Plugin tap vào hook build và tác động lên cả quá trình biên dịch}. - They go in the
pluginsarray as instances (new) {Chúng nằm trong mảngpluginsdưới dạng instance (new)}. HtmlWebpackPlugingenerates HTML and injects hashed bundle tags {HtmlWebpackPlugintạo HTML và tiêm thẻ bundle đã băm}.DefinePlugininlines compile-time constants (useJSON.stringify) {DefinePluginnội tuyến hằng số lúc biên dịch}.MiniCssExtractPluginemits real.cssfiles for production {MiniCssExtractPluginphát file.cssthật cho production}.
Next up {Tiếp theo}
Part 5 — Dev experience: webpack-dev-server, Hot Module Replacement, and source maps — the fast feedback loop that makes day-to-day development pleasant {Phần 5 — Trải nghiệm dev: webpack-dev-server, Hot Module Replacement, và source map — vòng phản hồi nhanh khiến phát triển hằng ngày dễ chịu}.