深入淺出的webpack4構建工具---Scope Hoisting(十六)

龍恩0707發表於2018-10-01

一:什麼是Scope Hoisting? 它有什麼作用?
Scope Hoisting 它可以讓webpack打包出來的程式碼檔案更小,執行更快,它可以被稱作為 "作用域提升"。是在webpack3中提出來的,當然現在webpack4也是支援的。

在介紹之前,我們還是來和之前一樣,看看我們專案整個目錄架構如下:

### 目錄結構如下:
demo1                                       # 工程名
|   |--- dist                               # 打包後生成的目錄檔案             
|   |--- node_modules                       # 所有的依賴包
|   |--- app
|   | |---index
|   | | |-- views                           # 存放所有vue頁面檔案
|   | | | |-- home.vue
|   | | | |-- index.vue
|   | | | |-- xxx.vue
|   | | |-- components                      # 存放vue公用的元件
|   | | |-- js                              # 存放js檔案的
|   | | |-- app.js                          # vue入口配置檔案
|   | | |-- router.js                       # 路由配置檔案
|   |--- views
|   | |-- index.html                        # html檔案
|   |--- webpack.config.js                  # webpack配置檔案 
|   |--- .gitignore  
|   |--- README.md
|   |--- package.json
|   |--- .babelrc                           # babel轉碼檔案

首先我們在 app/index/js 下新建 index.js 程式碼如下:

export default 'xxxx';

然後在我們的入口檔案 app/index/app.js 程式碼如下:

import index from './js/index';
console.log(index);

然後執行 npm run build 打包後,bundle.js 程式碼如下:

/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
/******/
/******/  // The require function
/******/  function __webpack_require__(moduleId) {
/******/
/******/    // Check if module is in cache
/******/    if(installedModules[moduleId]) {
/******/      return installedModules[moduleId].exports;
/******/    }
/******/    // Create a new module (and put it into the cache)
/******/    var module = installedModules[moduleId] = {
/******/      i: moduleId,
/******/      l: false,
/******/      exports: {}
/******/    };
/******/
/******/    // Execute the module function
/******/    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/    // Flag the module as loaded
/******/    module.l = true;
/******/
/******/    // Return the exports of the module
/******/    return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__)
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/    if(!__webpack_require__.o(exports, name)) {
/******/      Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/    }
/******/  };
/******/
/******/  // define __esModule on exports
/******/  __webpack_require__.r = function(exports) {
/******/    if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/    }
/******/    Object.defineProperty(exports, '__esModule', { value: true });
/******/  };
/******/
/******/  // create a fake namespace object
/******/  // mode & 1: value is a module id, require it
/******/  // mode & 2: merge all properties of value into the ns
/******/  // mode & 4: return value when already ns object
/******/  // mode & 8|1: behave like require
/******/  __webpack_require__.t = function(value, mode) {
/******/    if(mode & 1) value = __webpack_require__(value);
/******/    if(mode & 8) return value;
/******/    if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/    var ns = Object.create(null);
/******/    __webpack_require__.r(ns);
/******/    Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/    if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/    return ns;
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules
/******/  __webpack_require__.n = function(module) {
/******/    var getter = module && module.__esModule ?
/******/      function getDefault() { return module['default']; } :
/******/      function getModuleExports() { return module; };
/******/    __webpack_require__.d(getter, 'a', getter);
/******/    return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__
/******/  __webpack_require__.p = "";
/******/
/******/
/******/  // Load entry module and return exports
/******/  return __webpack_require__(__webpack_require__.s = "./app/index/app.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./app/index/app.js":
/*!**************************!*\
  !*** ./app/index/app.js ***!
  \**************************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _js_index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./js/index */ "./app/index/js/index.js");


console.log(_js_index__WEBPACK_IMPORTED_MODULE_0__["default"]);

/***/ }),

/***/ "./app/index/js/index.js":
/*!*******************************!*\
  !*** ./app/index/js/index.js ***!
  \*******************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);

/* harmony default export */ __webpack_exports__["default"] = ('xxxx');

/***/ })

/******/ });
//# sourceMappingURL=bundle.js.map

