jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Webpack · Part 3 — Loaders

How Webpack transforms non-JS files into modules — babel-loader for TS/JSX, css-loader and style-loader, Sass, and built-in asset modules — plus why loader chains run right-to-left. With an interactive loader pipeline.

Webpack only understands JavaScript and JSON out of the box {Webpack chỉ hiểu JavaScript và JSON từ đầu}. Loaders are the adapters that turn everything else — TypeScript, CSS, Sass, images — into modules it can add to the graph {Loader là bộ chuyển đổi biến mọi thứ khác — TypeScript, CSS, Sass, ảnh — thành module nó có thể thêm vào đồ thị}. This is where “everything is a module” becomes real {Đây là nơi “mọi thứ là module” trở thành thật}.

Pick a file type below and run it through its loader chain {Chọn một loại file bên dưới và chạy nó qua chuỗi loader}:


1. What a loader is {Loader là gì}

A loader is a function that takes file contents as input and returns transformed output {Loader là một hàm nhận nội dung file làm input và trả output đã biến đổi}. You declare them under module.rules, matching files by a test regex {Bạn khai báo chúng trong module.rules, khớp file bằng regex test}:

module.exports = {
  module: {
    rules: [
      { test: /\.css$/i, use: ["style-loader", "css-loader"] },
      { test: /\.tsx?$/, use: "babel-loader", exclude: /node_modules/ },
    ],
  },
};

Each rule says: “for files matching test, run them through use” {Mỗi rule nói: “với file khớp test, chạy chúng qua use”}.


2. Loader chains run right-to-left {Chuỗi loader chạy phải-sang-trái}

This trips up everyone {Điều này làm ai cũng vấp}. When use is an array, loaders execute from last to first {Khi use là mảng, loader thực thi từ cuối lên đầu}:

use: ["style-loader", "css-loader"]
// execution: css-loader FIRST, then style-loader

It reads like function composition — style-loader(css-loader(source)) {Đọc như hợp hàm — style-loader(css-loader(source))}. For CSS that’s exactly right {Với CSS thì đúng y vậy}:

  1. css-loader resolves @import and url() and turns the CSS into a JS module {css-loader giải quyết @importurl() và biến CSS thành module JS}.
  2. style-loader takes that and injects a <style> tag into the DOM at runtime {style-loader lấy cái đó và tiêm thẻ <style> vào DOM lúc chạy}.

Swap the order and it breaks {Đảo thứ tự là hỏng}. Run the Sass example in the demo to see a three-loader chain (sass-loadercss-loaderstyle-loader) {Chạy ví dụ Sass trong demo để xem chuỗi ba loader}.


3. The loaders you’ll actually use {Các loader bạn thực sự dùng}

npm install --save-dev babel-loader @babel/core @babel/preset-env \
  css-loader style-loader sass-loader sass
LoaderTurns {Biến}
babel-loaderTS/JSX/modern JS → browser-compatible JS {JS tương thích trình duyệt}
css-loaderCSS → a JS module (resolves imports) {CSS → module JS}
style-loaderJS-ified CSS → injected <style> {CSS đã JS-hóa → <style> tiêm vào}
sass-loaderSass/SCSS → CSS {Sass/SCSS → CSS}
postcss-loaderruns PostCSS (autoprefixer, Tailwind) {chạy PostCSS}

In production you usually replace style-loader with MiniCssExtractPlugin.loader to emit a real .css file instead of injecting it (Part 4) {Ở production bạn thường thay style-loader bằng MiniCssExtractPlugin.loader để phát ra file .css thật thay vì tiêm (Phần 4)}.


4. babel-loader in practice {babel-loader thực tế}

{
  test: /\.(js|jsx|ts|tsx)$/,
  exclude: /node_modules/,
  use: {
    loader: "babel-loader",
    options: {
      presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
    },
  },
}

exclude: /node_modules/ is important — transpiling dependencies is slow and usually unnecessary {exclude: /node_modules/ quan trọng — transpile phụ thuộc thì chậm và thường không cần}. Loader options configure the transform {options của loader cấu hình phép biến đổi}.


5. Asset modules — no loader needed {Asset module — không cần loader}

Webpack 5 has built-in asset handling, replacing file-loader, url-loader, and raw-loader {Webpack 5 có xử lý asset tích hợp sẵn, thay cho file-loader, url-loader, và raw-loader}. Use type instead of use {Dùng type thay vì use}:

