正文
* @param {String} moduleId 模块 ID,一般为模块的源码路径,如 "./src/index.js"
* @returns {Object} exports 导出对象
*/
function
__webpack_require__
(
moduleId
)
{
// ...
}
// 在 __webpack_require__ 函数对象上挂载一些变量及函数 ...
// 传入表达式的值为 "./src/index.js"
return
__webpack_require__
(
__webpack_require__
.
s
=
"./src/index.js"
);
})(/* modules */);
可以看到其实主要做了两件事:
-
定义一个模块加载函数
__webpack_require__
。
-
使用加载函数加载入口模块
"./src/index.js"
。
整个 webpackBootstrap 中只出现了入口模块的影子,那其他模块又是如何加载的呢?我们顺着 __webpack_require__("./src/index.js") 细看加载函数的内部逻辑:
function __webpack_require__(moduleId) {
// 重复加载则利用缓存
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 如果是第一次加载,则初始化模块对象,并缓存
var module = installedModules[moduleId] = {
i: moduleId, // 模块 ID
l: false, // 模块加载标识
exports: {} // 模块导出对象
};
/**
* 执行模块
* @param module.exports -- 模块导出对象引用,改变模块包裹函数内部的 this 指向
* @param module -- 当前模块对象引用
* @param module.exports -- 模块导出对象引用
* @param __webpack_require__ -- 用于在模块中加载其他模块
*/
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 模块加载标识置为已加载
module.l = true;
// 返回当前模块的导出对象引用
return module.exports;
}
首先,加载函数使用了闭包变量
installedModules
,用来将已加载过的模块保存在内存中。 接着是初始化模块对象,并把它挂载到缓存里。然后是模块的执行过程,加载入口文件时
modules
[
moduleId
]
其实就是
.
/src/
index
.
js
对应的模块函数。执行模块函数前传入了跟模块相关的几个实参,让模块可以导出内容,以及加载其他模块的导出。最后标识该模块加载完成,返回模块的导出内容。
根据
__webpack_require__
的缓存和导出逻辑,我们得知在整个
IIFE
运行过程中,加载已缓存的模块时,都会直接返回
installedModules
[
moduleId
].
exports
,换句话说,相同的模块只有在第一次引用的时候才会执行模块本身。
模块执行函数
__webpack_require__
中通过
modules
[
moduleId
].
call
()
运行了模块执行函数,下面我们就进入到
webpackBootstrap
的参数部分,看看模块的执行函数。
/*** 入口模块 ./src/index.js ***/
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
// 用于区分 ES 模块和其他模块规范,不影响理解 demo,战略跳过。
__webpack_require__.r(__webpack_exports__);
/* harmony import */
// 源模块代码中,`import {plus} from './utils/math.js';` 语句被 loader 解析转化。
// 加载 "./src/utils/math.js" 模块,
var _utils_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils/math.js");
console.log('Hello webpack!');
console.log('1 + 2: ', Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["plus"])(1, 2));
}),
"./src/utils/math.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */
// 源模块代码中,`export` 语句被 loader 解析转化。
__webpack_require__.d(__webpack_exports__, "plus", function () {
return plus;
});
const plus = (a, b) => {
return a + b;
};
})
执行顺序是:入口模块 -> 工具模块 -> 入口模块。入口模块中首先就通过
__webpack_require__
(
"./src/utils/math.js"
)
拿到了工具模块的
exports
对象。再看工具模块,
ES
导出语法转化成了
__webpack_require__
.
d
(
__webpack_exports__
,
[
key
],
[
getter
])
,而
__webpack_require__
.
d
函数的定义在
webpackBootstrap
内:
// 定义 exports 对象导出的属性。
__webpack_require__.d = function (exports, name, getter) {
// 如果 exports (不含原型链上)没有 [name] 属性,定义该属性的 getter。
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
});
}
};
// 包装 Object.prototype.hasOwnProperty 函数。
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
可见
__webpack_require__
.
d
其实就是
Object
.
defineProperty
的简单包装.
引用工具模块导出的变量后,入口模块再执行它剩余的部分。至此,Webpack 基本的模块执行过程就结束了。
好了,我们用流程图总结一下 Webpack 模块的加载思路:
异步加载
有上面的打包我们发现将不同的打包进一个
main
.
js
文件。
main
.
js
会集中消耗太多网络资源,导致用户需要等待很久才可以开始与网页交互。
一般的解决方式是:根据需求降低首次加载文件的体积,在需要时(如切换前端路由器,交互事件回调)异步加载其他文件并使用其中的模块。
Webpack 推荐用 ES import() 规范来异步加载模块,我们根据 ES 规范修改一下入口模块的 import 方式,让其能够异步加载模块: