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.
Loader làm việc trên một file mỗi lần. 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. Nếu loader là về biến đổi, thì plugin là về mọi thứ còn lại.
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. Plugin hoạt động thế nào
Plugin là một class có method apply(compiler). 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");
});
}
}
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. Demo trên hiện các hook chính và plugin nào tap cái nào. Ta tự viết plugin ở Phần 10.
2. Dùng plugin
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") }),
],
};
Lỗi người mới phổ biến nhất là quên new.
3. HtmlWebpackPlugin — cái phải có
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",
})
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 — hằng số lúc biên dịch
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),
})
Đâ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). 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 — file CSS thật
Ở Phần 3 style-loader tiêm CSS qua JS lúc chạy. 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" })],
};
Để ý nó vừa là loader (thay style-loader) vừa là plugin (để phát file) — mẫu phổ biến.
6. Plugin khác bạn sẽ gặp
| Plugin | Làm |
|---|---|
CopyWebpackPlugin | copy file tĩnh vào dist/ |
BannerPlugin (built-in) | thêm banner license/comment |
ProgressPlugin (built-in) | báo tiến độ build |
BundleAnalyzerPlugin | trực quan kích thước bundle (Phần 9) |
DefinePlugin / EnvironmentPlugin | tiêm hằng số lúc build |
Plugin tích hợp nằm trên object webpack; cái khác là package npm riêng.
7. Loader vs plugin
Phân biệt trong một dòng: 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ó. Cần xử lý một loại file? Loader. Cần tạo, copy, tiêm, hay tối ưu xuyên build? Plugin.
8. Bài tập
1. Bạn thêm HtmlWebpackPlugin nhưng Webpack ném “is not a constructor”. Bạn quên gì?
Lời giải
new — plugin phải được khởi tạo: new HtmlWebpackPlugin({...}).
2. DefinePlugin tiêm URL API nhưng build lỗi cú pháp ở nơi dùng giá trị. Vì sao?
Lời giải
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. 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?
Lời giải
Dùng MiniCssExtractPlugin và thay style-loader bằng MiniCssExtractPlugin.loader.
Nâng cao:trong trình trực quan, bật MiniCssExtractPlugin và CopyWebpackPlugin, chạy build, và để ý thêm main.css, favicon.ico, robots.txt ở output.
Điểm chính
- Plugin tap vào hook build và tác động lên cả quá trình biên dịch.
- Chúng nằm trong mảng
pluginsdưới dạng instance (new). HtmlWebpackPlugintạo HTML và tiêm thẻ bundle đã băm.DefinePluginnội tuyến hằng số lúc biên dịch.MiniCssExtractPluginphát file.cssthật cho production.
Tiếp theo
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.