网上的 webpack 分析系列文章已经很多了,但从自己理解的角度出发,进行记录和梳理,是一个知识重新构建的过程,更加有利于消化吸收。此次把 webpack 当作一个系列来记录,希望从整体的体系出发,加深对 webpack 的理解。
首先从 webpack 打包生成的文件进行简单分析。
1 | // b.js |
1 | // a.js |
1 | // index.js |
执行 npm run build
(webpack –mode development) 生成 main.js 如下
1 | (function(modules) { // webpackBootstrap |
可以看到,打包出来的是一个立即执行函数,参数是一个 key-value 的对象,key 是引入文件的路径,value 是对应的文件生成的模块化函数,立即执行函数返回一个从入口文件开始执行的__webpack_require__
函数,关键就是__webpack_require__
函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 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;
}
__webpack_require__
根据 moduleId 来执行每一个 module 函数,当 installedModules 缓存中有该模块的对象,也就是该模块已经加载过,则从缓存中去取该对象的 exports,否则创建一个新的 module 对象执行 module 函数,module 对象有三个属性,i 表示 moduleId , l 表示模块是否已经加载过,exports 表示 module 的导出内容。通过 call 方法调用 module 的执行函数,执行函数的 this 指向 module.exports,后面三个是传入该函数的参数,__webpack_require__
函数最后返回 module.exports。
回到最开始的立即执行函数,执行__webpack_require__(__webpack_require__.s = "./src/index.js")
,对应的入口文件的 module 函数如下。1
2
3
4
5
6
7
8
9
10
11/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
;
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\nconsole.log(_a__WEBPACK_IMPORTED_MODULE_0__[\"A\"]);\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ })
把无关的注释都删掉,取出 eval 里的代码(eval 函数可执行其中的的 JavaScript 代码)
1 | "./src/index.js": |
1 | "./src/a.js": |
1 | "./src/b.js": |
这样拆开来分析就比较一目了然了,从这三个文件可以看出,在执行模块时除了执行本身模块内容外,还会执行__webpack_require__.r
,__webpack_require__.d
等函数,从打包生成的 main.js 的立即执行函数中找这几个函数的定义
1 | // define __esModule on exports |
__webpack_require__.r
函数是 webpack 针对不同模块加了不同的标记处理,因为是 import 引入的,给 exports 对象加上ES harmony规范的标记。
执行__webpack_require__.d(__webpack_exports__, "A", function() { return A; });
实际上就是生成__webpack_exports__.A = A;
从入口的 index.js 分析,首先对 index.js 进行了 esModule 的模块化标记,因为 index.js 引入了 a.js,接着对 a 模块执行__webpack_require__
, 打印出 a 模块的执行结果。a 模块同样进行了 esModule 标记,并且生成了 module.exports.A = A
,将 A 变量导出,index.js 打印出 A 的结果。因为 b.js 使用的是export default
,webpack 处理后,会在 module.exports 中增加一个 default 属性。
至此,我们看到,webpack 的输出文件,将各个模块以参数的形式传递给 IIFE 函数,从入口文件开始递归解析依赖,在解析的过程中,分别对不同的模块进行处理,返回模块的exports。