{ test: /\.(png|jpe?g|svg|gif)$/i, type: "asset/resource" }   // emits a file, import → URL
{ test: /\.svg$/i, type: "asset/inline" }                       // inlines as a base64 data URI
{ test: /\.txt$/i, type: "asset/source" }                       // imports raw text
{ test: /\.png$/i, type: "asset" }                              // auto: inline if small, file if big

asset (the smart default) inlines files under 8 KB as data URIs and emits larger ones as separate files — fewer requests for tiny assets, caching for big ones {asset (mặc định thông minh) nội tuyến file dưới 8 KB thành data URI và phát file lớn hơn riêng — ít request cho asset nhỏ, cache cho cái lớn}.


6. Rule matching options {Tùy chọn khớp rule}

Beyond test, rules support include, exclude, oneOf (first match wins, faster), and resourceQuery {Ngoài test, rule hỗ trợ include, exclude, oneOf (khớp đầu tiên thắng, nhanh hơn), và resourceQuery}:

{
  oneOf: [
    { test: /\.module\.css$/, use: ["style-loader", { loader: "css-loader", options: { modules: true } }] },
    { test: /\.css$/, use: ["style-loader", "css-loader"] },
  ],
}

oneOf is how you handle CSS Modules (.module.css) differently from plain CSS {oneOf là cách bạn xử lý CSS Modules (.module.css) khác CSS thường}.


7. Exercises {Bài tập}

1. You write use: ["css-loader", "style-loader"] and get “Cannot read property ‘DOM’…” or styles never apply. Why? {Bạn viết use: ["css-loader", "style-loader"] và bị lỗi hoặc style không áp dụng. Vì sao?}

Solution {Lời giải}

Wrong order — chains run right-to-left, so style-loader runs first on raw CSS. It must be ["style-loader", "css-loader"] {Sai thứ tự — chuỗi chạy phải-sang-trái, nên style-loader chạy trước trên CSS thô. Phải là ["style-loader", "css-loader"]}.

2. You want small icons inlined as data URIs but large images emitted as files, automatically. Which asset module type? {Bạn muốn icon nhỏ nội tuyến thành data URI nhưng ảnh lớn phát ra file, tự động. Loại asset module nào?}

Solution {Lời giải}

type: "asset" — it inlines under ~8 KB and emits files above {type: "asset" — nội tuyến dưới ~8 KB và phát file ở trên}.

3. Your build is slow because Babel is transpiling everything in node_modules. Fix? {Build chậm vì Babel transpile mọi thứ trong node_modules. Sửa?}

Solution {Lời giải}

Add exclude: /node_modules/ to the babel-loader rule {Thêm exclude: /node_modules/ vào rule babel-loader}.

Stretch {Nâng cao}: in the pipeline demo, run the theme.scss example and confirm the execution order is sass-loader → css-loader → style-loader, the reverse of the config array {trong demo pipeline, chạy ví dụ theme.scss và xác nhận thứ tự thực thi là sass-loader → css-loader → style-loader, ngược với mảng config}.


Key takeaways {Điểm chính}

  • Loaders transform non-JS files into modules, declared in module.rules {Loader biến đổi file không phải JS thành module, khai báo trong module.rules}.
  • Loader chains in use arrays run right-to-left {Chuỗi loader trong mảng use chạy phải-sang-trái}.
  • babel-loader handles TS/JSX; always exclude: /node_modules/ {babel-loader lo TS/JSX; luôn exclude: /node_modules/}.
  • CSS chains as ["style-loader", "css-loader"] (+ sass-loader for Sass) {CSS xâu chuỗi ["style-loader", "css-loader"] (+ sass-loader cho Sass)}.
  • Webpack 5 asset modules (type) replace file/url/raw-loader {Asset module của Webpack 5 (type) thay file/url/raw-loader}.

Next up {Tiếp theo}

Part 4 — Plugins: where loaders transform individual files, plugins hook into the whole build — HtmlWebpackPlugin, DefinePlugin, MiniCssExtractPlugin — and how the plugin/hook system works {Phần 4 — Plugin: loader biến đổi từng file, còn plugin móc vào cả build — HtmlWebpackPlugin, DefinePlugin, MiniCssExtractPlugin — và cách hệ thống plugin/hook hoạt động}.