tree shaking 不处理未导出的死代码,仅移除已导出但未被引用的代码;未导出代码是否保留取决于内部引用关系,最终由 terser 等压缩器在 minimize 阶段基于无副作用假设进行清除。

Tree Shaking 本身不处理“未导出的死代码”,它只作用于 已导出但未被引用的代码。换句话说,如果一个函数或变量根本没写 export,Webpack 或其他打包工具压根不会把它纳入分析范围——它连模块接口都不是,自然不在 Tree Shaking 的管辖之内。
为什么未导出的代码不在 Tree Shaking 范围内
Tree Shaking 的本质是基于 ES 模块静态结构的依赖图分析:从入口开始,顺着 import 向下追踪哪些 export 被实际使用。未导出的变量、函数、常量属于模块内部实现细节,构建工具默认认为它们“不可见、不可触达”,也就不会标记、不会保留、更不会刻意删除——它们通常在后续压缩阶段由 Terser 自动清理(前提是没副作用且未被闭包捕获)。
- 未导出的代码是否被打包,取决于它是否被模块内其他导出内容间接引用
- 若一个私有函数只被某个导出函数调用,而该导出函数又被外部引用,那这个私有函数会随调用链一起保留
- 若一个私有函数完全没人调用,且没有闭包引用、无全局副作用,Terser 在压缩时大概率会直接删掉它
真正能删掉未导出死代码的环节是压缩器
Webpack 的 Tree Shaking 阶段只负责“摇掉未被 import 的 export”。真正剔除模块内部未使用的私有变量、未调用的局部函数、冗余表达式等,靠的是 Terser(Webpack 默认)或 esbuild(Vite/Rollup 常用)这类 JavaScript 压缩器。
- 启用
minimize: true(生产模式默认开启)才能触发 Terser - Terser 会做函数内联、常量折叠、无用语句移除、未使用变量声明清除等深度优化
- 例如:
const unused = 123; console.log('hello');中的unused声明,在压缩后会被彻底抹去
如何让未导出的死代码更容易被压缩器识别并删除
关键在于保持代码“干净”和“无副作用”,避免干扰压缩器的静态判断:
- 避免给局部变量赋值后不再读取(Terser 可能保留,尤其涉及对象属性或函数调用时)
- 不要在模块顶层写无意义的表达式,比如
{} + [];或空的for循环 - 确保没有意外的闭包捕获:一个未导出的函数若被某个导出函数返回,即使没显式调用,也可能因潜在可执行性被保留
- 禁用 Babel 的
modules: 'commonjs'转换——它会把 ES 模块转成 require,破坏整个静态分析基础
对比:export vs 未 export 的处理路径
假设模块中有如下代码:
// utils.js const helper = () => 'internal'; // 未导出,私有 export const used = () => helper() + '!'; // 被外部引用 export const unusedExport = () => 'never used'; // 导出但未被 import
-
helper:未导出 → 不参与 Tree Shaking 标记 → 若仅被used调用且used被保留,则helper也会保留在最终 bundle 中;若 Terser 能确认它无副作用且调用可内联,可能被合并或消除 -
unusedExport:已导出但未被任何import引用 → WebpackusedExports: true会标记它为未使用 → Terser 在压缩阶段将其整段删除
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











