sideEffects 副作用
webpack4新增的功能。
允许通过配置的方式去标识代码是否有副作用,从而为 Tree Shaking 提供更多的压缩空间。
sideEffects 一般用于开发npm模块时,标记是否有副作用。
官方文档中将它和 Tree Shaking 放在一起讲,所以容易误解为它们是因果关系,实际上二者没什么关系。
副作用
副作用:模块执行时除了导出成员之外所作的事情。
例如一个模块中定义了其他内容:
export default () => {
console.log('本模块不只导出一个默认成员,还做了一些其他事情')
}
// 以下是副作用代码
console.log('模块被使用')
window.foo = '往全局添加一个变量'
webpack 的 Tree Shaking 是 通过 usedExports 标记未使用的成员,打包时不生成导出它们的代码,从而通过minimize 移除它们。
所以 Tree Shaking 通过 usedExports 摇掉的是模块自己的代码中不被需要的部分。
但是导入这个模块的文件中,还保留了import代码,例如Tree Shaking示例打包后包裹/src/index.js
的函数中会保留「导入模块行为」的代码。
// 打包结果中包裹 /src/index.js 的函数
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// 保留了「导入模块行为」的代码
/* harmony import */ var _component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
document.body.append(Object(_component__WEBPACK_IMPORTED_MODULE_0__[/* Button */ "a"])());
})
因为package.json中的sideEffects字段,默认为true,即默认这个npm包有副作用(其下模块们除了导出还做了其他事情)
并且webpack的当前配置下,也未指定是否处理无副作用的代码。
所以这些import
代码被保留下来,自然,打包后,那些被import
调用的模块,不管里面的成员是否被使用或者导出的内容是否为空,都会生成一个包裹它们的函数。
而这些函数,作为打包文件中,执行webpack方法的参数(一个数组)中的元素,自然不会被minimize删除。
例如将上面的/src/component.js
中的成员分别单独创建一个文件(即「模块A」们),并在一个index.js
(即「模块B」)文件中全部导入。
使用时直接导入这个index.js
。
// /src/components/button.js ---------------
export default () => {
return document.createElement('button')
}
// /src/components/link.js ---------------
export default () => {
return document.createElement('a')
}
// 添加一个副作用代码
document.write('This is a side effect code')
// /src/components/heading.js ---------------
export default level => {
return document.createElement('h' + level)
}
// src/components/index.js ---------------
export { default as Button } from './button'
export { default as Link } from './link'
export { default as Heading } from './heading'
// src/index.js ---------------
import { Button } from './components'
document.body.append(Button())
沿用之前的示例,关闭babel的modules配置,optmization只启用usedExports。
// webpack.config.js
module.exports = {
mode: 'none',
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [ '@babel/preset-env' ]
},
},
},
],
},
optimization: {
usedExports: true,
// 压缩代码
// minimize: true,
},
}
打包查看结果,3个模块中的成员正常被标记已使用和未使用,但是/src/comonents/index.js
中保留了3个模块的「导入」代码。
即使link和headingm没有副作用,导入它们已经没有了意义。
// 包裹/src/components/index.js的函数
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// 导入button.js
/* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
// 导出button.js
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "a", function() { return _button__WEBPACK_IMPORTED_MODULE_0__["a"]; });
// 虽然没有用到link和heading中的成员,但还是保留了「导入模块行为」的代码
/* harmony import */ var _link__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
/* harmony import */ var _heading__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
})
webpack 和 package.json 中的 sideEffects
webpack通过配置optimization.sideEffects
为true
,表示打包时跳过那些没有被使用的且被package.json标记为无副作用的模块。
表现为:
- webpack打包结果不会保留这些没有意义的「导入模块行为」的代码。
- webpack打包结果不会保留没有使用(导入)的模块。
开启后,webpack会去package.json中寻找sideEffects
字段。
package.json中的sideEffects
字段表示,当前项目中的模块是否有副作用,默认为true
。
webpack找到这个字段,且它定义的如果是false
(当前项目中的模块无副作用)。
那么这些没有被用到的模块就不会被打包。
从而实现,一个模块中的成员全都没有被外部使用时,即这个模块没有被使用,这个模块就不会被打包进结果。
// package.json
{
"sideEffects": false // 默认true,表示当前项目中的模块是否有副作用
}
// webpack.config.js
module.exports = {
optimization: {
sideEffects: true // 默认false,表示是否移除无副作用的模块
usedExports: true,
}
}
打包结果:
// 打包中函数的参数
[
/* 0 */,
/* 1 */,
/* 2 */,
/* 3 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
document.body.append(Object(_components__WEBPACK_IMPORTED_MODULE_0__[/* default */ "a"])());
}),
/* 4 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony default export */ __webpack_exports__["a"] = (function () {
return document.createElement('button');
});
})
]
对比下不移除无副作用代码的打包结果:
[
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
document.body.append(Object(_components__WEBPACK_IMPORTED_MODULE_0__[/* Button */ "a"])());
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "a", function() { return _button__WEBPACK_IMPORTED_MODULE_0__["a"]; });
/* harmony import */ var _link__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3);
/* harmony import */ var _heading__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4);
}),
/* 2 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony default export */ __webpack_exports__["a"] = (function () {
return document.createElement('button');
});
}),
/* 3 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* unused harmony default export */ var _unused_webpack_default_export = (function () {
return document.createElement('a');
});
document.write('This is a side effect code');
}),
/* 4 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* unused harmony default export */ var _unused_webpack_default_export = (function (level) {
return document.createElement('h' + level);
});
})
]
注意:
package.json和webpack配置文件中的sideEffects
虽然同名,但表示的意义不同。
- package.json的
sideEffects
:标识当前package.json所影响的项目,当中所有的代码是否有副作用- 默认true,表示当前项目中的代码有副作用
- webpack配置文件中的
sideEffects
:开启功能,是否移除无副作用的代码- 默认false,表示不移除无副作用的模块
- 在production模式下自动开启。
webpack不会识别代码是否有副作用,只会读取package.json的sideEffects
字段。
二者需要配合使用,才能处理无副作用的模块。
sideEffects 使用注意
使用webpack sideEffects功能的前提是,确保代码没有副作用。
否则webpack打包时就会误删那些有副作用的代码。
例如导入一个模块,它的内容是立即执行的,而不是通过导出成员等待被调用。
// /src/extend.js
window.onload = () => {
alert('This is a side effect code')
}
// /src/index.js
import './extend'
当使用sideEffects功能后,这个模块就不会被打包。
这样的场景通常是直接加载一个模块中的内容:
- 加载立即执行的脚本
- 加载 css 模块
- 等
解决办法:
- 在package.json中关闭标识无副作用
- 或者具体标识项目中哪些文件有副作用,webpack就不会忽略这些有副作用的模块
- 这种方式,未被标识的模块,就会被当作无副作用处理
// package.json
{
"sideEffects": [
"./src/extend.js", // 标识有副作用的文件
"*.css", // 也可以使用路径通配符
"style/" // 注意目录必须带后面的/
]
}