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