sideEffects是webpack中的 package.json 的一个配置项,用于标记该包的副作用情况,可优化tree shaking。
sideEffects 除了能设置 boolean 值, 还可以设置为数组, 传递需要保留副作用的代码文件(例如: "./src/polyfill.js") 或者传递模糊匹配符(例如: "src/**/*.css")
sideEffects: boolean | string[]
tree shaking
Webpack官方定义:tree shaking是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。tree shaking 它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。你可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
在 webpack 2 版本, 增加了对 ES Module 的支持, 使得 webpack 能够分析出未使用的 export 内容, 然后将其 tree-shrking 掉。但是模块中那些具备副作用的代码, webpack 会将其保留。
举一个例子, 项目中存在utils/a.js模块和/utils/b.js模块, 并通过utils/index.js提供统一入口,其中 b 模块包含一条打印语句, 是具有副作用的:
// utils/a.js
export function a() {
console.log('a');
}
// utils/b.js
console.log('======== b.js ==========');
export function b() {
console.log('b');
}
// utils/index.js
export * from './a';
export * from './b';
添加主入口app.js, 只引用 a 模块, 我们期望未使用的 b 模块被 tree-shaking 掉:
// app.js
import { a } from './utils';
a();
我们看一下打包后的结果, 注意要在 production 模式下打包. 结果如下所示, 我去掉了无关的 webpack 启动代码:
// output
([
function(e, t, r) {
'use strict';
r.r(t),
console.log('======== b.js =========='),
console.log('a');
},
])
打包结果中, 不包含 b 模块, 但是b.js中的副作用代码被保留了, 这是合乎情理的。像👆🏻这个例子,webpack 身为保守派,对于这类的代码默认情况下就会选择保留。(ES Module的静态分析能力可以简单的判断变量后续是否被引用、修改,但是不能判断一个变量完整的修改过程,不知道它是否已经指向了外部变量,所以很多有可能会产生副作用的代码,都只能保守的不删除。)
sideEffects
修改 package.json, 新增字段 "sideEffects": false, 该字段表明整个工程是"无副作用"的
重新调用 webpack 编译, 期待在 b 模块没被使用的情况下, b 中定义的 sum 方法也被 tree-shaking 掉, 结果如下
([
function(e, t, r) {
'use strict';
r.r(t), console.log('a');
},
])
如期望那样, 整个 b 模块都被 tree-shaking 掉了, 包括包含副作用的代码。
实际应用
实际项目中, 通常并不能简单的设置为 "sideEffects": false, 有些副作用是需要保留的, 比如引入样式文件
webpack 会认为所有 import 'xxx' 语句是仅引入而未使用, 如果你错误的将其声明成了"无副作用", 它们就会被 tree-shaking 掉, 并且由于 tree-shaking 仅在 production 模式生效, 本地开发时可能一切仍是正常的, 并不能及时发现问题。
下面这些都是"仅引入而未使用"的例子:
import './normalize.css';
import './polyfill';
import './App.less';
相应的, 下面这种就不算:
import icon from './icon.png';
function Icon() {
return (
<img src={icon} />
)
}
这些有副作用的文件, 我们要正确声明, 修改 sideEffects 值:
// package.json
"sideEffects": [
"./src/**/*.css"
]
antd4是通过 webpack4 提供的 sideEffects 配置项来实现按需加载的:
然而antd3则是专门开发了bebel插件,使得用户能以import { Button, Message } form 'antd'这样的方式去按需加载。本质上就是通过插件将上一句的代码又转化成如下:
import { Button, Message } form 'antd';
// 转换
import Button from 'antd/lib/button';
import Message from 'antd/lib/button';
显然这种方式麻烦了,所以sideEffects: false的方式肯定是更优的。
总结
初次看到 sideEffects 配置可能会很奇怪, 代码明明是有副作用的, 为什么要声明它是"无副作用"呢?
其实可以换个角度来想, sideEffects 是通知 webpack 该模块是可以安全的 tree-shaking 的, 无需关心其副作用。也就是说,只要你的包不是用来做 polyfill 或 shim 之类的事情,就尽管放心的给他加上 sideEffects: false 吧!
参考:
Webpack 中的 sideEffects 到底该怎么用?