tree shaking 是构建阶段的静态依赖分析,非运行时检测;它基于 es 模块的静态 import/export 构建依赖图,标记并剔除未被任何 import 引用的导出,前提包括使用 esm、顶层静态导入、正确声明 sideeffects 及选用 esm 兼容的第三方库。

Tree Shaking 不是运行时检测,而是构建阶段的静态依赖分析。它不“遍历运行路径”,而是从入口文件出发,顺着 ES 模块的 import/export 语句 构建一张确定的依赖图,再标记哪些导出(函数、常量、类等)从未被任何 import 引用——未被标记的,就是可安全剔除的冗余内容。
依赖树分析的前提:必须是静态可判定的 ESM
只有满足以下条件,构建工具才能可靠构建和分析依赖树:
- 所有模块使用
export/import(不能是require或module.exports) - 导入语句写在顶层,且模块路径是字符串字面量(如
import { foo } from './utils'✅,不是import(`./pages/${name}.js`)❌) - 没有破坏静态结构的语法(如条件导入、动态表达式、
export default { ... }聚合对象)
为什么有些模块或函数没被消除?常见卡点
即使写了 export,也未必能被摇掉。以下情况会阻断分析或导致保留:
-
副作用模块未声明:某文件只执行
axios.defaults.baseURL = '/api'却无export,若未在package.json中设"sideEffects": false或精确列出该文件,它可能被误删,或反过来让整个模块被保守保留 -
导入方式不当:用了
import * as utils from './utils'或import utils from './utils'(默认导入),工具无法判断具体用了哪些成员,倾向保留全部导出 -
第三方库不提供 ESM 入口:比如直接
import _ from 'lodash',lodash 是 CommonJS;应改用import { debounce } from 'lodash-es'并确认其package.json有"type": "module"或"exports"中定义import字段 -
导出被间接引用:例如
export { a as b },又在别处import { b } from—— 这仍算被引用;但若仅export const b = a却未被 import,则会被剔除
如何验证某个函数是否真被剔除?实操步骤
别猜,直接看产物:
- 运行构建命令:
vite build或webpack --mode production - 打开
dist/下生成的 JS 文件(通常是.js或.mjs) - 用编辑器全局搜索那个函数名(如
subtract、unreachableHelper) - 结果为空 → 成功剔除;若存在 → 检查是否被
* as导入、是否在副作用列表中、是否被动态import()触发、或是否属于未启用usedExports的旧版 Webpack 配置
辅助定位:开启构建详细日志
部分工具支持输出摇树过程信息:
- Webpack:加
--stats=verbose,搜索unused exports或模块名,看是否标记为not used - Vite:启用
build.rollupOptions.treeshake: { moduleSideEffects: false, propertyReadSideEffects: false }并配合插件如rollup-plugin-visualizer查看依赖图 - Rollup:默认高亮未使用导出,终端会提示
xxx is not exported by xxx或xxx is unused
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











