Webpack · Part 10 — Advanced Resolve + Authoring Your Own Loader & Plugin
How module resolution actually works (resolve, alias, extensions, modules, externals), then write a real custom loader and a custom plugin tapping compiler hooks. With a live loader & plugin lab.
Bạn đã dùng loader và plugin suốt chín phần. Giờ bạn tự viết — và khi làm xong, cả công cụ thôi cảm giác như phép thuật. Trước hết, phần chạy trước mọi loader: phân giải module.
Chạy một loader nhỏ trên input thật và đọc khung plugin hoạt động trong lab bên dưới:
1. Phân giải hoạt động thế nào
Khi code nói import x from "./util", Webpack phải biến chuỗi đó thành một file chính xác trên đĩa. Việc đó là phân giải:
module.exports = {
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", ".json"], // try in order
modules: ["src", "node_modules"], // where to look for bare imports
alias: {
"@": path.resolve(__dirname, "src"),
"@components": path.resolve(__dirname, "src/components"),
},
},
};
- cho phép viết
import "./util"và để Webpack thử.tsx,.ts,.js… theo thứ tự. - thêm
"src"cho phép viếtimport "components/Button"thay vì"../../components/Button". - tên ngắn, ổn định, sống sót qua refactor.
2. Alias vs paths của TypeScript
Nếu dùng TypeScript, bạn đặt cùng lối tắt ở hai nơi — TS cần để check kiểu, Webpack cần để bundle:
// tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["src/*"] } } }
// webpack.config.js
resolve: { alias: { "@": path.resolve(__dirname, "src") } }
Giữ chúng đồng bộ, hoặc dùng tsconfig-paths-webpack-plugin để suy ra alias Webpack từ tsconfig tự động.
3. externals — đừng bundle cái này
Đôi khi một phụ thuộc được tải cách khác (CDN <script>, hoặc do app chủ cung cấp). Bảo Webpack để yên nó:
module.exports = {
externals: {
react: "React", // expect a global `React` at runtime
"react-dom": "ReactDOM",
},
};
Giờ import React from "react" biên dịch thành tham chiếu tới global, và React không nằm trong bundle. Thiết yếu cho thư viện và micro-frontend (Phần 11).
4. Viết một loader
Loader là một hàm nhận source và trả về source đã biến đổi. Đó là toàn bộ hợp đồng:
// my-banner-loader.js
module.exports = function (source) {
const { author = "anon" } = this.getOptions() || {};
return `/* © ${author} ${new Date().getFullYear()} */\n${source}`;
};
Gắn nó qua đường dẫn (loader được phân giải như module):
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: path.resolve("./my-banner-loader.js"),
options: { author: "Vinh" },
},
},
],
},
};
Bật/tắt tùy chọn trong lab phía trên để xem output đổi từng dòng.
Quy tắc quan trọng
- Chạy phải-sang-trái —
brồia. - Trả về chuỗi (hoặc gọi
this.callbackcho source map). - Thuần & cache được — cùng input → cùng output.
- Bất đồng bộ? dùng
this.async().
5. Viết một plugin
Plugin là một class có apply(compiler) móc vào một trong nhiều hook của Webpack. Loader biến đổi từng file, plugin tác động lên cả build:
class BuildManifestPlugin {
apply(compiler) {
// emit hook: add assets before they're written to disk
compiler.hooks.emit.tapAsync("BuildManifestPlugin", (compilation, cb) => {
const files = Object.keys(compilation.assets).join("\n");
compilation.assets["manifest.txt"] = {
source: () => files,
size: () => files.length,
};
cb();
});
}
}
module.exports = BuildManifestPlugin;
plugins: [new BuildManifestPlugin()];
Đổi hook trong lab để xem khung done, emit, compile.
Các hook hay dùng
| Hook | Kích hoạt khi | Dùng để |
|---|---|---|
compile | một compilation bắt đầu | log, đồng hồ |
compilation | tạo object compilation | móc hook con |
emit | asset sẵn sàng, chưa ghi | thêm/sửa file output |
done | build xong | báo cáo, thông báo |
Hệ thống hook của Webpack là Tapable — tap, tapAsync, tapPromise.
6. Khi nào viết cái nào
- Cần biến đổi nội dung file? → loader.
- Cần tác động lên build? → plugin.
- Cần cả hai? Nhiều công cụ ship cả loader và plugin nói chuyện với nhau.
7. Bài tập
1. Bạn cứ viết import dài. Hai cấu hình nào làm @components/Button chạy trong dự án TS?
Lời giải
resolve.alias trong webpack config và compilerOptions.paths trong tsconfig.
2. Thư viện của bạn không nên bundle React vì app chủ đã có. Cấu hình gì?
Lời giải
externals ánh xạ React sang global.
3. Bạn muốn mọi file JS output bắt đầu bằng banner giấy phép. Loader hay plugin?
Lời giải
Cả hai được, nhưng loader đơn giản nhất cho nội dung từng file (hoặc dùng webpack.BannerPlugin sẵn có).
Nâng cao:trong lab, bật cả ba tùy chọn loader, rồi tự viết hàm loader từ trí nhớ trước khi xem source.
Điểm chính
- Phân giải biến chuỗi import thành file — chỉnh bằng
extensions,modules,alias. - Giữ
resolve.aliasvàtsconfig.pathsđồng bộ. externalsgiữ một phụ thuộc khỏi bundle.- Loader là hàm biến đổi nội dung file, chạy phải-sang-trái.
- Plugin là class có
apply(compiler)móc hook để tác động cả build.
Tiếp theo
Phần 11 — Module Federation: chia sẻ code và phụ thuộc giữa các app triển khai độc lập lúc runtime, nền tảng của micro-frontend hiện đại.