package.json 里的 sideEffects 到底是干嘛的?
你第一次在 package.json 里看到 sideEffects,大概率是在某个组件库/工具库里:
1 | { |
然后心里冒出一个很真实的问题:这玩意儿跟“副作用”到底有什么关系?我业务代码也没写 Redux 啊。
其实它就是在跟打包工具(最典型是 webpack)打招呼:“兄弟,我这里哪些模块导入时不会做额外事情,你可以放心 Tree Shaking;哪些模块导入时会偷偷改世界观,你别把它摇没了。”
先搞清楚:这里说的副作用是什么?
在 sideEffects 语境里,“副作用”指的是:模块在被 import 的那一刻,就会发生一些“跟导出值无关”的行为。
最常见的副作用长这样:
- import 一个 CSS 文件(它不导出啥,但会影响样式)
- import 一个 polyfill(它会改全局对象/原型)
- import 一个初始化脚本(它会注册全局事件、往 window 挂东西、打点、改运行时配置)
- import 一个“自动注册”模块(比如某些库会在顶层执行
registerPlugin())
举几个直观例子:
1 | // 1) CSS:你只是 import,它就生效了 |
这些模块即使“没有被任何地方显式用到它的导出”,也不能删。因为它们的价值就在“执行那一下”。
sideEffects 是怎么影响 Tree Shaking 的?
Tree Shaking 的核心目标是:把没用到的代码干掉。
但打包工具要做这件事,必须保证一件事:删掉某段代码不会改变运行结果。
这里就有一个现实难题:如果一个模块可能有副作用,那么即使你只 import 了它但没用它的任何 export,打包工具也不敢随便删,因为“它可能做了点很关键的事”。
sideEffects 就是你主动告诉打包工具:哪些文件是“纯的”。
1) sideEffects: false
1 | { |
含义是:这个包里的模块默认都没有副作用。如果某个文件只是被 import 了,但它的导出又没被用到,那它就可能被整文件剪掉。
这在“纯工具函数库 / 纯组件导出库”里很常见,收益也很可观:能把“我只是引了个入口文件但其实只用了两个函数”的损耗降下去。
但前提是:你真的纯。否则后果通常是“没报错,但样式没了 / 初始化没了 / 行为不对了”,排查起来特别磨人。
2) sideEffects: [“…”]
更稳的写法是白名单:告诉打包工具“默认都没副作用,但这些文件例外,有副作用,别删”。
1 | { |
这套配置在“导出组件 + 同时有样式文件”的库里尤其常见。你想要 Tree Shaking,又不想把样式摇没,那就把 CSS 标出来。
最容易踩的坑:把 CSS 摇没了
最经典事故是这种:
- 你在库里写了
sideEffects: false - 你的组件样式是通过 import 引入的(例如
import './Button.css') - 用户只用了组件的某一部分导出
- 打包工具觉得“这个 CSS 文件没被用到”,于是把它干掉
- 线上:组件结构都在,但样式像没睡醒
所以如果你的包有样式 side effect,通常要配:
1 | { |
或者更明确一点,只标你自己的样式目录(看你的仓库结构)。
另一个坑:顶层代码“偷摸干活”
有些文件看起来像工具,实际一 import 就会做事:
1 | // analytics.ts |
这种文件如果被当成“无副作用”摇掉了,你可能不会立刻报错,最多就是“怎么埋点没了”。然后你开始怀疑人生。
这类模块要么拆开:
analytics-core.ts(纯函数、无副作用)analytics-init.ts(顶层注册/挂载,全副作用)
要么直接把它列进 sideEffects 白名单。
这东西是写给谁的?应用要不要配?
我的经验:
- 库(npm 包):很值得认真写。因为你写一次,所有使用者都能收益。
- 应用(业务工程):大多数时候没必要专门动
sideEffects。应用本身就是“我要把页面跑起来”,副作用多到数不过来,Tree Shaking 的收益更多来自正确的 ESM 构建、合理的导入方式、按需加载、拆包等。
如果你是做组件库/工具库的,那 sideEffects 基本就是必修课;如果你是业务工程里看到别人加了它,先别急着抄,确认下你有没有 CSS/初始化脚本/全局 patch 这类东西。
怎么判断自己写没写错?
两条很实用的自测思路:
- 构建后跑一遍关键页面:样式、初始化逻辑、埋点、polyfill 有没有异常。
- 特别关注“没报错但行为不对”的问题:比如某些注册行为、全局事件、样式注入,这些都是 sideEffects 写错时的高发区。
写 sideEffects 的目标不是“看起来更工程化”,而是让打包工具能更大胆地优化,同时你自己也能明确哪些模块是“导出即用”,哪些模块是“导入即执行”。
Happy Coding!
