Q06:Tree-shaking 的底层原理是什么?为什么有时候即使配置了,某些无用的代码还是摇不掉?
⏱ 预计阅读时间:4 分钟
原题:Tree-shaking 的底层原理是什么?为什么有时候即使配置了,某些无用的代码还是摇不掉?(提示:副作用 sideEffects、CommonJS vs ESM、
/*#__PURE__*/注释、Webpack 5 innerGraph 优化)
🎯 面试官在考什么
- 考点1:Tree-shaking 的前提条件——ESM 的静态分析特性
- 考点2:摇不掉的四大根因(sideEffects、CJS、隐式副作用、innerGraph)
- 考点3:Webpack 5 的 innerGraph 优化机制
✅ 答题框架(建议顺序)
- 先说原理本质:Tree-shaking 依赖 ESM 的静态结构——import/export 在编译时即可确定,构建工具通过静态分析标记"被引用的导出",未标记的即视为 Dead Code 并在压缩阶段移除
- 再说为什么摇不掉:四大根因——① CommonJS 的动态 require 无法静态分析 ② 包的 sideEffects 未配置或配置错误,Webpack 不敢摇 ③ 模块顶层有隐式副作用(如
Array.prototype.xxx = ...)④ 未使用的导出被赋值给变量,变量间的间接引用链导致无法判定可摇(innerGraph 解决此问题) - 最后说 innerGraph:Webpack 5 在生产模式默认开启
optimization.innerGraph,能追踪模块内变量间的依赖关系,对"导出 A 引用了工具函数 B,但 A 未被外部使用"的情况也能摇掉 B,这是比简单标记更深的分析
⚠️ 常见踩坑
- 认为 Tree-shaking 是"Webpack 的功能" → Tree-shaking 最早由 Rollup 实现,Webpack 从 2.0 引入。核心依赖 ESM 静态分析 + Terser 压缩移除 Dead Code,是两个步骤配合的结果
- 设了
sideEffects: false就万事大吉 → 如果包内确实有副作用模块(如全局 CSS、polyfill),设为 false 会导致它们被错误摇掉。正确做法是用数组排除:"sideEffects": ["*.css", "./src/polyfill.js"] - 忽略 Babel 对 ESM 的转换 → Babel 的
@babel/preset-env默认将 ESM 转为 CJS,直接破坏 Tree-shaking 前提。必须配置modules: false保留 ESM
💎 加分项
- 提到
/*#__PURE__*/注释:手动标记函数调用无副作用,如const result = /*#__PURE__*/createSomething(),即使 result 未使用,构建工具也敢安全移除 - 提到 Rolldown/Oxc 的 Tree-shaking 增强:Vite 8 统一引擎后,Oxc 的语义分析能力比 esbuild 更强,可以识别更多可摇除的代码模式
- 区分"标记阶段"和"移除阶段":Tree-shaking 分两步——构建时标记未使用的导出(harmony export 标记)+ 压缩时 Terser 移除 Dead Code。光标记不移除等于没摇
📚 关键知识点速查
- ESM 静态分析:import/export 语句在编译时即可确定模块依赖关系,无需执行代码,是 Tree-shaking 的前提
- sideEffects:package.json 中的字段,告知构建工具哪些文件有副作用。
false表示全部可安全摇,数组表示排除项 /*#__PURE__*/:内联注释,标记函数调用无副作用,允许构建工具在返回值未使用时安全移除整个调用- innerGraph:Webpack 5 的优化选项(生产模式默认开启),追踪模块内部变量间的依赖关系,实现更深层的 Dead Code 检测
- Dead Code Elimination:压缩阶段由 Terser/swc 执行的实际代码移除,与 Tree-shaking 的标记阶段配合完成优化
优先级:🔴高频