jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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 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 needimport { debounce } from "lodash-es", not import _ from "lodash" {Chỉ import cái cầnimport { debounce } from "lodash-es", không phải import _ from "lodash"}.
  • Prefer libraries that ship ESM and declare sideEffects (e.g. lodash-es over lodash) {Ưu tiên thư viện ship ESM và khai báo sideEffects (vd lodash-es hơn lodash)}.
  • 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 modulesrequire() can’t be shaken {Nó cần ES modulerequire() không rung được}.
  • It only removes code in production mode {Nó chỉ xóa code ở production}.
  • "sideEffects": false lets Webpack drop whole pure modules {"sideEffects": false cho 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}.