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/" // 注意目錄必須帶後面的/
]
}