如上程式碼我們沒有使用 Scope Hoisting 功能,輸出如上那麼多程式碼,下面我們來看看引入 Scope Hoisting 功能,打包後的程式碼;

要使用 Scope Hoisting 功能,首先我們需要 的是我們JS檔案都使用ES6的語法來編寫的,否則它是不會生效的。還是上面的程式碼不變。

使用 Scope Hoisting(編寫的程式碼需要支援ES6規範)

Scope Hoisting 是webpack內建的功能,只要配置一個外掛即可,如下在webpack.config.js 程式碼如下配置:

module.exports = {
  plugins: [
    // 開啟 Scope Hoisting 功能
    new webpack.optimize.ModuleConcatenationPlugin()
  ]
}

然後執行npm run build 打包後的bundle.js 程式碼如下:

/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
/******/
/******/  // The require function
/******/  function __webpack_require__(moduleId) {
/******/
/******/    // Check if module is in cache
/******/    if(installedModules[moduleId]) {
/******/      return installedModules[moduleId].exports;
/******/    }
/******/    // Create a new module (and put it into the cache)
/******/    var module = installedModules[moduleId] = {
/******/      i: moduleId,
/******/      l: false,
/******/      exports: {}
/******/    };
/******/
/******/    // Execute the module function
/******/    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/    // Flag the module as loaded
/******/    module.l = true;
/******/
/******/    // Return the exports of the module
/******/    return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__)
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/    if(!__webpack_require__.o(exports, name)) {
/******/      Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/    }
/******/  };
/******/
/******/  // define __esModule on exports
/******/  __webpack_require__.r = function(exports) {
/******/    if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/    }
/******/    Object.defineProperty(exports, '__esModule', { value: true });
/******/  };
/******/
/******/  // create a fake namespace object
/******/  // mode & 1: value is a module id, require it
/******/  // mode & 2: merge all properties of value into the ns
/******/  // mode & 4: return value when already ns object
/******/  // mode & 8|1: behave like require
/******/  __webpack_require__.t = function(value, mode) {
/******/    if(mode & 1) value = __webpack_require__(value);
/******/    if(mode & 8) return value;
/******/    if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/    var ns = Object.create(null);
/******/    __webpack_require__.r(ns);
/******/    Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/    if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/    return ns;
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules
/******/  __webpack_require__.n = function(module) {
/******/    var getter = module && module.__esModule ?
/******/      function getDefault() { return module['default']; } :
/******/      function getModuleExports() { return module; };
/******/    __webpack_require__.d(getter, 'a', getter);
/******/    return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__
/******/  __webpack_require__.p = "";
/******/
/******/
/******/  // Load entry module and return exports
/******/  return __webpack_require__(__webpack_require__.s = "./app/index/app.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./app/index/app.js":
/*!**************************************!*\
  !*** ./app/index/app.js + 1 modules ***!
  \**************************************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";

// CONCATENATED MODULE: ./app/index/js/index.js

/* harmony default export */ var js = ('xxxx');
// CONCATENATED MODULE: ./app/index/app.js


console.log(js);

/***/ })

/******/ });
//# sourceMappingURL=bundle.js.map

如上程式碼可以看到,開啟 Scope Hoisting後,函式宣告由兩個變成了一個,app/index/js/index.js 程式碼直接被注入到 app.js裡面去了,如上程式碼 var js = ('xxx');

因此 啟用 Scope Hoisting的優點如下:
1. 程式碼體積會變小,因為函式宣告語句會產生大量程式碼,但是第二個沒有函式宣告。
2. 程式碼在執行時因為建立的函式作用域減少了,所以記憶體開銷就變小了。

具體的可以看官網(https://webpack.js.org/plugins/module-concatenation-plugin/)

但是對於有很多第三方庫並沒有使用ES6模組語法的程式碼,webpack它會降級處理這些非ES6編寫的程式碼,不使用 Scope Hoisting 優化。

因此在webpack中還需要加上如下程式碼配置:

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

我們可以在啟動webpack時候帶上 --display-optimization-bailout 引數,在輸出日誌中就會包含類似如下的日誌:如下圖

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

因此在webpack中所有的配置程式碼如下:

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

相關文章