Webpack · Part 7 — Tree Shaking & Production Mode
How Webpack drops unused exports: why ES modules are required, the role of sideEffects and usedExports, minification with Terser, and what mode: production turns on. With an interactive tree-shaking visualizer.
Bạn import hai hàm từ một file util xuất hai mươi. Tree shaking là cách Webpack đảm bảo mười tám hàm còn lại không bao giờ tới bundle. Nhưng nó chỉ chạy dưới điều kiện cụ thể, và làm sai sẽ âm thầm ship code chết.
Bật/tắt các điều kiện bên dưới và xem export nào sống sót:
1. Tree shaking là gì
Tree shaking là loại bỏ code chết cho export của ES module. Webpack phân tích export nào thực sự được import xuyên đồ thị, đánh dấu cái không dùng, và minimizer xóa chúng. “Cây” là đồ thị phụ thuộc; bạn “rung” lá chết rụng.
2. Nó cần ES module
Đây là điều kiện không thương lượng. Tree shaking dựa vào cấu trúc tĩnh của import/export — Webpack thấy lúc build chính xác cái gì được dùng:
// ✓ ESM — statically analyzable
import { add } from "./utils";
// ✗ CommonJS — dynamic, can't be shaken
const { add } = require("./utils");
require() là lời gọi hàm có thể có điều kiện hoặc tính toán, nên Webpack không thể an toàn biết cái gì không dùng. Chuyển demo sang CommonJS và xem mọi export sống sót. Quan trọng, đừng để Babel transpile ESM của bạn thành CommonJS — đặt @babel/preset-env thành modules: false.
3. Nó cần production mode
Ở development, Webpack đánh dấu export không dùng nhưng không xóa (để build nhanh và gỡ lỗi được). Việc xóa thực sự xảy ra khi minify, chỉ chạy ở production:
module.exports = {
mode: "production", // enables usedExports + minimize
};
Chuyển demo sang development mode để thấy code chết ở lại bundle.
4. sideEffects — bỏ cả module
Một số file làm việc chỉ bằng việc được import — một CSS import, một polyfill, một đăng ký global. Webpack không thể bỏ chúng dù export không dùng, vì xóa sẽ đổi hành vi. Bạn báo Webpack file nào thuần qua package.json:
{
"sideEffects": false
}
false nghĩa là “mọi module ở đây không có side-effect, bỏ cái nào export không dùng”. Nếu vài file có side-effect, liệt kê chúng:
{ "sideEffects": ["*.css", "./src/polyfills.js"] }
Đây là cờ tác động lớn nhất để thu nhỏ bundle khi dùng thư viện.
5. Minify với Terser
Ở production, Webpack chạy TerserPlugin để minify JS — rút tên, bỏ khoảng trắng, bỏ nhánh chết. Nó bật mặc định; bạn chỉ cấu hình để tùy chỉnh:
const TerserPlugin = require("terser-webpack-plugin");
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({ terserOptions: { compress: { drop_console: true } } }),
"...", // keep the defaults (e.g. CSS minimizer) too
],
}
Kết hợp với DefinePlugin đặt NODE_ENV thành "production" (Phần 4), Terser có thể loại bỏ cả khối if (process.env.NODE_ENV !== "production") chỉ-dành-cho-dev.
6. Viết code tree-shake được
- Dùng named ESM export, không phải một object default khổng lồ.
- Chỉ import cái cần —
import { debounce } from "lodash-es", không phảiimport _ from "lodash". - Ưu tiên thư viện ship ESM và khai báo
sideEffects(vdlodash-eshơnlodash). - Tránh side-effect ở cấp cao nhất trong module muốn được tree-shake.
7. Bài tập
1. Bạn import một hàm từ file utils nhưng cả file vào bundle. Bạn dùng require(). Vì sao nó phá tree shaking?
Lời giải
CommonJS động, không phân tích tĩnh được — Webpack không chứng minh được cái gì không dùng. Dùng ESM import.
2. Tree shaking chạy ở bản prod nhưng bản dev vẫn chứa export không dùng. Có phải lỗi không?
Lời giải
Không — code chết chỉ bị xóa ở production bởi minimizer; dev chỉ đánh dấu.
3. Helper không dùng của thư viện không rung ra dù ESM + production. package.json của thư viện nên khai báo gì?
Lời giải
"sideEffects": false (hoặc danh sách file có side-effect) để Webpack an toàn bỏ module thuần.
Nâng cao:trong trình trực quan, đặt ESM + production + sideEffects, để ý kích thước bundle, rồi đổi sang CommonJS và xem mọi export không dùng quay lại.
Điểm chính
- Tree shaking = loại bỏ code chết cho export ESM không dùng.
- Nó cần ES module —
require()không rung được. - Nó chỉ xóa code ở
production. "sideEffects": falsecho Webpack bỏ cả module thuần.- TerserPlugin minify và bỏ nhánh chết ở production.
Tiếp theo
Phần 8 — Caching & caching dài hạn: [contenthash], moduleIds tất định, tách runtime, và chiến lược đặt tên file cho phép trình duyệt cache bundle cả năm.