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.
You import two functions from a utility file that exports twenty {Bạn import hai hàm từ một file util xuất hai mươi}. Tree shaking is how Webpack ensures the other eighteen never reach your bundle {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}. But it only works under specific conditions, and getting them wrong silently ships dead code {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}.
Toggle the conditions below and watch which exports survive {Bật/tắt các điều kiện bên dưới và xem export nào sống sót}:
1. What tree shaking is {Tree shaking là gì}
Tree shaking is dead-code elimination for ES module exports {Tree shaking là loại bỏ code chết cho export của ES module}. Webpack analyzes which exports are actually imported across the graph, marks the unused ones, and the minimizer deletes them {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}. The “tree” is your dependency graph; you “shake” the dead leaves off {“Cây” là đồ thị phụ thuộc; bạn “rung” lá chết rụng}.
2. It requires ES modules {Nó cần ES module}
This is the non-negotiable condition {Đây là điều kiện không thương lượng}. Tree shaking relies on the static structure of import/export — Webpack can see at build time exactly what’s used {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() is a function call that can be conditional or computed, so Webpack can’t safely tell what’s unused {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}. Switch the demo to CommonJS and watch every export survive {Chuyển demo sang CommonJS và xem mọi export sống sót}. Critically, don’t let Babel transpile your ESM to CommonJS — set @babel/preset-env to modules: false {Quan trọng, đừng để Babel transpile ESM của bạn thành CommonJS — đặt @babel/preset-env thành modules: false}.
3. It requires production mode {Nó cần production mode}
In development, Webpack marks unused exports but doesn’t remove them (so builds stay fast and debuggable) {Ở development, Webpack đánh dấu export không dùng nhưng không xóa (để build nhanh và gỡ lỗi được)}. The actual deletion happens during minification, which only runs in production {Việc xóa thực sự xảy ra khi minify, chỉ chạy ở production}:
module.exports = {
mode: "production", // enables usedExports + minimize
};
Switch the demo to development mode to see the dead code stay in the bundle {Chuyển demo sang development mode để thấy code chết ở lại bundle}.
4. sideEffects — dropping whole modules {sideEffects — bỏ cả module}
Some files do work just by being imported — a CSS import, a polyfill, a global registration {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 can’t drop those even if their exports are unused, because removing them would change behavior {Webpack không thể bỏ chúng dù export không dùng, vì xóa sẽ đổi hành vi}. You tell Webpack which files are pure via package.json {Bạn báo Webpack file nào thuần qua package.json}:
{
"sideEffects": false
}
false means “every module here is side-effect-free, drop any whose exports are unused” {false nghĩa là “mọi module ở đây không có side-effect, bỏ cái nào export không dùng”}. If some files do have side effects, list them {Nếu vài file có side-effect, liệt kê chúng}:
{ "sideEffects": ["*.css", "./src/polyfills.js"] }
This is the single most impactful flag for shrinking bundles when consuming libraries {Đây là cờ tác động lớn nhất để thu nhỏ bundle khi dùng thư viện}.
5. Minification with Terser {Minify với Terser}
In production, Webpack runs TerserPlugin to minify JS — shortening names, removing whitespace, dropping dead branches {Ở production, Webpack chạy TerserPlugin để minify JS — rút tên, bỏ khoảng trắng, bỏ nhánh chết}. It’s on by default; you only configure it to customize {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
],
}
Combined with DefinePlugin setting NODE_ENV to "production" (Part 4), Terser can eliminate entire if (process.env.NODE_ENV !== "production") dev-only blocks {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. Writing tree-shakeable code {Viết code tree-shake được}
- Use named ESM exports, not a giant default-exported object {Dùng named ESM export, không phải một object default khổng lồ}.
- Import only what you need —
import { debounce } from "lodash-es", notimport _ from "lodash"{Chỉ import cái cần —import { debounce } from "lodash-es", không phảiimport _ from "lodash"}. - Prefer libraries that ship ESM and declare
sideEffects(e.g.lodash-esoverlodash) {Ưu tiên thư viện ship ESM và khai báosideEffects(vdlodash-eshơnlodash)}. - Avoid top-level side effects in modules meant to be tree-shaken {Tránh side-effect ở cấp cao nhất trong module muốn được tree-shake}.
7. Exercises {Bài tập}
1. You import one function from your utils file but the whole file ends up in the bundle. You’re using require(). Why does that break tree shaking? {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?}
Solution {Lời giải}
CommonJS is dynamic, not statically analyzable — Webpack can’t prove what’s unused. Use ESM import {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 works in your prod build but your dev build still contains unused exports. Is that a bug? {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?}
Solution {Lời giải}
No — dead code is only removed in production mode by the minimizer; dev only marks it {Không — code chết chỉ bị xóa ở production bởi minimizer; dev chỉ đánh dấu}.
3. A library’s unused helpers won’t shake out even with ESM + production. What should the library’s package.json declare? {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ì?}
Solution {Lời giải}
"sideEffects": false (or a list of the files that do have side effects) so Webpack can safely drop pure modules {"sideEffects": false (hoặc danh sách file có side-effect) để Webpack an toàn bỏ module thuần}.
Stretch {Nâng cao}: in the visualizer, set ESM + production + sideEffects, note the bundle size, then flip to CommonJS and watch every unused export return {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}.
Key takeaways {Điểm chính}
- Tree shaking = dead-code elimination for unused ESM exports {Tree shaking = loại bỏ code chết cho export ESM không dùng}.
- It requires ES modules —
require()can’t be shaken {Nó cần ES module —require()không rung được}. - It only removes code in
productionmode {Nó chỉ xóa code ởproduction}. "sideEffects": falselets Webpack drop whole pure modules {"sideEffects": falsecho Webpack bỏ cả module thuần}.- TerserPlugin minifies and removes dead branches in production {TerserPlugin minify và bỏ nhánh chết ở production}.
Next up {Tiếp theo}
Part 8 — Caching & long-term caching: [contenthash], deterministic moduleIds, splitting the runtime, and the file-naming strategy that lets browsers cache your bundles for a year {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}.