wangning0 / Autumn_Ning_Blog

572 stars 120 forks source link

webpack3之Scope Hoisting #28

Open wangning0 opened 7 years ago

wangning0 commented 7 years ago

webpack3之Scope Hoisting

介绍

在webpack2发布半年不到,webpack3又发布了,这次的发布版本虽然跨度比较大,但是不同于webpack2的升级,它对于向下的兼容性还是要好很多的,但是由于内部的突破性变化导致某些插件的使用,根据官方的数据表明,目前为止,至少98%的用户都没有遇到升级过程中不兼容的问题。

在这次升级中,有以下特性:

我们在这篇文章中,主要的对象是作用域的提升scope hoisting

如果对于webpack前两个版本了解的同学,应该知道,在打包后的文件里面,每个模块都会被包装在一个单独的函数闭包中,这些闭包会导致你JS在浏览器中的执行速度变慢,并且会导致内存占用率增加,而另外的打包工具rollup则是将所有的模块包装在一个大的闭包中

所以在webpack3中增加了这个功能,我们可以在配置项中添加对应的配置来开启该功能

module.exports = {
    // ...
    plugins: [
        new webpack.optimize.ModuleConcatenationPlugin()
    ]
}
    // ...

并且该功能也会让代码打包的体积变得更小,加快运行的速度

Scope Hoisting 和 Code splitting

我们都知道webpack有一个核心功能是Code-splitting, 并且在webpack2也开始支持了原生ES6的模块加载方案,那么对于按需加载的模块,肯定是在打包过程中,形成另外的打包文件的,那么webpack 是怎么做到 code splitting的呢?

所以在webpack中不能将所有所有的模块直接放在同一个作用域下,有以下几个原因:

可能说起来比较抽象,我们举个例子,来对比下webpack2和webpack3再打包同样的代码后生成的代码

webpack2 VS webpack3

我们都统一建立如下关系的文件,分别用webpack2和webpack3打包,观察下生成的打包文件的不同

71309eff-092d-4ecc-9486-3897d5d782f8

上图中实现表示的是同步的依赖关系,大箭头表示的是异步的依赖关系

根据我们上文中所说的规则,webpack会使用一种称为 “局部范围提升”的方法,该方法会选择可以展开的最大的ES模块进行变量提升,会将它们和webpack的默认原函数相连接

所以上述的模块之间的关系会变成下图:

db1d93a5-22e4-4263-9a08-5241fa6e7f86

index.js

import { a, x, y } from "./a";
import * as b from "./b";

import("./lazy").then(function(lazy) {
    console.log(a, b.a(), x, y, lazy.c, lazy.d.a, lazy.x, lazy.y);
});

lazy.js

export * from "./c";
import * as d from "./d";
export { d };

a.js

// module a
export var a = "a";
export * from "./shared";

b.js

// module b
export function a() {
    return "b";
};

c.js

// module c
import { c as e } from "./cjs";

export var c = String.fromCharCode(e.charCodeAt(0) - 2);

export { x, y } from "./shared";

d.js

// module d
export var a = "d";

cjs.js

// module cjs (commonjs)
exports.c = "e";

shared.js

// shared module
export var x = "x";
export * from "./shared2";

shared2.js

// shared2 module
export var y = "y";

webopack2打包的代码

只保留了模块的代码

// bundle.js
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return a; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__shared__ = __webpack_require__(2);
/* harmony namespace reexport (by used) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_0__shared__["a"]; });
/* harmony namespace reexport (by used) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_0__shared__["b"]; });
// module a
var a = "a";

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = a;
// module b
function a() {
    return "b";
};

/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return x; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__shared2__ = __webpack_require__(5);
/* harmony namespace reexport (by used) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_0__shared2__["a"]; });
// shared module
var x = "x";

/***/ }),
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__a__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__b__ = __webpack_require__(1);

__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 4)).then(function(lazy) {
    console.log(__WEBPACK_IMPORTED_MODULE_0__a__["a"], __WEBPACK_IMPORTED_MODULE_1__b__["a"](), __WEBPACK_IMPORTED_MODULE_0__a__["b" /* x */], __WEBPACK_IMPORTED_MODULE_0__a__["c" /* y */], lazy.c, lazy.d.a, lazy.x, lazy.y);
});

