tree shaking 依赖 es6 模块的静态特性实现:通过编译时静态分析 import/export 构建依赖图谱,标记并剔除未被引用的导出代码;commonjs 因动态特性无法支持,且副作用需通过 sideeffects 字段显式声明。

模块执行上下文本身不直接“支持”Tree Shaking,真正起作用的是ES模块(ESM)的静态结构特性,而模块执行上下文只是这一特性的运行时体现。Tree Shaking 依赖的不是代码如何执行,而是代码在编译/构建阶段能否被静态分析——这由模块语法决定,而非执行环境。
为什么 ESM 的模块上下文是 Tree Shaking 的前提
ES 模块的导入导出必须出现在顶层作用域,且导入路径和导出标识符必须是静态字符串。这种强制性让构建工具(如 Webpack、Rollup、Vite)能在不运行代码的前提下,准确构建完整的依赖图谱:
- import 语句明确声明了“需要什么”,而非“可能加载什么”
- export 语句明确声明了“提供什么”,且每个导出可被独立追踪
- 模块之间没有动态条件分支影响导出可见性(例如不能在 if 中 export)
CommonJS 上下文为何无法支持 Tree Shaking
CommonJS 使用 require() 和 module.exports,其行为本质是动态的:
- require 可以写在函数、条件语句甚至 try/catch 中,构建工具无法在编译时确定是否执行
- module.exports 赋值方式灵活(如 exports.foo = ... 或 module.exports = { foo }),导出内容可能被运行时逻辑覆盖或拼接
- 没有静态导出标识符,无法安全标记“某个函数未被引用”
模块执行上下文中的副作用会干扰 Tree Shaking
即使使用 ESM,若模块在顶层执行有副作用的操作(如修改全局变量、发起请求、操作 DOM、调用 console),构建工具可能保守地保留整个模块,避免误删关键逻辑。这时需通过 package.json 的 sideEffects 字段显式声明:
-
"sideEffects": false表示所有模块都无副作用,可安全摇树 -
"sideEffects": ["*.css", "init.js"]表示只有列出的文件有副作用,其余可放心优化
现代构建工具如何利用模块上下文做标记与剪枝
以 Webpack 为例,它在构建阶段会:
- 解析 import 语句,建立从入口出发的静态依赖链
- 对每个 ESM 模块的 export 进行“使用标记”:仅当某 export 被其他模块的 import 明确引用,才标记为“已使用”
- 在压缩阶段移除未标记的函数、类、常量等顶层声明(注意:不会删除整个模块文件,除非该模块没有任何被引用的 export)











