Skip to content

Tree Shaking 冷知识总结

Tree Shaking 核心原理

ESM 静态分析的基础

javascript
// ✅ 可以被静态分析
import { add, multiply } from './math.js';  // 只导入需要的函数
import utils from './utils.js';             // 默认导入

// ❌ 无法被静态分析
const mathModule = require('./math.js');    // CommonJS 动态特性
const { add } = mathModule;                 // 运行时才知道用了什么

动态导入破坏 Tree Shaking

javascript
// ❌ 动态导入让打包器无法预知
async function loadMath() {
  const { add } = await import('./math.js');  // 运行时决定
  return add;
}

// ❌ 条件导入也会破坏
if (process.env.NODE_ENV === 'development') {
  const { debugUtils } = await import('./debug.js');
}

// ✅ 静态导入可以被分析
import { add } from './math.js';

破坏 Tree Shaking 的常见陷阱

1. console.log 的副作用

javascript
// math.js
export function add(a, b) {
  console.log('add function called');  // 副作用!
  return a + b;
}

export function multiply(a, b) {
  console.log('multiply function called');  // 副作用!
  return a * b;
}

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

// 结果:multiply 函数不会被移除!
// 因为它包含 console.log 副作用

解决方案

javascript
// 使用 /*#__PURE__*/ 注释
export function multiply(a, b) {
  /*#__PURE__*/ console.log('multiply function called');
  return a * b;
}

// 或者在 package.json 中标记
{
  "sideEffects": false  // 告诉打包器这个包没有副作用
}

2. * 导入的问题

javascript
// ❌ 导入整个模块
import * as utils from './utils.js';
utils.add(1, 2);  // 即使只用了 add,整个 utils 都会被打包

// ✅ 按需导入
import { add } from './utils.js';
add(1, 2);  // 只打包 add 函数

3. 类和原型链的陷阱

javascript
// utils.js
export class Calculator {
  add(a, b) { return a + b; }
  multiply(a, b) { return a * b; }
  divide(a, b) { return a / b; }
}

// main.js
import { Calculator } from './utils.js';
const calc = new Calculator();
calc.add(1, 2);  // 整个 Calculator 类都会被打包,包括未使用的方法

高级 Tree Shaking 技巧

1. 使用 sideEffects 配置

json
// package.json
{
  "sideEffects": [
    "*.css",           // CSS 文件有副作用
    "./src/polyfills.js",  // polyfill 有副作用
    "./src/global-setup.js"
  ]
}

2. 函数式编程友好

javascript
// ✅ Tree Shaking 友好
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => a / b;

// ❌ 不够友好
export default {
  add: (a, b) => a + b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b,
};

3. 条件导出的处理

javascript
// ❌ 运行时条件
export const utils = process.env.NODE_ENV === 'development' 
  ? { debug: true, log: console.log }
  : { debug: false, log: () => {} };

// ✅ 构建时条件(通过打包器处理)
export const utils = __DEV__ 
  ? { debug: true, log: console.log }
  : { debug: false, log: () => {} };

实际案例分析

1. Lodash 的 Tree Shaking

javascript
// ❌ 导入整个 lodash(~70KB)
import _ from 'lodash';
_.debounce(fn, 300);

// ✅ 按需导入(~2KB)
import debounce from 'lodash/debounce';
debounce(fn, 300);

// ✅ 使用 lodash-es(ESM 版本)
import { debounce } from 'lodash-es';
debounce(fn, 300);

2. 第三方库的陷阱

javascript
// 某些库的内部实现可能破坏 Tree Shaking
// moment.js 就是典型例子
import moment from 'moment';  // 整个库都会被打包

// 更好的选择
import { format } from 'date-fns';  // 只打包需要的函数

调试 Tree Shaking

1. Webpack Bundle Analyzer

bash
npm install --save-dev webpack-bundle-analyzer
javascript
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

2. 查看打包结果

bash
# Webpack
npx webpack --mode=production --optimization-used-exports

# Rollup
npx rollup -c --silent

# Vite
npx vite build --mode=production

最佳实践总结

  1. 优先使用 ESMimport/export 而不是 require
  2. 按需导入:避免 import * 和默认导出对象
  3. 标记副作用:正确配置 sideEffects
  4. 避免运行时逻辑:在导入语句中避免条件判断
  5. 使用 Pure 注释:对有副作用但安全的代码使用 /*#__PURE__*/
  6. 选择合适的库:优先选择支持 Tree Shaking 的库版本

常见误区

误区1:以为所有 ESM 都支持 Tree Shaking

javascript
// 即使是 ESM,如果有副作用也不会被 shake 掉
export const config = {
  apiUrl: process.env.API_URL || 'default'  // 运行时逻辑
};

误区2:认为 default export 不支持 Tree Shaking

javascript
// ✅ default export 也可以被 shake
export default function add(a, b) {
  return a + b;
}

// 问题在于导出对象
export default {
  add: (a, b) => a + b,
  multiply: (a, b) => a * b  // 即使不用也会被打包
};

误区3:忽略第三方库的 Tree Shaking 支持

javascript
// 检查库是否支持 Tree Shaking
// 1. 查看 package.json 的 sideEffects 字段
// 2. 查看是否提供 ESM 版本
// 3. 使用 bundle analyzer 验证效果

总结

Tree Shaking 是现代前端优化的重要技术,理解这些细节能帮你写出更高效的代码。关键是要理解静态分析的限制,避免副作用,选择合适的导入导出方式。

Released under the MIT License.