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}:
css-loaderresolves@importandurl()and turns the CSS into a JS module {css-loadergiải quyết@importvàurl()và biến CSS thành module JS}.style-loadertakes that and injects a<style>tag into the DOM at runtime {style-loaderlấ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-loader → css-loader → style-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
| Loader | Turns {Biến} |
|---|---|
babel-loader | TS/JSX/modern JS → browser-compatible JS {JS tương thích trình duyệt} |
css-loader | CSS → a JS module (resolves imports) {CSS → module JS} |
style-loader | JS-ified CSS → injected <style> {CSS đã JS-hóa → <style> tiêm vào} |
sass-loader | Sass/SCSS → CSS {Sass/SCSS → CSS} |
postcss-loader | runs 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 trongmodule.rules}. - Loader chains in
usearrays run right-to-left {Chuỗi loader trong mảngusechạy phải-sang-trái}. babel-loaderhandles TS/JSX; alwaysexclude: /node_modules/{babel-loaderlo TS/JSX; luônexclude: /node_modules/}.- CSS chains as
["style-loader", "css-loader"](+sass-loaderfor Sass) {CSS xâu chuỗi["style-loader", "css-loader"](+sass-loadercho 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}.