通過Scope Hoisting優化Webpack輸出

浩麟發表於2019-03-01

Scope Hoisting 可以讓 Webpack 打包出來的程式碼檔案更小、執行的更快, 它又譯作 "作用域提升",是在 Webpack3 中新推出的功能。 單從名字上看不出 Scope Hoisting 到底做了什麼,下面來詳細介紹它。

認識 Scope Hoisting

讓我們先來看看在沒有 Scope Hoisting 之前 Webpack 的打包方式。

假如現在有兩個檔案分別是 util.js:

export default 'Hello,Webpack';
複製程式碼

和入口檔案 main.js:

import str from './util.js';
console.log(str);
複製程式碼

以上原始碼用 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,Webpack');
  })
]
複製程式碼

在開啟 Scope Hoisting 後,同樣的原始碼輸出的部分程式碼如下:

[
  (function (module, __webpack_exports__, __webpack_require__) {
    var util = ('Hello,Webpack');
    console.log(util);
  })
]
複製程式碼

從中可以看出開啟 Scope Hoisting 後,函式申明由兩個變成了一個,util.js 中定義的內容被直接注入到了 main.js 對應的模組中。 這樣做的好處是:

  • 程式碼體積更小,因為函式申明語句會產生大量程式碼;
  • 程式碼在執行時因為建立的函式作用域更少了,記憶體開銷也隨之變小。

Scope Hoisting 的實現原理其實很簡單:分析出模組之間的依賴關係,儘可能的把打散的模組合併到一個函式中去,但前提是不能造成程式碼冗餘。 因此只有那些被引用了一次的模組才能被合併。

由於 Scope Hoisting 需要分析出模組之間的依賴關係,因此原始碼必須採用 ES6 模組化語句,不然它將無法生效。 原因和4-10 使用 TreeShaking 中介紹的類似。

使用 Scope Hoisting

要在 Webpack 中使用 Scope Hoisting 非常簡單,因為這是 Webpack 內建的功能,只需要配置一個外掛,相關程式碼如下:

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
  plugins: [
    // 開啟 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};
複製程式碼

同時,考慮到 Scope Hoisting 依賴原始碼需採用 ES6 模組化語法,還需要配置 mainFields。 原因在 4-10 使用 TreeShaking 中提到過:因為大部分 Npm 中的第三方庫採用了 CommonJS 語法,但部分庫會同時提供 ES6 模組化的程式碼,為了充分發揮 Scope Hoisting 的作用,需要增加以下配置:

module.exports = {
  resolve: {
    // 針對 Npm 中的第三方模組優先採用 jsnext:main 中指向的 ES6 模組化語法的檔案
    mainFields: ['jsnext:main', 'browser', 'main']
  },
};
複製程式碼

對於採用了非 ES6 模組化語法的程式碼,Webpack 會降級處理不使用 Scope Hoisting 優化,為了知道 Webpack 對哪些程式碼做了降級處理, 你可以在啟動 Webpack 時帶上 --display-optimization-bailout 引數,這樣在輸出日誌中就會包含類似如下的日誌:

[0] ./main.js + 1 modules 80 bytes {0} [built]
    ModuleConcatenation bailout: Module is not an ECMAScript module
複製程式碼

其中的 ModuleConcatenation bailout 告訴了你哪個檔案因為什麼原因導致了降級處理。

也就是說要開啟 Scope Hoisting 併發揮最大作用的配置如下:

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
  resolve: {
    // 針對 Npm 中的第三方模組優先採用 jsnext:main 中指向的 ES6 模組化語法的檔案
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 開啟 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};
複製程式碼

本例項提供專案完整程式碼

通過Scope Hoisting優化Webpack輸出

《深入淺出Webpack》全書線上閱讀連結

閱讀原文

相關文章