Hãy hình dung dự án của bạn import một thư viện tiện ích có 200 hàm, nhưng thực tế chỉ dùng 3 hàm. Nếu không xử lý, cả 200 hàm vẫn nằm trong file JavaScript gửi xuống trình duyệt của người dùng. “Rung cây cho lá khô rụng” là cách ví von cho kỹ thuật làm sạch phần dư thừa đó: giữ lại nhánh code còn sống, để code chết tự rơi ra khỏi bundle.
Vì sao việc này quan trọng với tốc độ web
Mỗi kilobyte JavaScript thừa đều khiến trình duyệt phải tải thêm, phân tích cú pháp (parse) thêm và thực thi thêm trên luồng chính (main thread — luồng xử lý chính của trình duyệt). Theo web.dev, đa số website gửi đi nhiều JavaScript hơn mức thực sự cần, và đây là một trong những nguyên nhân lớn nhất làm chậm trang.
Hệ quả chạm thẳng vào Core Web Vitals — bộ ba chỉ số trải nghiệm trang của Google (đo ở phân vị 75 — p75 dữ liệu người dùng thật). Ngưỡng “Good” hiện hành: LCP (Largest Contentful Paint — thời điểm phần tử lớn nhất hiện ra) ≤ 2,5 giây, INP (Interaction to Next Paint — độ trễ phản hồi tương tác) ≤ 200 mili-giây, CLS (Cumulative Layout Shift — độ dịch chuyển bố cục) ≤ 0,1. JavaScript dư thừa làm luồng chính bận rộn, kéo lùi cả LCP lẫn INP. Cắt bớt code chết là một đòn bẩy trực tiếp.

Tree shaking hoạt động thế nào
Chìa khoá nằm ở ES module (chuẩn module ES2015 với cú pháp import/export). Khác với module kiểu cũ (CommonJS, dùng require), ES module có cấu trúc tĩnh: bundler có thể đọc và biết chắc đoạn nào được import, đoạn nào không, ngay tại thời điểm build mà không cần chạy code.
Từ phân tích tĩnh đó, bundler đánh dấu các export không ai dùng, rồi để bước nén (minify) thực sự xoá chúng đi. Ví dụ:
// utils.js
export function dungToi(a, b) { return a + b; }
export function khongDung(x) { return x * 9999; } // không ai import
// main.js
import { dungToi } from './utils.js';
console.log(dungToi(2, 3));
Sau khi build, hàm khongDung không xuất hiện trong bundle cuối vì không có nơi nào tham chiếu tới nó.
Bundler nào hỗ trợ tree shaking
| Bundler | Tree shaking | Ghi chú |
|---|---|---|
| Rollup | Có (vốn nổi tiếng nhờ tính năng này) | Khái niệm tree shaking được phổ biến qua Rollup |
| Webpack | Có (từ Webpack 2, mở rộng ở 4–5) | Cần mode: "production" để minify xoá thật |
| esbuild | Có, mặc định | Tự loại export không dùng, rất nhanh |
| Vite | Có (build production dựa trên Rollup) | Đang chuyển dần sang Rolldown viết bằng Rust |
Với Webpack, một điểm hay quên: tree shaking chỉ thực sự cắt code khi chạy ở chế độ production, vì lúc đó plugin nén (Terser) mới được bật để xoá phần đã đánh dấu.
Điều kiện để tree shaking thật sự hiệu quả
- Dùng ES module. Code và thư viện phải xuất ở định dạng ESM (
import/export). Thư viện chỉ phát hành bản CommonJS thường khó rung sạch. - Khai báo side-effect (tác dụng phụ). “Side effect” là đoạn code khi được import sẽ làm gì đó ngoài việc cung cấp export — ví dụ polyfill thay đổi phạm vi toàn cục (global scope), hoặc CSS import. Bundler phải thận trọng không xoá nhầm chúng.
- Import có chọn lọc. Ưu tiên
import { mot } from 'thu-vien'thay vì gom cả thư viện rồi chỉ dùng một phần.
Để báo cho bundler biết package không có tác dụng phụ, khai báo trong package.json:
{
"name": "thu-vien-cua-ban",
"sideEffects": false
}
Nếu một số file CÓ tác dụng phụ (ví dụ file CSS hoặc polyfill), liệt kê chúng bằng glob để các file còn lại được coi là an toàn để rung:
{
"sideEffects": ["*.css", "./src/polyfill.js"]
}
Lưu ý quan trọng từ tài liệu Webpack: chỉ đặt "sideEffects": false khi bạn chắc chắn code không có tác dụng phụ. Khai báo sai có thể khiến bundler xoá nhầm đoạn cần thiết và làm vỡ ứng dụng.
Tree shaking khác gì code-splitting và minify
Ba kỹ thuật này hay bị gộp làm một, nhưng giải quyết ba việc khác nhau:
- Tree shaking — loại bỏ code không dùng tới (dead code) khỏi bundle.
- Code-splitting (chia tách bundle) — không xoá code, mà chia nhỏ bundle để chỉ tải phần cần cho màn hình hiện tại, phần còn lại tải khi cần.
- Minify (rút gọn) — giữ nguyên logic, chỉ nén code: xoá khoảng trắng, rút tên biến, bỏ comment.
Trên thực tế chúng bổ trợ nhau: tree shaking vứt bỏ phần thừa, code-splitting sắp xếp phần cần tải khi nào, minify ép phần còn lại nhỏ nhất có thể. Một vấn đề thường đi kèm là CSS thừa — xem thêm cách loại bỏ CSS không dùng để làm sạch nốt phần stylesheet.

Cách kiểm tra code thừa trong bundle
Tab Coverage trong Chrome DevTools cho biết bao nhiêu phần trăm JavaScript đã tải mà không hề chạy — đây là dấu hiệu rõ ràng cho cơ hội cắt giảm. Ngoài ra, các công cụ trực quan hoá bundle (như bundle analyzer của Webpack/Rollup) giúp bạn nhìn ra module nào đang phình to file cuối.
Từ chính việc vận hành web22.dev, đội Web22 nhận thấy phần lớn dung lượng JS dư đến từ việc import nguyên cả thư viện chỉ để dùng một, hai hàm. Sửa ở khâu import thường nhẹ nhàng và an toàn hơn nhiều so với chỉnh sâu vào cấu hình bundler.
Câu hỏi thường gặp
Tree shaking có tự bật sẵn không?
Với esbuild thì gần như mặc định. Với Webpack, bạn cần chạy ở chế độ production để bước minify xoá thật phần đã đánh dấu. Vite/Rollup bật sẵn khi build production.
Vì sao đã bật mà bundle vẫn không nhỏ đi?
Thường do thư viện chỉ có bản CommonJS, hoặc khai báo sideEffects chưa đúng khiến bundler không dám xoá. Hãy kiểm tra thư viện có bản ESM và rà lại import có chọn lọc chưa.
Tree shaking có làm vỡ trang không?
Có rủi ro nếu một module thực ra có tác dụng phụ nhưng bị đánh dấu nhầm là an toàn. Vì vậy chỉ khai báo "sideEffects": false khi bạn chắc chắn.
Nếu bundle JavaScript của bạn đang nặng và kéo lùi điểm tốc độ, Web22 nhận tối ưu hiệu năng web từ khâu build đến đo lường thực tế.
