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,也要看看其中一些不常見的知識,比如本文介紹的 Scope Hoisting,它對我們項目優化非常有幫助,但平常又很少會去注意。
六、參考文章《通過Scope Hoisting優化Webpack輸出》《webpack 的 scope hoisting 是什麼?》回復「加群」與大佬們一起交流學習~
點擊「閱讀原文」查看70+篇原創文章