tree shaking 是构建阶段的静态分析技术,依赖 es module 的顶层、静态 import/export 语法建立可追踪的依赖图,webpack/rollup 等工具据此标记并移除未使用导出;commonjs 因 require 动态性无法支持。

Tree Shaking 并不由 V8 引擎执行,它发生在构建阶段(如 Webpack、Rollup、ESBuild 编译时),而非运行时。V8 负责执行已加载的 JavaScript 代码,不参与模块依赖分析或死代码剔除。所谓“静态模块结构”指的是 ES Module(import/export)语法在编译期就确定的、不可变的导入导出关系——这为构建工具提供了静态可分析的基础,而不是 V8 的优化逻辑。
为什么 ES Module 是 Tree Shaking 的前提
ES Module 的导入导出必须写在顶层、不能条件化或动态拼接,例如:
-
import { foo } from 'lib'→ 构建工具能立即知道只用到了foo -
export const a = 1; export const b = 2;→ 导出项明确、可枚举、无运行时分支 -
if (Math.random() > 0.5) import('./mod')→ 这是动态import(),属于运行时行为,无法被 Tree Shaking 分析
构建工具如何利用静态结构做扫描
以 Webpack 或 ESBuild 为例,它们在解析阶段会:
- 用解析器(如 Acorn 或内置 parser)将每个模块转成 AST,提取所有
import和export声明 - 基于这些声明构建模块依赖图(Dependency Graph),明确谁导出了什么、谁引用了什么
- 从入口模块出发,沿着
import链向上追踪“被实际使用”的导出标识符(如func1),未出现在任何import中的导出项即标记为“未使用” - 结合
sideEffects: false或显式副作用声明,判断能否安全移除整个模块文件
CommonJS 为什么不行
因为 require() 允许运行时计算路径、条件加载、赋值给变量等,例如:
-
const mod = require(condition ? 'a' : 'b')→ 构建时无法确定加载哪个模块 -
module.exports = { [key]: value }→ 导出结构动态生成,无法静态枚举 - 即使你只用了
lodash.get,但通过const _ = require('lodash')引入,打包工具无法推断具体用到哪些方法
真正影响 Tree Shaking 效果的关键点
- 项目和依赖库都必须使用 ES Module 语法(包括第三方库的
module字段或 ESM 包) - 构建工具需启用相关配置:Webpack 中
optimization.usedExports: true+TerserPlugin;ESBuild 默认开启 - 避免无意引入副作用:比如在模块顶层执行
localStorage.setItem或修改全局原型,这类代码即使没被import,也不能被删 - 注意动态
import():它本身不会破坏 Tree Shaking,但其内部模块无法被上游静态分析,属于独立 chunk










