Tree Shaking
Loại bỏ code JavaScript không sử dụng trong quá trình build (ES6 modules), giảm kích thước bundle.
Tree Shaking là gì?
Tree Shaking là kỹ thuật loại bỏ những đoạn mã JavaScript không được sử dụng (dead code) trong quá trình xây dựng ứng dụng (build time), chủ yếu dựa trên mô hình ES6 modules (import/export). Tên gọi 'rũ cây' (tree shaking) xuất phát từ hình ảnh: như việc rung một cái cây để làm rụng những chiếc lá khô — ở đây là những hàm, biến, lớp không được gọi đến trong suốt vòng đời ứng dụng.
Khác với các công cụ tối ưu hóa chạy thời gian thực (runtime), Tree Shaking hoạt động tĩnh — nghĩa là nó phân tích mã nguồn trước khi biên dịch, không dựa vào hành vi người dùng hay điều kiện thực thi. Kết quả là bundle cuối cùng nhỏ hơn, tải nhanh hơn và tiêu tốn ít tài nguyên CPU hơn khi chạy.
Tại sao quan trọng trong SEO?
Tốc độ tải trang là một trong những yếu tố xếp hạng trực tiếp của Google, đặc biệt trên thiết bị di động. Tree Shaking góp phần cải thiện ba chỉ số Core Web Vitals quan trọng:
- Largest Contentful Paint (LCP): Giảm kích thước JavaScript giúp trình duyệt render nội dung chính sớm hơn.
- Total Blocking Time (TBT): Ít mã hơn → ít thời gian parse/compile → giảm thời gian chặn tương tác.
- Cumulative Layout Shift (CLS): Khi bundle nhẹ, khả năng load đồng bộ tài nguyên (CSS, font, script) ổn định hơn → hạn chế dịch chuyển bố cục bất ngờ.
Ngoài ra, bundle nhỏ hơn còn giảm băng thông tiêu thụ, tăng tỷ lệ hoàn tất tải (especially trên mạng 3G/4G), từ đó cải thiện trải nghiệm người dùng — yếu tố gián tiếp ảnh hưởng đến tỷ lệ thoát và thời gian ở lại trang.
Cách hoạt động
Tree Shaking dựa vào hai đặc điểm cốt lõi của ES6 modules:
- Tính tĩnh của import/export: Các câu lệnh
importvàexportphải nằm ở đầu file và không được thay đổi runtime — trình biên dịch có thể xác định rõ ràng mối quan hệ phụ thuộc giữa các module. - Tính bất biến của binding: Các tên export (ví dụ:
export const utils = {...}) không thể bị ghi đè hoặc thay đổi giá trị sau khi export — giúp công cụ xác định chắc chắn đâu là code 'có thể đạt tới' (reachable).
Khi chạy build (ví dụ với Webpack hoặc Rollup), trình biên dịch xây dựng đồ thị phụ thuộc (dependency graph), đánh dấu tất cả các export đã được import ở bất kỳ đâu trong ứng dụng. Những export không xuất hiện trong đồ thị sẽ bị loại bỏ — miễn là chúng không bị 'đánh dấu là cần thiết' bởi các side effect.
Hướng dẫn thực hiện
Tree Shaking không tự động bật — cần cấu hình đúng và tuân thủ chuẩn. Dưới đây là các bước bắt buộc:
- Sử dụng ES6 modules: Chỉ
import/export, không dùngrequirehoặcmodule.exports. Nếu dùng thư viện bên ngoài, kiểm tra xem phiên bản hỗ trợ ESM (thường có trường"module"trongpackage.json). - Tắt chế độ CommonJS fallback: Trong Webpack, đặt
mode: 'production'và đảm bảooptimization.usedExports: true. - Khởi tạo
sideEffectstrongpackage.json: Khai báo"sideEffects": falsenếu toàn bộ mã không gây hiệu ứng phụ (ví dụ: không thay đổi DOM, không gọi API, không chỉnh style toàn cục). Nếu có file CSS hoặc file khởi tạo global, liệt kê cụ thể:["*.css", "./src/init.js"]. - Tránh các mẫu phá vỡ Tree Shaking: Không gán biến toàn cục (
window.myLib = ...), không dùngeval(), không import toàn bộ namespace (import * as X from 'x'nếu không dùng hết).
Lỗi thường gặp
Dưới đây là những vấn đề phổ biến khiến Tree Shaking thất bại — kèm cách khắc phục:
| Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
| Thư viện không bị cắt dù không dùng hàm nào | Thư viện xuất bản dưới dạng UMD/CommonJS, hoặc thiếu trường "module" trong package.json |
Dùng phiên bản ESM (ví dụ: lodash-es thay vì lodash), hoặc cấu hình resolve.alias để trỏ sang bản ESM. |
| Hàm vẫn tồn tại trong bundle dù không import | Có side effect tiềm ẩn (ví dụ: import CSS, khởi tạo logger, đăng ký custom element) | Thêm khai báo "sideEffects": ["*.css", "*.scss"] trong package.json của dự án. |
Webpack cảnh báo 'export ... was not found in ...' |
Import sai tên export (tên sai, viết hoa sai, hoặc dùng default import cho named export) | Kiểm tra lại tài liệu thư viện; dùng import { functionName } from 'lib' thay vì import lib from 'lib' nếu không có default export. |
Ví dụ thực tế
Giả sử bạn dùng thư viện lodash-es:
import { debounce, throttle } from 'lodash-es';
const debouncedSearch = debounce(searchAPI, 300);
Sau khi build với Webpack 5 (ở chế độ production), chỉ debounce được giữ lại. Hàm throttle — dù đã import — sẽ bị loại nếu không được gọi. Kích thước bundle giảm khoảng 8–12 KB so với việc import toàn bộ lodash (khoảng 70 KB).
Một ví dụ khác: Dự án React dùng @mui/material. Nếu viết import Button from '@mui/material/Button' thay vì import { Button } from '@mui/material', Tree Shaking vẫn hoạt động — nhưng hiệu quả thấp hơn do mỗi component MUI đều import nhiều phụ thuộc con. Cách tốt nhất là dùng cấu hình Babel plugin @babel/plugin-transform-modules-commonjs kết hợp với import { Button } from '@mui/material' để tối ưu sâu hơn.
Câu hỏi thường gặp
Tree Shaking có hoạt động với TypeScript không?
Có — miễn là TypeScript được cấu hình xuất ra ES6 modules (cài "module": "ESNext" trong tsconfig.json) và trình build (Webpack/Rollup/Vite) xử lý đúng output. Tuy nhiên, nếu dùng "module": "CommonJS", Tree Shaking sẽ không kích hoạt.
Có cần dùng thêm plugin để Tree Shaking mạnh hơn?
Với Webpack 4+, không cần plugin riêng — tính năng đã tích hợp sẵn. Với các công cụ cũ hơn hoặc muốn tối ưu sâu hơn (ví dụ: loại bỏ hàm helper thừa), có thể dùng webpack-dead-code-plugin — nhưng hiệu quả thường không đáng kể và có thể gây lỗi. Với Vite hoặc esbuild, Tree Shaking được bật mặc định và tối ưu cao hơn Webpack.
Tree Shaking có áp dụng được với code server-side (Node.js)?
Không — Tree Shaking là kỹ thuật dành riêng cho môi trường build frontend. Trên Node.js, các module được tải runtime qua require hoặc import(), nên không có giai đoạn phân tích tĩnh toàn bộ ứng dụng. Một số công cụ như esbuild --tree-shaking có thể áp dụng cho bundle Node.js, nhưng không phổ biến và hiệu quả hạn chế — tùy trường hợp.