一:什麼是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() ] };