/***/ }),
/* 4 */,
/* 5 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return y; });
// shared2 module
var y = "y";

/***/ })
/******/ ]);
// 0.bundle.js
webpackJsonp([0],[
/* 0 */,
/* 1 */,
/* 2 */,
/* 3 */,
/* 4 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(6);
/* harmony namespace reexport (by provided) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_0__c__["a"]; });
/* harmony namespace reexport (by provided) */ __webpack_require__.d(__webpack_exports__, "x", function() { return __WEBPACK_IMPORTED_MODULE_0__c__["b"]; });
/* harmony namespace reexport (by provided) */ __webpack_require__.d(__webpack_exports__, "y", function() { return __WEBPACK_IMPORTED_MODULE_0__c__["c"]; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__d__ = __webpack_require__(8);
/* harmony reexport (module object) */ __webpack_require__.d(__webpack_exports__, "d", function() { return __WEBPACK_IMPORTED_MODULE_1__d__; });

/***/ }),
/* 5 */,
/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return c; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__cjs__ = __webpack_require__(7);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__cjs___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__cjs__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__shared__ = __webpack_require__(2);
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return __WEBPACK_IMPORTED_MODULE_1__shared__["a"]; });
/* harmony reexport (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return __WEBPACK_IMPORTED_MODULE_1__shared__["b"]; });
// module c

var c = String.fromCharCode(__WEBPACK_IMPORTED_MODULE_0__cjs__["c"].charCodeAt(0) - 2);

/***/ }),
/* 7 */
/***/ (function(module, exports) {

// module cjs (commonjs)
exports.c = "e";

/***/ }),
/* 8 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return a; });
// module d
var a = "d";

/***/ })
]);

上述代码可以发现,每个文件都被视为一个module,并且使用了闭包函数把它们分别进行了包裹

webpack3打包代码

只保留模块的代码

// bundle.js
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

// CONCATENATED MODULE: ./shared2.js
// shared2 module
var y = "y";
// CONCATENATED MODULE: ./shared.js
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return x; });
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "b", function() { return y; });
// shared module
var x = "x";

/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

// CONCATENATED MODULE: ./a.js
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__shared__ = __webpack_require__(0);
// module a
var a = "a";

// CONCATENATED MODULE: ./b.js
// module b
function b_a() {
    return "b";
};
// CONCATENATED MODULE: ./index.js

__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 3)).then(function(lazy) {
    console.log(a, b_a(), __WEBPACK_IMPORTED_MODULE_0__shared__["a"], __WEBPACK_IMPORTED_MODULE_0__shared__["b"], lazy.c, lazy.d.a, lazy.x, lazy.y);
});
// 0.bundle.js
webpackJsonp([0],[
/* 0 */,
/* 1 */,
/* 2 */
/***/ (function(module, exports) {

// module cjs (commonjs)
exports.c = "e";

/***/ }),
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

// CONCATENATED MODULE: ./c.js
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__cjs__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__cjs___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__cjs__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__shared__ = __webpack_require__(0);
// module c

var c = String.fromCharCode(__WEBPACK_IMPORTED_MODULE_0__cjs__["c"].charCodeAt(0) - 2);

// CONCATENATED MODULE: ./d.js
var d_namespaceObject = {};
__webpack_require__.d(d_namespaceObject, "a", function() { return a; });
// module d
var a = "d";
// CONCATENATED MODULE: ./lazy.js
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "c", function() { return c; });
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "x", function() { return __WEBPACK_IMPORTED_MODULE_1__shared__["a"]; });
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "y", function() { return __WEBPACK_IMPORTED_MODULE_1__shared__["b"]; });
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "d", function() { return d_namespaceObject; });

/***/ })
]);

从代码上分析,是符合我们上面的图所划分的进行的作用域提升的

结论

通过webpack2和webpack3的所形成的打包文件的对比来看,在代码的体积上,和性能上是有所提升的,对于一个很大型且复杂的项目来说,webpack3的性能速度有所提升和打包后的代码体积更小。

xcatliu commented 7 years ago

看上去不错,期待 webpack3

JLraining commented 7 years ago

说好的加上耗时对比呢。。。。

wangning0 commented 7 years ago

@JLraining 今天搞!昨天没开机

wangning0 commented 7 years ago

image image

ToPeas commented 7 years ago

博主有什么文章推荐webpack打包过程和打包出来的文件分析的吗?

wangning0 commented 7 years ago

https://github.com/lcxfs1991/blog/issues/14 @ToPeas