tree shaking 的核心原理是静态依赖分析+死代码标记+压缩阶段剔除:基于es6模块的静态import/export语法构建依赖图,通过ast识别未被引用的导出并标记,最终由terser等压缩器在production模式下剔除;不支持commonjs,需配置sideeffects以保障副作用安全。

Tree Shaking 的核心工作原理,本质是“静态依赖分析 + 死代码标记 + 压缩阶段剔除”。它不运行代码,只靠读代码结构就判断哪些导出没被用上。
依赖图从入口开始静态构建
打包工具(如 Webpack、Rollup、Vite)以 入口文件为根节点,逐个解析所有 import 语句,递归收集模块,形成一张完整的依赖关系图。这个过程发生在构建早期,完全不执行任何 JS 逻辑。
- 每个
import被当作明确的、编译期可确定的引用 - 每个
export被记录为一个潜在可到达的出口 - CommonJS 的
require()不参与该图——因为它可能出现在 if 判断里、循环中,甚至拼字符串,无法静态判定是否加载
ES 模块的静态性是前提
ES6 模块要求 import 和 export 必须写在顶层、不能动态变化。这使得工具能直接扫描源码,准确回答:“谁导入了 utils.js 里的 subtract?” 答案是:没人。于是 subtract 被打上“未使用”标签。
- 不是靠运行时调用栈追踪,而是靠语法树(AST)识别标识符引用关系
- 哪怕函数内部调用了另一个函数,只要那个函数没被任何
import引入,它依然会被摇掉 - 类、变量、默认导出、命名导出,全部按同样规则处理
真正删除发生在压缩阶段
Tree Shaking 本身不直接删代码,它只是在 AST 上标记哪些导出未被引用;最终移除动作由压缩器(如 Terser)完成。只有在 production 模式下,压缩器才会读取这些标记并剔除对应代码块。
- 开发模式通常跳过压缩,所以你看不到体积变化
- 即使标记成功,若模块有副作用(比如改写了
Array.prototype),还需靠"sideEffects": false或白名单告知工具“这里不能动” - 摇不掉的常见原因:混用
require/module.exports、未设mode: "production"、遗漏sideEffects配置
Rollup 为什么“摇得更干净”
Rollup 默认将模块“扁平化”到同一作用域,能精确到函数粒度删除;Webpack 则保留模块封装结构,更多时候只能整文件排除。例如一个工具文件导出 5 个函数,Rollup 可能只留下 1 个,Webpack 可能因某处 require('./utils') 存在而保留整个文件。
- 这不是能力差距,而是设计取舍:Rollup 专注库打包,追求极致精简;Webpack 侧重应用,兼顾兼容与调试体验
- Vite 底层用 Rollup 构建生产包,因此也继承了高精度摇树能力
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











