【高频】关键词速查:Q06-Tree-shaking底层原理

3 阅读3分钟

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 优化机制

✅ 答题框架(建议顺序)

  1. 先说原理本质:Tree-shaking 依赖 ESM 的静态结构——import/export 在编译时即可确定,构建工具通过静态分析标记"被引用的导出",未标记的即视为 Dead Code 并在压缩阶段移除
  2. 再说为什么摇不掉:四大根因——① CommonJS 的动态 require 无法静态分析 ② 包的 sideEffects 未配置或配置错误,Webpack 不敢摇 ③ 模块顶层有隐式副作用(如 Array.prototype.xxx = ...)④ 未使用的导出被赋值给变量,变量间的间接引用链导致无法判定可摇(innerGraph 解决此问题)
  3. 最后说 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 的标记阶段配合完成优化

优先级:🔴高频