【踩坑】sideEffects与摇树优化丢失问题
问题背景
在项目中引入 lodash 后,通过分析打包资源发现 lodash 的体积过大。
为了优化包体积,我们决定通过配置 package.json 中的 sideEffects 字段来启用摇树优化(Tree Shaking)。
然而在启用摇树优化后,出现了两个问题:
CSS 样式丢失
部分 JS 模块未被执行
解决方法
CSS 样式丢失的解决方案
在 package.json 中将 CSS 文件标记为有副作用:
"sideEffects": [
"**/*.css"
]JS 模块未执行的解决方案
对于没有导出任何内容(没有 export)但需要被执行的模块,需要将其添加到 sideEffects 配置中:
"sideEffects": [
"./src/utils/resetFontSize.ts"
]
摇树优化原理
sideEffects 字段用于标记哪些文件具有副作用。打包工具在进行摇树优化时,主要遵循以下步骤:
基于
ESModule进行静态分析,识别未被引用的模块判断模块是否有副作用,无副作用则可以安全删除
怎么知道有没有副作用呢?
一方面
webpack依赖的terser会去检测语句中的副作用(但 JS 作为动态类型语言,很多情况下不执行是分析不出结果的,所以语句层面的副作用分析能力有限,可以通过魔法注解/*#__PURE__*/来配合,旨在标明该声明无副作用)。另一方面我们可以在
sideEffects配置项中告诉打包工具哪些文件是有副作用的,即允许打包工具应该跳过对整个模块及其子树的分析。很多情况下静态分析无法准确判断副作用。可以使用魔法注释/*#__PURE__*/来辅助标记无副作用的代码。
实际案例
以下是一个典型的问题示例:
// resetFontSize.ts
import { detectDeviceType } from './deviceUtils';
(function flexible(window: Window, document: Document) {
function resetFontSize() {
const clientWidth = parseInt(
document.documentElement.clientWidth.toString(),
10
);
let size = 0;
// 使用 detectDeviceType 函数判断设备类型
if (detectDeviceType() === 'desktop') {
size = (document.documentElement.clientWidth / 1920) * 16;
document.documentElement.style.fontSize = (size <= 14 ? 13 : size) + 'px';
} else {
size = clientWidth;
const fontSize = (size / 750) * 16;
document.documentElement.style.fontSize = fontSize + 'px';
}
}
resetFontSize();
window.addEventListener('pageshow', resetFontSize);
window.addEventListener('resize', resetFontSize);
})(window, document);
// index.ts
import '@utils/resetFontSize';虽然 index.ts 中引用了 resetFontSize,但由于该模块没有任何 export,摇树优化会将其删除。解决方案是在 sideEffects 中声明:
// package.json
"sideEffects": [
"./src/utils/resetFontSize.ts"
]
Vue 项目的特殊处理
对于 Vue 项目,如果组件中包含样式,需要特别处理:
// package.json
"sideEffects": [
"**/*.css",
// 标记包含样式的 Vue 组件
"./src/ComponentWithStyle.vue"
]
另外,也可以选择完全禁用摇树优化。
最佳实践
为了更好地利用摇树优化:
优先使用支持 ES 模块的包版本,如使用
lodash-es替代lodash对于必要的副作用代码,明确在
sideEffects中声明合理使用
/*#__PURE__*/注释标记无副作用的代码