Tree Shaking 不会强行保留代码,而是因静态分析无法确认无用而保守保留;关键在导入方式、副作用声明和ESM结构是否规范,验证需查构建产物中未引用函数名是否残留。

Tree Shaking 不会“强行保留”代码——它只是不敢删。真正原因是你写的代码让打包工具无法静态确认“这玩意儿真没用”。排查重点不是运行时行为,而是构建前的导入方式、副作用声明和模块结构是否清晰。
看构建产物里有没有残留函数名
这是最直接、最不可替代的验证方式:
- 执行构建(
npm run build),打开生成的dist/assets/*.js文件(或main.js) - 搜索你确定没 import 过的函数名,比如
subtract、debounce - 如果搜到了,且它没被压缩成单字母变量(如
function s(){...}),说明 Tree Shaking 没生效 - 再查 source map(如有),定位到原始源码行,确认它确实是未引用的命名导出
检查是不是对象属性访问在捣鬼
这是最常见的“假按需”陷阱:你以为只用了某个方法,其实整个对象都被拖进来了。
-
默认导出 + 点调用:
import utils from './utils'; utils.methodA();→ 整个utils对象保留 -
命名空间导入但没访问:
import * as u from './utils'; console.log('ok');→ 工具可能保守保留全部 -
解构了但没全用:
import { methodA, methodB } from './utils'; methodA();→ 旧版工具或复杂上下文里methodB可能逃逸
修复方式统一:改用具名导出 + 具名导入,确保 import 语句和调用完全对应。
确认副作用有没有被误判
Webpack 或 Rollup 一旦认为一个模块“有副作用”,就会放弃摇掉它的任何导出,哪怕你一个都没用。
- 检查
package.json中的"sideEffects"字段:设为false要非常谨慎;推荐用数组精确声明,例如["*.css", "index.js"] - 翻一遍你怀疑的模块:有没有顶层
console.log、localStorage.setItem、Array.prototype.xxx =、import './reset.css'这类无导出但有执行效果的代码 - 临时把该模块的
sideEffects设为false测试构建——如果报错或功能异常,说明它真有副作用,必须保留在白名单中
用可视化工具逆向追踪谁在“拉住”它
光看代码容易漏,靠工具看清依赖链:
- 装
webpack-bundle-analyzer或rollup-plugin-visualizer,构建后打开分析页面 - 找到体积异常大的模块(比如你的
utils.js),点开查看它有哪些导出项 - 观察哪些导出被标记为
unused export却仍出现在 bundle 中 → 这就是被“卡住”的线索 - 顺着它的依赖线往上点,看是哪个模块 import 了它,又是否间接触发了副作用或动态引用
不复杂但容易忽略的是:Tree Shaking 的判断依据只有三样——静态 import/export、明确的副作用声明、纯净的 ESM 结构。其余所有“看起来没用”的代码,问题一定出在这三者之一。











