jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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ết import "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 — b rồi a.
  • Trả về chuỗi (hoặc gọi this.callback cho 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

HookKích hoạt khiDùng để
compilemột compilation bắt đầulog, đồng hồ
compilationtạo object compilationmóc hook con
emitasset sẵn sàng, chưa ghithêm/sửa file output
donebuild xongbáo cáo, thông báo

Hệ thống hook của Webpack là Tapabletap, 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 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 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.aliastsconfig.paths đồng bộ.
  • externals giữ 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.