tree shaking 是构建工具在编译阶段通过静态分析 es 模块依赖图、标记未引用导出、再由压缩器(如 terser)物理剔除的死代码消除流程,依赖 import/export 的顶层静态性,不适用于 commonjs 或动态导入。

Tree Shaking 不是运行时行为,也没有独立的“源码”可直接阅读;它本质是构建工具在编译阶段执行的一套静态分析 + 死代码消除(DCE)流程。要看它的底层实现,关键不是找某个叫 tree-shaking.js 的文件,而是理解打包器(如 Webpack、Rollup、esbuild)如何利用 ES 模块特性完成三件事:解析 AST、构建依赖图、标记并剔除未引用导出。
核心依赖:ES Module 的静态性必须成立
Tree Shaking 能工作的前提,是模块系统支持编译时确定依赖。这意味着:
- import/export 必须写在顶层作用域,不能包裹在 if、函数或 try/catch 中
-
导入路径必须是字符串字面量,不能是变量拼接(
import(`./${name}.js`)是动态导入,无法被分析) - CommonJS(require/module.exports)不参与 Tree Shaking,因为其导出是运行时赋值的对象属性,静态分析无法推断哪些属性真被用了
实际发生在哪里:以 Webpack 为例的关键环节
Webpack 并没有一个单独的 “Tree Shaking 模块”,它的能力分散在几个核心步骤中:
-
ModuleGraph 构建阶段:从入口开始递归解析
import,记录每个模块的export和谁import了它 -
HarmonyExportDependency 分析:识别 ES 模块导出,标记哪些导出被其他模块的命名导入(
import { foo })显式引用 - FlagDependencyUsagePlugin:遍历依赖图,从入口出发,反向标记所有“活跃”的导出(live exports)
- TerserPlugin(压缩阶段):真正删掉未被标记的函数、变量声明——这步才是物理移除,前面只是打标
怎么验证你写的代码真被摇掉了?
别只信配置,动手看结果最可靠:
- 用
webpack --mode=production --stats=verbose构建,查compilation.modules输出,看未使用函数是否还在模块列表里 - 开启
optimization.providedExports: true和usedExports: true,让 Webpack 显式标注哪些导出被用/未用 - 检查最终 bundle 源码(未压缩前),搜索你确认没调用的函数名,如果还存在,说明 Tree Shaking 失效了
- 用 webpack-bundle-analyzer 可视化依赖,确认某模块是否整包引入
常见失效点,也是你看源码时该盯住的地方
很多“摇不掉”不是工具问题,是你代码触发了静态分析盲区:
-
副作用(sideEffects)未声明:CSS 导入、polyfill、全局样式注入等有运行时影响的模块,Webpack 默认不敢删——必须在
package.json中明确写"sideEffects": ["*.css", "*.scss"]或false -
聚合导出(export * from)或对象挂载导出:
export const utils = { a(), b() }会让整个对象被保留,哪怕只调用utils.a -
导出被间接引用:比如通过
export { foo as bar }再被另一个模块import { bar },中间层若没做正确 re-export 声明,可能断链











