跳到主要内容

Tree Shaking 与 sideEffects

如果你希望代码或开源库能够被正确地 Tree Shaking(让打包器在最终产物中自动移除未使用的代码),通常需要显式告诉构建工具:哪些文件在被引入时不会产生副作用。最常见的做法是在 package.json 中配置 sideEffects

⚠️ 不配置 sideEffects****,可能导致 Tree Shaking 看起来完全没有生效。 打包工具无法确定某个模块能否被安全删除时,通常会采取保守策略:即使模块的导出没有被使用,也可能保留整个模块及其依赖。这个问题在依赖包、barrel file(index.ts 重导出)和模块依赖链较复杂时尤其常见。

需要注意:这不代表所有打包器在缺少该配置时都无法删除任何未使用的导出。ES Module 的静态分析仍可能完成一部分导出级优化;但文件级、模块级裁剪往往会因为潜在副作用而受阻,最终表现为“明明没有使用,代码却还在产物里”。因此,当 Tree Shaking 不生效时,sideEffects 应当是优先排查项。

  • 绝大多数纯 JS/TS 工具库:可以设置为 "sideEffects": false。这表示包内模块在被 import 时不会执行影响外部环境的行为,例如修改全局变量、注册 polyfill、自动打补丁、向 window 挂载属性或自动注入样式。打包器因此可以放心删除未使用的模块,而不只是未使用的导出。
  • 确实有副作用的文件:不要一刀切地设置为 false,否则样式或初始化逻辑可能被错误裁剪。应使用数组将这些文件加入白名单,例如 *.css*.scss、polyfill 和全局注册文件。

简单来说:sideEffects 不只是一个“锦上添花”的优化提示。对很多库和复杂依赖图来说,它直接决定了打包器是否敢于删除整个未使用模块。


什么是 Tree Shaking?

Tree Shaking 是打包工具(Webpack/Rollup/esbuild)在生产构建时自动删除未使用的导出代码的优化手段。它依赖 ES Module 的静态结构分析。

sideEffects 配置详解

package.json 配置

{
"name": "my-library",
"sideEffects": false
}

// 或者指定有副作用的文件
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfill.ts",
"./src/register-global.ts"
]
}

什么算”副作用”?

  • 修改全局变量(window.xxx = …)
  • 注册 polyfill(import “core-js/stable”)
  • CSS/SCSS 文件的引入(import “./style.css”)
  • 立即执行的函数表达式(IIFE)且影响外部状态
  • 模块顶层的 console.log / fetch 等调用

什么不算”副作用”?

  • 纯函数定义和导出
  • 类型定义和接口声明
  • 常量定义(export const FOO = 1)
  • 纯计算逻辑(不依赖/修改外部状态)

Webpack 中的 Tree Shaking

// webpack.config.js
module.exports = {
mode: 'production', // 生产模式自动启用
optimization: {
usedExports: true, // 标记未使用的导出
minimize: true, // 压缩时删除标记的代码
sideEffects: true, // 读取 package.json 的 sideEffects
},
};

常见问题与排查

问题 1:CSS 被错误删除

// ❌ 设置了 sideEffects: false,CSS import 被当作无副作用删除
import './button.css';

// ✅ 解决:将 CSS 加入白名单
// package.json: "sideEffects": ["*.css"]

问题 2:类的静态属性导致无法 Tree Shake

// ❌ 类的静态属性赋值被视为副作用
class MyComponent {
static displayName = 'MyComponent'; // 这行是副作用!
}

// ✅ 添加 /*#__PURE__*/ 注释
const MyComponent = /*#__PURE__*/ (() => {
class MyComponent {}
MyComponent.displayName = 'MyComponent';
return MyComponent;
})();

问题 3:barrel file(index.ts 重导出)影响 Tree Shaking

// ❌ src/index.ts(barrel file)
export { Button } from './Button';
export { Modal } from './Modal';
export { Table } from './Table';
// 即使只用了 Button,Modal 和 Table 的初始化代码也可能被保留

// ✅ 直接从具体模块导入
import { Button } from 'my-lib/Button';

验证 Tree Shaking 效果

  • 使用 webpack-bundle-analyzer 查看打包结果
  • 检查构建产物中是否包含未使用的模块
  • 使用 sideEffects-analysis 工具分析项目副作用
# 安装分析工具
pnpm add -D webpack-bundle-analyzer

# 在 webpack 配置中添加
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [new BundleAnalyzerPlugin()]