Tree Shaking 失效主因是代码阻断静态分析:对象属性访问(如import utils from './utils'; utils.methodA())、命名空间导入未访问、CommonJS混用(require/module.exports)、顶层副作用及动态import均会致整模块保留。

Tree Shaking 失效不是配置没开,而是代码或结构“拦住了”静态分析。它只认明确、静态、无副作用的 import/export 关系,任何模糊、动态或隐含影响的行为,都会让打包器放弃剔除。
哪些写法会直接阻断 Tree Shaking
这些情况会让工具无法确定某段代码是否真没被用,从而整块保留:
-
对象属性访问式导入:比如
import utils from './utils'; utils.methodA();—— 即使只调用了methodA,整个utils对象(含未用的methodB)仍被保留 -
命名空间导入但没访问属性:如
import * as utils from './utils'; console.log('init');—— 没出现utils.xxx,部分构建器会保守保留全部导出 -
CommonJS 混用:模块里写了
require('lodash')或module.exports = {...},Webpack/Rollup 就不再分析该模块的导出使用情况 -
顶层副作用代码:如
console.log('loaded')、window.MyLib = {}、localStorage.setItem('a', '1'),会被视为“有副作用”,整文件不摇 -
动态 import 或字符串拼接路径:如
import(`./pages/${page}.js`),静态分析无法推断加载目标,相关模块常被全量保留
怎么确认是不是真失效了
别猜,看产物:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
- 构建后打开
dist/assets/index.xxxx.js,搜索你确定没引用的函数名(如subtract)。如果存在且未压缩混淆,说明没被摇掉 - 在入口加一行
import { unusedHelper } from './utils';,重新构建。体积没变 → 已生效;体积明显增大 → 这个导出原本就残留着 - 用
webpack-bundle-analyzer或 Vite 的build.report查看模块明细,确认utils.js中哪些导出进了最终 chunk - 开启
optimization.usedExports: true,再看 source map,若某导出旁标有/* unused export subtract */,说明已识别但未删——可能是副作用或压缩阶段未触发
修复的关键动作
核心是让导入与使用一一对应,消除间接性和不确定性:
- 把
export default { a, b }改成具名导出:export function a() {}、export function b() {} - 严格用
import { a } from './utils',禁用import utils from和import * as utils - 第三方库换 ESM 友好版:用
lodash-es替代lodash,用date-fns/esm路径 - 在
package.json中声明"sideEffects": false(纯工具库),或精确列出有副作用的文件,如"sideEffects": ["*.css", "src/init.js"] - Babel 配置中确保
modules: false,防止把import编译成require
检查副作用和模块格式是否合规
很多失效藏在“看起来没问题”的细节里:
- 查
node_modules里依赖的package.json,看它有没有"type": "module"或"exports"字段,没有就大概率是 CommonJS - 打开某个工具模块源码,确认没有顶层赋值、原型修改、
eval、import 'xxx.css'这类副作用语句 - 运行构建时加
--stats=verbose,搜索usedExports: false的模块,重点排查它们为何被判定为“不可安全移除”

















