什么是 Tree Shaking?如何确保它生效?

Tree Shaking

摇树优化:打包时移除未使用的代码(死代码),减小包体积。

// math.js
export function add(a, b) { return a + b }
export function subtract(a, b) { return a - b }
export function multiply(a, b) { return a * b }

// app.js
import { add } from './math.js'
console.log(add(1, 2))

// 打包后:subtract 和 multiply 被移除

工作原理

  1. 依赖 ESM 的静态结构(import/export 在编译时确定)
  2. 打包工具分析模块依赖关系
  3. 标记未被导入的 export 为"未使用"
  4. 压缩阶段移除未使用的代码

为什么 CommonJS 不能 Tree Shake

// CommonJS — 动态加载,无法静态分析
const math = require('./math')
const fn = process.env.TEST ? math.add : math.subtract  // 运行时决定

// ESM — 静态结构,编译时确定
import { add } from './math'  // 明确只用了 add

确保 Tree Shaking 生效

1. 使用 ESM

// ✅ ES Module
import { debounce } from 'lodash-es'

// ❌ CommonJS(不触发 Tree Shaking)
import { debounce } from 'lodash'

2. package.json 配置 sideEffects

{
  "name": "my-lib",
  "sideEffects": false  // 所有文件都没有副作用,可安全 Tree Shake
}

或指定有副作用的文件:

{
  "sideEffects": ["*.css", "*.scss", "./src/polyfills.js"]
}

什么是副作用?

// 有副作用:修改了全局状态
window.myGlobal = 'hello'
Array.prototype.myMethod = function() {}

// 无副作用:纯导入导出
export function add(a, b) { return a + b }

3. 生产模式

// Webpack
mode: 'production'  // 自动开启 Tree Shaking

// Vite/Rollup
// 生产构建自动开启

4. 避免整包导入

// ❌ 导入整个 lodash
import _ from 'lodash'
_.debounce(fn, 300)

// ✅ 按需导入
import debounce from 'lodash/debounce'
// 或
import { debounce } from 'lodash-es'  // 配合 Tree Shaking

验证 Tree Shaking

# Webpack
npx webpack --analyze

# Vite
vite build --mode production
# 查看包大小变化

常见问题

1. 函数有副作用导致无法摇除

// ❌ 有副作用(访问了全局变量)
export function init() {
  console.log('init')  // 副作用
}

// ✅ 纯函数
export function add(a, b) {
  return a + b
}

2. 类的方法不会被摇除

export class MyClass {
  unusedMethod() {}  // 不会被摇除(可能有副作用)
}

3. 动态导入无法静态分析

const lib = await import(dynamicPath)  // ❌ 无法 Tree Shake

Webpack vs Vite Tree Shaking

两者都支持 Tree Shaking,Vite(Rollup)在 Tree Shaking 上通常更激进。

同分类其他题目