Tree Shaking 的核心是静态分析导入导出关系,关键在消除模糊引用、避免副作用误判,并通过产物验证与精准改写确保生效。

掌握 Tree Shaking 的关键不是背规则,而是理解它“只看怎么引、不看怎么用”的静态分析本质。真正卡住摇树的,往往不是逻辑错误,而是导入导出方式模糊了引用关系。
对象属性访问:最隐蔽的残留源头
看似只调用了某个方法,却让整个模块被保留——这是高频陷阱。
-
默认导出 + 点号访问:如
import utils from './utils'; utils.formatDate();。即使只用formatDate,utils对象里所有属性(formatPhone、randomId)都会进包 -
命名空间导入但没访问属性:如
import * as utils from './utils'; console.log('init');。工具无法确认你是否后续会用utils[xxx],只能保守保留全部导出 -
解构导入未使用项:如
import { debounce, throttle } from './utils'; debounce(fn, 300);。现代构建工具通常能剔除throttle,但如果该模块在别处被非具名方式引入过,它仍可能整体存活
副作用误判:Webpack 不敢删,不是不想删
Webpack 一旦怀疑模块有副作用,就会跳过摇树,哪怕你只用了其中一个纯函数。
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
-
顶层副作用代码:如
console.log('loaded')、window.myLib = {}、localStorage.setItem('flag', '1'),这些语句会让整块模块被标记为“不可摇” -
无导出的导入:如
import './reset.css'或import 'core-js/stable',它们不提供任何变量,只靠执行产生效果,必须显式列入sideEffects白名单 -
package.json 中 sideEffects 字段配置错误:设为
false却实际有 CSS 注入或全局修改;或该字段缺失,导致 Webpack 默认认为所有文件都有副作用
验证是否真生效:别信感觉,要看产物
直觉容易误判,构建后文件才是唯一证据。
-
直接搜索函数名:打开
dist/assets/index.xxxx.js,全局搜subtract或unusedHelper。若存在且未被压缩混淆,说明未被摇掉 -
加测试导入对比体积:在入口加一行
import { unusedHelper } from './utils';,重新构建。体积没变 → 已生效;体积增大 → 此前该导出未被剔除 -
用可视化工具定位:运行
vite build --report或webpack-bundle-analyzer,查看utils.js中哪些导出进了最终 chunk,一目了然
修复动作要精准:改写法,不改逻辑
核心是让“导入语句”和“实际使用”完全对齐,消除间接引用。
-
把默认导出对象拆成具名导出:将
export default { methodA, methodB }改为export function methodA() {}和export function methodB() {} -
严格使用具名导入:只写
import { methodA } from './utils',禁用import utils from和import * as utils -
第三方库选 ESM 版本:用
lodash-es替代lodash,用date-fns替代moment;同时检查 Babel 配置,确保没启用@babel/plugin-transform-modules-commonjs -
声明副作用边界:若工具函数纯计算无 DOM 操作、无全局修改,就在
package.json中设"sideEffects": false;若有 CSS 或 polyfill,则精确列出路径,如"./src/polyfill.js"

















