天天看點

【Webpack】654- 了不起的 Webpack Scope Hoisting 學習指南

【Webpack】654- 了不起的 Webpack Scope Hoisting 學習指南

一、什麼是 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】654- 了不起的 Webpack Scope Hoisting 學習指南

通過分析,我們可以得出以下結論:

  • webpack 打包輸出打是一個 IIFE(匿名閉包);
  • ​modules​

    ​  是一個數組,每一項是一個子產品初始化函數;
  • 使用​

    ​__webpack_require()​

    ​ 來家在子產品,傳回 ​

    ​module.exports​

    ​ ;
  • 通過​

    ​__webpack_require__(__webpack_require__.s = 0);​

    ​ 啟動程式。

三、Scope Hoisting 原理

Scope Hoisting 的實作原理其實很簡單:分析出子產品之間的依賴關系,盡可能将打散的子產品合并到一個函數中,前提是不能造成代碼備援。是以「隻有那些被引用了一次的子產品才能被合并」。

由于 Scope Hoisting 需要分析出子產品之間的依賴關系,是以源碼「必須采用 ES6 子產品化語句」,不然它将無法生效。原因和4-10 使用 TreeShaking 中介紹的類似。

【Webpack】654- 了不起的 Webpack Scope Hoisting 學習指南

四、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"
}      

接着打包測試,可以看到控制台輸出下面日志:

【Webpack】654- 了不起的 Webpack Scope Hoisting 學習指南

輸出的日志中 ​

​ModuleConcatenation bailout​

​ 告訴我們哪些檔案因為什麼原因導緻降級處理了。

​五、總結​

​六、參考文章​

  • 《通過Scope Hoisting優化Webpack輸出》
  • 《webpack 的 scope hoisting 是什麼?》

繼續閱讀