webpack 分析系列-打包后 js 文件分析

网上的 webpack 分析系列文章已经很多了,但从自己理解的角度出发,进行记录和梳理,是一个知识重新构建的过程,更加有利于消化吸收。此次把 webpack 当作一个系列来记录,希望从整体的体系出发,加深对 webpack 的理解。
首先从 webpack 打包生成的文件进行简单分析。

本文代码地址

1
2
// b.js
export default B = 'b';
1
2
3
4
// a.js
import B from './b';
console.log(B);
export const A = 'a';
1
2
3
// index.js
import { A } from './a';
console.log(A);

执行 npm run build(webpack –mode development) 生成 main.js 如下

1
2
3
4
5
6
7
8
9
10
11
12
(function(modules// webpackBootstrap
// ...

   // Load entry module and return exports
   return __webpack_require__(__webpack_require__.s = "./src/index.js");
})(
{
"./src/a.js": (function(module, __webpack_exports__, __webpack_require__) {},
"./src/b.js": (function(module, __webpack_exports__, __webpack_require__) {},
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {}
}
)

可以看到,打包出来的是一个立即执行函数,参数是一个 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__) {

"use strict";
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
2
3
4
5
6
7
8
9
"./src/index.js":
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/a.js");
console.log(_a__WEBPACK_IMPORTED_MODULE_0__["A"]);

})
1
2
3
4
5
6
7
8
9
10
"./src/a.js":
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "A", function() { return A; });
var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/b.js");
console.log(_b__WEBPACK_IMPORTED_MODULE_0__["default"]);
const A = 'a';
}),
1
2
3
4
5
6
"./src/b.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (B = 'b');
}),

这样拆开来分析就比较一目了然了,从这三个文件可以看出,在执行模块时除了执行本身模块内容外,还会执行__webpack_require__.r,__webpack_require__.d等函数,从打包生成的 main.js 的立即执行函数中找这几个函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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 });
};
// 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 });
}
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

__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。