核心结论:的Tree-基于ES模块静态结构,通过构建阶段分析模块依赖图,精准剔除未使用代码,生成更小、更优的打包结果
与等打包器相比,从设计之初就围绕ES模块(ESM)的静态特性构建Tree-能力,能够实现更彻底的未引用代码消除,是构建库、组件和现代前端项目的首选工具。
1. Tree-是什么
Tree-(摇树优化)是一种通过静态分析,从最终打包结果中移除未被实际使用(导入但未调用)的代码的优化技术。它依赖于ES模块的静态结构——和必须在顶层,且模块依赖关系在编译时即可确定,无法动态修改,这使得打包器可以安全地分析并删除死代码(dead code)。
2. Tree-的工作原理
的Tree-流程分为三个阶段:
2.1 构建模块依赖图
从入口文件开始,递归解析所有语句,构建出完整的模块依赖树。每个模块都会被标记为“已使用”或“未使用”。
2.2 静态分析导出使用情况
会分析每个模块中导出的变量、函数、类等是否被其他模块实际导入并调用。只有那些被直接或间接引用的导出才会被保留,未被引用的导出会连同其定义一起被移除。
例如,模块A导出两个函数:
// utils.js
foo() { .log('foo'); }
bar() { .log('bar'); }
入口文件只导入foo:
// main.js
{ foo } from './utils.js';
foo();
打包结果中只会包含foo函数的实现,bar函数被完全移除。
2.3 作用域跟踪与副作用分析
会深入分析函数调用链和变量使用情况。如果一个导出函数内部调用了其他模块的函数,但该导出本身未被使用,那么整个调用链都会被移除。此外,会通过副作用检测判断一段代码是否可能产生外部影响(如修改全局变量、调用.log等),保留有副作用的代码,避免因过度优化导致运行时错误。
3. 在项目中启用 Tree-
3.1 基础配置
创建..js,启用es格式输出(或iife、umd,但es格式对Tree-最友好):
{
input: 'src/main.js',
: {
file: 'dist/.js',
: 'es' // 或 'esm'
},
// 无需额外配置,默认开启Tree-
};
默认情况下,在生产构建(不设置watch或未显式关闭)时自动进行Tree-。
3.2 控制副作用
无法识别某些模块内的副作用,可能导致误删或误保留。通过.json中的字段可以明确告知模块是否有副作用。
"": false:声明模块中的所有文件均无副作用,可以安全删除任何未直接导入的代码。
"": ["<>.css", "</>.scss"]:指定某些文件(如样式文件)存在副作用,不可删除。
{
"name": "my-lib",
"": false
}
对于第三方库,如果其.json未声明,会采用保守策略,保留所有未明确标记为无副作用的代码。
3.3 在代码中辅助Tree-
使用ES模块语法:始终使用/,避免和动态导入(除非必要)。
避免模块级别的立即执行代码:例如,在模块顶层直接调用函数、修改全局对象等,这些会被视为副作用而保留。
通过/<>#</>/注释标记纯函数:告诉该函数调用没有副作用,可以安全删除。常用于库开发者。
const = /#/ n();
4. 常见问题与解决方案
4.1 为什么某些未使用的代码仍被保留?
可能原因:
副作用未被正确标记:检查.json的配置,确保无副作用模块已声明false。
使用了模块:对的Tree-能力有限。通过@/-转换后仍可能残留,建议将依赖也转换为ES模块。
动态()的模块:动态导入的模块会作为独立chunk,其内部代码可能因副作用而被保留。
4.2 vs Tree-差异
| 特性 | ||
|---|---|---|
| 依赖分析粒度 | 函数/变量级别 | 模块级别(需借助插件精细化) |
| 副作用处理 | 内置基于和代码分析 |
需配置. |
| 输出格式 | 优先输出ES模块,保留原结构 | 默认输出包裹的模块,Tree-需开启 |
| 适用场景 | 库、组件、框架打包 | 应用级打包,配置更复杂 |
结论:对于追求极致体积的库或组件,是更优选择。
4.3 验证Tree-效果
查看打包输出文件,搜索未被引用的变量或函数是否消失。
使用--生成依赖关系图,直观查看保留的模块大小。
5. 权威参考与进一步阅读
通过以上配置和原理理解,你可以充分借助的Tree-机制,在保证功能完整的前提下,获得最小的打包体积。这一机制不仅适用于库开发,也适用于现代前端应用中独立模块的精细打包场景。

