
一、什麼是 Scope Hoisting
Scope Hoisting 是 webpack3 的新功能,直譯為 "「作用域提升」",它可以讓 webpack 打包出來的「代碼檔案更小」,「運作更快」。
在 JavaScript 中,還有“變量提升”和“函數提升”,JavaScript 會将變量和函數的聲明提升到目前作用域頂部,而“作用域提升”也類似,webpack 将引入到 JS 檔案“提升到”它的引入者的頂部。
首先回顧下在沒有 Scope Hoisting 時用 webpack 打包下面兩個檔案:
// main.js
export default "hello leo~";
// index.js
import str from "./main.js";
使用 webpack 打包後輸出檔案内容如下:
[
(function (module, __webpack_exports__, __webpack_require__) {
var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]);
}),
(function (module, __webpack_exports__, __webpack_require__) {
__webpack_exports__["a"] = ('hello leo~');
})
]
再開啟 Scope Hoisting 後,相同源碼打包輸出結果變為:
[
(function (module, __webpack_exports__, __webpack_require__) {
var util = ('hello leo~');
console.log(util);
})
]
對比兩種打包方式輸出的代碼,我們可以看出,啟用 Scope Hoisting 後,函數聲明變成一個,
main.js
中定義的内容被直接注入到
main.js
對應子產品中,這樣做的好處:
- 「代碼體積更小」,因為函數申明語句會産生大量代碼,導緻包體積增大(子產品越多越明顯);
- 代碼在運作時因為建立的函數作用域更少,「記憶體開銷也随之變小」。
二、webpack 子產品機制
我們使用下面
webpack.config.js
配置,打包來看看 webpack 子產品機制:
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'none',
optimization: {
usedExports: true,
},
};
打包後輸出結果(精簡後):
通過分析,我們可以得出以下結論:
- webpack 打包輸出打是一個 IIFE(匿名閉包);
-
是一個數組,每一項是一個子產品初始化函數;modules
- 使用
來家在子產品,傳回 __webpack_require()
;module.exports
- 通過
啟動程式。__webpack_require__(__webpack_require__.s = 0);
三、Scope Hoisting 原理
Scope Hoisting 的實作原理其實很簡單:分析出子產品之間的依賴關系,盡可能将打散的子產品合并到一個函數中,前提是不能造成代碼備援。是以「隻有那些被引用了一次的子產品才能被合并」。
由于 Scope Hoisting 需要分析出子產品之間的依賴關系,是以源碼「必須采用 ES6 子產品化語句」,不然它将無法生效。原因和4-10 使用 TreeShaking 中介紹的類似。
四、Scope Hoisting 使用方式
1. 自動啟用
在 webpack 的
mode
設定為
production
時,會預設自動啟用 Scope Hooting。
// webpack.config.js
// ...
module.exports = {
// ...
mode: "production"
};
2. 手動啟用
在 webpack 中已經内置 Scope Hoisting ,是以用起來很簡單,隻需要配置ModuleConcatenationPlugin 插件即可:
// webpack.config.js
// ...
const webpack = require('webpack');
module.exports = {
// ...
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
考慮到 Scope Hoisting 以來 ES6 子產品化文法,而現在很多 npm 包的第三方庫還是使用 CommonJS 文法,為了充分發揮 Scope Hoisting 效果,我們可以增加以下
mainFields
配置:
// webpack.config.js
// ...
const webpack = require('webpack');
module.exports = {
// ...
resolve: {
// 針對 npm 中的第三方子產品優先采用 jsnext:main 中指向的 ES6 子產品化文法的檔案
mainFields: ['jsnext:main', 'browser', 'main']
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
針對非 ES6 子產品化文法的代碼,webpack 會降級處理不使用 Scope Hoisting 優化,我們可以在 webpack 指令上增加
--display-optimization-bailout
參數,在輸出的日志檢視哪些代碼做了降級處理:
// package.json
{
// ...
"scripts": {
"build": "webpack --display-optimization-bailout"
}
}
我們寫個簡單示例代碼:
// index.js
import str from "./main.js";
const { name } = require('./no-es6.js');
// main.js
export default "hello leo~";
// no-es6.js
module.exports = {
name : "leo"
}
接着打包測試,可以看到控制台輸出下面日志:
輸出的日志中
ModuleConcatenation bailout
告訴我們哪些檔案因為什麼原因導緻降級處理了。
五、總結
六、參考文章
- 《通過Scope Hoisting優化Webpack輸出》
- 《webpack 的 scope hoisting 是什麼?》