摇树 Tree‑shaking:前端打包优化技术

介绍

Tree‑shaking 是死代码消除(Dead‑code Elimination)的前端打包优化技术,比喻:摇动依赖树,抖落没用的枯叶(未使用代码),只保留真正用到的代码,减小 bundle 体积,提升页面加载速度。

概念最早由 Rollup 普及,Webpack、Vite、esbuild、Terser 都支持该能力。

原理

依赖 ES Module(ESM)静态模块特性

  1. ESM 的 import / export 只能写在顶层,编译构建阶段就可以确定依赖关系,不是运行时才解析。
  2. 打包工具构建模块依赖图,标记哪些导出被引用、哪些完全没被使用。
  3. Webpack/Rollup 只做标记;真正删除代码靠压缩器 Terser;生产模式才会完整执行整套流程。

❗ CommonJS(require / module.exports)做不到 Tree‑shaking:require 是运行时动态执行,变量可以控制导入,构建阶段无法静态确定哪些代码不用,不能安全删除代码。

简单示例

math.js 文件代码如下:

export const add = (a,b) => a+b
export const mul = (a,b) => a*b

main.js 文件代码如下:

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

这里 mul 完全没有被导入使用,Tree‑shaking 会把 mul 从最终产物删掉。

生效必备条件

  1. 源码使用 ESM import / export,不能被转成 CommonJS。
    • Babel:@babel/preset‑env 务必设置 modules: false,禁止把 ESM 转 require;一旦转成 CommonJS,摇树直接失效。
  2. 开启生产构建模式
    • Webpack:mode: production,自动开启 Scope Hoisting(模块合并)+ Terser 压缩;开发模式不会删除死代码。
  3. 正确处理模块副作用 sideEffects,否则工具不敢删代码。

Vue3 中的 Tree‑shaking

Vue3 + Vite 默认 Tree‑shaking 情况:

  • 生产构建(vite build):默认开启树摇

  • 开发服务(vite dev):不做树摇。

提示:Vite生产底层是 Rollup,Rollup 的 treeshake 默认值就是 true,不需要你写任何额外配置。

开发环境

  • 使用 esbuild 做即时转译,不会做删除死代码的 tree‑shaking
  • 目的:速度优先,方便调试,保留全部源码
  • ✅ 现象:你写的未使用代码,浏览器网络面板里依然能看到,这不是树摇失效,环境使然

    ⚠️ 不要在dev环境判断树摇有没有生效,必须看build后的dist产物

生产打包

  1. Vite 调用 Rollup 进行打包,treeshake: true 默认打开
  2. 压缩默认 minify: 'esbuild',esbuild 会做死代码移除,支持 /*#__PURE__*/
  3. 只要你是标准 ESM import/export,就会执行树摇。

    ✔️ 不需要手动配置 treeshake:true,默认就是。

// vite.config 不需要写这段,本来就是默认行为
build: {
  rollupOptions: {
    treeshake: true // 可省略,默认true
  }
}

但是!默认开启 ≠ 一定生效

Vite只是打开了树摇开关,遇到下面情况,树摇照样失效:

第三方库是 CommonJS 格式

Rollup对CommonJS只能做启发式有限分析,树摇效果很差,比如原始lodash。

聚合对象导出破坏静态分析

// 无法树摇
export default { CompA, CompB }

// ✅ 支持树摇
export const CompA = ...
export const CompB = ...

组件全局注册

app.component('CompA', CompA) // 组件一定会打进包,无法被摇掉

全部导入整个库

import ElementPlus from 'element-plus' // 全量引入,树摇帮不了你