在 javascript 项目中实现 tree shaking,入口文件需要确保使用 es module (esm) 语法的 import 和 export,并严格遵循纯函数式编程原则。tree shaking 依赖静态分析来识别未使用的代码,因此必须避免使用 commonjs 的 require 或动态导入。只要入口文件正确引用了模块中的特定导出项,构建工具(如 webpack 或 vite)就能自动剔除未引用的死代码。

如何配置构建工具以启用 Tree Shaking
要启用 Tree Shaking,必须在构建工具的配置中明确设置模式为 production 或在 optimization 选项中开启 usedExports 和 sideEffects。对于 Webpack,这通常意味着将 mode 设为 'production',并在 package.json 中标记无副作用的文件。Vite 则默认基于 Rollup 启用此功能,无需额外配置即可处理 ESM 树摇。

{
"name": "my-tree-shake-app",
"version": "1.0.0",
"sideEffects": false,
"dependencies": {
"lodash-es": "^4.17.21"
}
}// webpack.config.js
const path = require('path');
module.exports = {
mode: 'production', // 关键:生产模式默认优化
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
usedExports: true, // 标记未使用的导出
sideEffects: true, // 根据 package.json 跳过副作用文件
},
};上述配置确保了构建过程会检查每个导出的使用情况。如果 package.json 中的 sideEffects 设置为 false,构建工具可以安全地假设所有 JS 文件都是纯函数,从而大胆地移除未使用的部分。若存在 CSS 或全局脚本等副作用,需在数组中列出它们,例如 ["*.css", "src/polyfill.js"]。
立即学习“Java免费学习笔记(深入)”;
如何处理带有副作用的库
当引入的第三方库包含全局副作用(如修改原型链或注册全局变量)时,Tree Shaking 可能会误删必要的初始化代码。此时需要在 package.json 的 sideEffects 字段中显式声明这些文件,或者在导入时直接引入整个模块而非特定导出项。对于像 lodash 这样的传统库,应改用 lodash-es 版本以支持 ESM 按需加载。

// 错误做法:可能触发副作用但无法摇树
import _ from 'lodash';
console.log(_.chunk([1, 2], 1));
// 正确做法:仅导入需要的函数
import { chunk } from 'lodash-es';
console.log(chunk([1, 2], 1));
// 针对有副作用的库(如 polyfills)
// 在 package.json 中声明:
// "sideEffects": ["./src/polyfill.js"]
// 或者在代码中直接引入整个文件以保留副作用
import 'some-library/global-setup';通过区分纯函数库和有副作用的库,开发者可以精确控制哪些代码被保留。对于 lodash-es,由于它完全基于 ESM 且无副作用,Tree Shaking 效果最佳。而对于需要全局状态的库,必须通过 sideEffects 配置或全量导入来保护其核心逻辑不被意外剥离。
为什么某些导出仍然未被移除
即使配置正确,某些导出仍可能残留在最终包中,常见原因包括:使用了动态属性访问、非纯函数的副作用、或构建工具未能正确解析依赖图。例如,如果代码通过字符串拼接访问对象属性(如 obj['key']),静态分析器无法确定该属性是否被使用,从而保守地保留相关代码。此外,若模块内部存在隐式副作用(如执行时的全局变量赋值),也会阻止 Tree Shaking。
// 导致 Tree Shaking 失效的代码示例
const config = {
debug: true,
verbose: false
};
// 动态访问可能导致分析器无法确定 usage
function getConfig(key) {
return config[key]; // 静态分析难以追踪 key 的具体值
}
// 更好的做法:显式导出并使用
export const DEBUG_MODE = true;
export function isDebug() {
return DEBUG_MODE;
}
// 入口文件调用
import { isDebug } from './config';
if (isDebug()) {
console.log('Debug on');
}解决此类问题的关键在于消除动态性并确保模块的纯函数特性。将配置项拆分为独立的命名导出,并避免在模块顶层执行不可预测的逻辑,能让构建工具更准确地进行死代码消除。同时,定期审查构建输出,使用工具如 webpack-bundle-analyzer 可视化残留代码,有助于定位具体的失效点。


















