Tree Shaking
Tree Shaking 消除未使用代码,减少打包体积。
#type / concept
#status / growing
#tech / dev / frontend
[!info] related notes
- 所属 MOC: 前端性能优化 MOC
- 前置概念: code-splitting
- 并列概念: resource-compression
Tree Shaking
一句话定义
Tree Shaking 是通过 ES Module 的静态分析,在构建阶段标记并消除未被引用的代码(dead code),从而减小最终 bundle 体积的优化技术。
核心机制 / 工作原理
为什么只对 ESM 有效
ES Module 的 import/export 是静态声明,在编译时就能确定模块间的依赖关系:
// ESM — 静态,构建时可知
import { add } from './math'
// CommonJS — 动态,运行时才知道
const math = require('./math')
const fn = math[getRandomName()] // 无法静态分析
标记-清除流程
- 解析阶段:构建工具从入口文件开始,解析所有
import/export语句,构建模块依赖图 - 标记阶段:从入口开始,递归标记所有被实际使用的导出
- 清除阶段:未被标记的导出在后续压缩(minify)阶段被移除
math.js:
export function add(a, b) { return a + b } ← 被 import,保留
export function subtract(a, b) { return a - b } ← 未被 import,shaking 掉
sideEffects 配置
package.json 中的 sideEffects 字段告诉构建工具哪些模块可以安全地进行 tree shaking:
{
"sideEffects": false // 声明整个包无副作用
}
{
"sideEffects": ["*.css", "./src/polyfill.js"] // 有副作用的文件列表
没有 sideEffects 标记时,构建工具不敢删除任何代码,因为可能有副作用(如全局赋值、polyfill)。
各构建工具的实现方式
| 工具 | 实现方式 | 特点 |
|---|---|---|
| Webpack | 生产模式默认启用(Terser 配合) | 需要 mode: 'production',结合 Terser 删除未使用代码 |
| Rollup | 原生支持,核心特性 | ESM-first 设计,tree shaking 是其架构核心 |
| Vite | 开发模式用 ESM 原生加载,生产用 Rollup | 开发模式无打包,生产模式继承 Rollup 的 tree shaking |
| esbuild | 极快的 tree shaking | 速度优势明显,适合大型项目 |
最小例子
// utils.js
export function add(a, b) { return a + b }
export function subtract(a, b) { return a - b }
// main.js
import { add } from './utils'
console.log(add(1, 2))
// subtract 会被 tree shaking 移除
构建后的 bundle 中只包含 add 函数,subtract 被消除。
常见无法 shake 的场景
| 场景 | 原因 | 解决方案 |
|---|---|---|
| CommonJS 模块 | require() 是动态的 | 使用 ESM 版本或 lodash-es |
| 副作用代码 | import 'polyfill' 无法判断是否可删 | 正确配置 sideEffects |
| 动态属性访问 | math[fnName]() 无法静态分析 | 改为直接导入 |
| 全局副作用 | 模块内部修改全局状态 | 避免全局副作用或标记为有副作用 |
| 被引用但未使用 | import 了但赋值给未使用的变量 | 使用 _ 前缀或 eslint 检测 |
// 无法 tree shaking 的写法
import _ from 'lodash' // 整个 lodash 被打包
_.get(obj, 'a.b')
// 可以 tree shaking 的写法
import get from 'lodash-es/get' // 只打包 get 函数
get(obj, 'a.b')
边界与常见误解
- 误解:Tree Shaking 能移除任何未使用的代码。 只能移除未使用的 ESM 导出。有副作用的代码(如
console.log、全局赋值)不会被移除。 - 误解:Tree Shaking 在开发模式下也生效。 Webpack 开发模式默认不启用完整的 tree shaking(为调试保留代码)。
- 误解:用了 ESM 就自动 tree shaking。 还需要构建工具正确配置,且模块本身无副作用。
- 边界:Tree Shaking 的效果取决于代码结构。 barrel 文件(
export * from './a')可能阻碍 tree shaking。
实际效果衡量
- 使用 webpack-bundle-analyzer 或 rollup-plugin-visualizer 可视化 bundle 组成
- 对比开启/关闭 tree shaking 前后的 bundle 大小
- 关注首屏加载的 JS 体积变化
- 典型优化效果:引入大型 UI 库时可减少 30%-70% 体积