实现完美 Tree Shaking 需坚持静态分析原则:优先命名导出、避免 default 导出对象、消除模块副作用、禁用动态/条件导出,并提供扁平化子入口。

在类库开发中实现完美 Tree Shaking,核心是让打包工具(如 Webpack、Vite)能静态分析出哪些导出未被使用,并安全地剔除。这要求导出语句必须满足“可静态分析”原则:不能动态计算导出名、不能包裹在条件或运行时逻辑中、避免 default 导出混用命名导出的歧义写法。
使用命名导出(Named Exports)代替默认导出
Tree Shaking 对 export default 支持较弱,尤其当 default 是一个对象或函数调用结果时,工具难以判断其内部属性是否被引用。应优先使用具名导出,每个功能独立导出:
// ✅ 推荐:每个工具函数单独命名导出
export const debounce = (fn, delay) => { /* ... */ };
export const throttle = (fn, limit) => { /* ... */ };
export const deepClone = (obj) => { /* ... */ };
<p>// ❌ 避免:将所有功能塞进一个 default 对象
export default {
debounce,
throttle,
deepClone
}; // 此写法会导致整个对象无法被 shake(即使只用了 debounce)避免副作用导致的导出不可摇动
如果导出语句所在模块执行了副作用(如修改全局变量、发起请求、操作 DOM),打包工具会保守保留整个模块,即使只导入其中某个函数。确保导出文件本身无副作用:
- 导出文件只做声明,不执行初始化逻辑
- 将副作用逻辑延迟到函数调用时(即“懒执行”)
- 在
package.json中显式声明"sideEffects": false(若全模块无副作用)或列出有副作用的文件(如["*.css", "src/polyfill.js"])
禁止动态导出与运行时分支
以下写法会让 Tree Shaking 失效,因为导出结构无法在构建时确定:
// ❌ 错误:导出名由变量决定 → 无法静态分析
const name = 'filter';
export { [name]: filter };
<p>// ❌ 错误:条件导出 → 工具无法判定哪个分支生效
if (process.env.NODE_ENV === 'development') {
export const debugLog = () => console.log('debug');
}</p><p>// ❌ 错误:导出赋值语句(非声明式)
export {};
export.filter = filter; // 这是 CommonJS 风格,ESM 不支持,且破坏静态性按需导出 + 独立入口(推荐进阶实践)
为最大化 Tree Shaking 效果,可提供扁平化入口,让用户直接导入子模块:
- 在
src/index.ts中仅做聚合导出(不新增逻辑):export { debounce } from './debounce';<br>export { throttle } from './throttle'; - 配置
exports字段(Node.js 12.20+ / npm v17+)支持深层路径导入:"exports": {<br> "./debounce": "./dist/debounce.js",<br> "./throttle": "./dist/throttle.js",<br>  ".": "./dist/index.js"<br>} - 用户可直接
import { debounce } from 'my-lib/debounce',跳过主入口,彻底规避未使用代码


















