Tree Shaking 失效主因是动态属性访问(如 obj[key])导致打包工具无法静态分析,应改用静态键显式导入(如 import { fn } from 'x')并避免将函数挂载于导出对象中。

Tree Shaking 失效常因 JavaScript 动态属性访问(如 obj[prop]、obj[variable])触发,导致打包工具(如 Webpack、Rollup、Vite)无法静态分析出哪些属性被真正使用,从而不敢安全地移除未引用的导出。排查关键在于识别“不可推断的访问模式”,并将其转化为可静态分析的形式。
识别动态访问的典型写法
以下代码会让 Tree Shaking 退化:
-
import { a, b, c } from './utils.js'; const key = 'b'; console.log(obj[key]);—— 即使只用了b,打包器可能保留a和c的导出逻辑(尤其当obj是具名导出对象时) -
const method = 'fetchData'; api[method]();—— 若api是一个具名导出的对象(如export const api = { fetchData() {}, saveData() {} }),则整个api对象可能被保留 -
module.exports[config.name] = ...或export[flag ? 'A' : 'B'] = ...—— 条件分支或运行时变量直接用于属性名,完全阻断静态分析
用静态键 + 显式引用替代动态访问
将运行时决定的 key 转为编译期确定的字面量,并显式调用目标属性:
- ❌ 错误:
const key = 'formatDate'; utils[key](date); - ✅ 正确:
import { formatDate } from './utils.js'; formatDate(date); - 若需条件逻辑,用
if/else或switch显式分发:if (type === 'date') formatDate(x); else if (type === 'number') formatNumber(x);—— 每个分支都对应明确导入,Tree Shaking 可分别处理
避免把具名导出对象当作“配置字典”使用
常见反模式:把所有工具函数挂在一个导出对象上,再通过字符串访问 —— 这等于主动放弃模块粒度的摇树能力:
立即学习“Java免费学习笔记(深入)”;
- ❌ 不推荐:
export const tools = { validate(), debounce(), throttle() };然后在别处写tools['debounce']() - ✅ 推荐:
export function validate() { ... }; export function debounce() { ... }; export function throttle() { ... };并按需导入:import { debounce } from './tools.js'; - 若必须统一管理,可用
Object.freeze({})+ TypeScript 类型约束辅助,但核心仍是“导入具体名字”,而非访问对象属性
借助工具验证是否生效
检查最终打包产物中未使用代码是否真的被剔除:
- Webpack:开启
stats: 'verbose'或使用webpack-bundle-analyzer,搜索未使用的导出名是否出现在 chunk 中 - Rollup/Vite:启用
build.rollupOptions.output.manualChunks或查看.vite/deps和dist输出,配合rollup-plugin-visualizer - 关键验证点:搜索你的模块路径(如
utils.js),确认只有被实际import的符号出现在生成代码里,且无整块export { ... }被完整保留
不复杂但容易忽略:只要属性访问的 key 是字符串字面量、且导入路径和符号名在源码中显式可见,现代打包工具基本都能正确摇树。动态性越强,静态分析能力越弱 —— 把“运行时决策”尽量上移到“构建时决策”或“源码显式分支”,是保持 Tree Shaking 健壮的核心原则。


















