webpack 在打包时把各个模块打包为一个闭包,会有很多多余的代码,这些打包函数使你的 JavaScript 在浏览器中处理的更慢。Scope Hoisting 又译作 "作用域提升",它可以让 Webpack 打包出来的代码文件更小、运行的更快。
认识 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
在 webpack 中,设置 mode: 'production' 之后,就默认添加了插件webpack.optimize.ModuleConcatenationPlugin(),开启了 Scope Hoisting。
如果想要关闭Scope Hoisting 可以通过下面的配置完成。
optimization: {
concatenateModules: false // 关闭Scope Hoisting
}
原理
Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。
由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。
实例
入口JS
// index.js
import foo from './foo'
import bar from './bar'
console.log('run => index.js')
console.log(`log => foo.name: ${foo.name}`)
console.log(`log => bar.name: ${bar.name}`)
export default {
name: 'index'
}
// foo.js
import bar from './bar'
console.log('run => foo.js')
console.log(`log => bar.name: ${bar.name}`)
export default {
name: 'foo'
}
// bar.js
console.log('run => bar.js')
export default {
name: 'bar'
}
打包后的代码
关注立即执行函数的入参
(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 = 0))
})(
/************************************************************************/
[
/* 0 */
/***/ function (module, __webpack_exports__, __webpack_require__) {
'use strict'
__webpack_require__.r(__webpack_exports__)
// CONCATENATED MODULE: ./src/yh-webpack/bar.js
console.log('run => bar.js')
/* harmony default export */ var bar = {
name: 'bar',
}
// CONCATENATED MODULE: ./src/yh-webpack/foo.js
console.log('run => foo.js')
console.log('log => bar.name: '.concat(bar.name))
/* harmony default export */ var foo = {
name: 'foo',
}
// CONCATENATED MODULE: ./src/yh-webpack/index.js
console.log('run => index.js')
console.log('log => foo.name: '.concat(foo.name))
console.log('log => bar.name: '.concat(bar.name))
/* harmony default export */ var yh_webpack = (__webpack_exports__[
'default'
] = {
name: 'index',
})
/***/
},
]
)