1.核心前提 : ES6 Module
Tree Shaking 生效的根本前提是代码必须使用 ES6 模块语法(import / export),而不是 CommonJS(require / module.exports)
CommonJS 是动态的: 模块的导入和导出可以在代码运行时发生变化(例如放在 if 语句中)。构建工具在编译时无法确定到底引用了什么。
ES6 Module 是静态的: import 和 export 必须在顶层,不能在条件语句中。这意味着构建工具可以通过静态分析(即不运行代码,只分析代码结构)就能确定模块之间的依赖关系。
2.工作流程(三步走)
第一步:静态分析 (AST 解析)
构建工具(如 Webpack)在构建过程中,会把源码解析成 AST(Abstract Syntax Tree)。通过分析 AST,它能清楚地知道:
- 哪个文件导出了(export)哪些变量/函数。
- 哪个文件导入了(import)哪些变量/函数。
第二步:标记 (Marking)
构建工具会从入口文件(Entry)开始遍历,标记所有被使用的模块导出。
- 如果一个模块导出了函数 A 和函数 B,但在其他文件中只
import { A },那么函数 A 被标记为“使用中”。 - 函数 B 虽然被导出了,但没有被引用,因此不会被标记。
第三步:消除 (Elimination)
这一步通常由代码压缩工具(Minifier,如 Terser 或 UglifyJS)完成。 当压缩工具处理代码时,它会识别出那些被标记为“未引用”的代码(Dead Code),直接将其从最终的 Bundle 中剔除。
3.代码示例
//utils.js
export function add(a, b) {
return a + b;
}
export function minus(a, b) { // 这个函数没有被用到
return a - b;
}
//main.js
import { add } from './utils';
console.log(add(1, 2));
Tree Shaking 后的结果: minus 函数会被完全丢弃,最终打包结果里只有 add 函数和 console.log
4.最大障碍 : 副作用(Side Effects)
这是 Tree Shaking 中最容易踩坑的地方。
什么是副作用? 副作用是指一个模块除了导出成员之外,还做了其他事情,例如:
- 修改全局变量(如
window.polyfill = ...)。 - 在原型链上添加方法(如
Array.prototype.includes = ...)。 - 引入 CSS 文件(
import './style.css')。 - 执行
console.log。
为什么副作用会阻碍 Tree Shaking? 如果构建工具发现一个文件有“副作用”,即使你没有在代码里显式引用它的导出内容,构建工具也不敢轻易删除它,因为它担心删除后会破坏程序的逻辑。
5.总结
依赖 ESM: 利用 ES6 模块的静态特性。
构建依赖图: 遍历代码,分析 import/export。
可达性分析: 判断哪些导出的代码被使用了,哪些是“死代码”。
压缩删除: 结合 Terser 等压缩工具,将未使用的代码物理移除。
副作用处理: 通过 sideEffects 标记,手动告知工具哪些文件可以安全跳过副作用检查。