开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 5 天,点击查看活动详情
文章内容:用 webpack 打包两个模块(通过 import 语法导入模块),分析打包后的代码,也就是 webpack 运行时代码。
代码准备
index.js
// const sum = require('./sum')
import sum, { test } from './sum';
import * as s from './sum';
console.log(sum(6, 9));
console.log("test", test);
console.log("s", s)
sum.js
const sum = (a, b) => {
return a + b;
}
export default sum;
export const test = 'test';
webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'build')
},
mode: 'none'
}
在控制台执行
npx webpack
输出 build/main.js 文件
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
/* harmony export */ "test": () => (/* binding */ test)
/* harmony export */ });
const sum = (a, b) => {
return a + b;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);
const test = 'test';
/***/ })
/******/ ]);
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log((0,_sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
console.log("s", _sum__WEBPACK_IMPORTED_MODULE_0__)
})();
/******/ })()
;
然后就可以直接断点调试 build/main.js 啦~
运行时代码分析
首先与不包含 ESM 的产物代码(这里用的是《分析一个极简的 Webpack 运行时代码》文章中的产物代码)做对比
标红的部分是 webpack 为处理 ESM 模块所增加的处理逻辑
分析变量与函数
__webpack_modules__
: 是一个数组,存放所有加载到的模块。__webpack_module_cache__
: 是一个对象,对模块执行结果进行缓存,这样能够保证每个模块只被执行一次;__webpack_require__
: 是一个函数,作用是加载模块,如果模块第一次被加载,则通过__webpack_modules__[moduleId]
匹配上对应的模块,并进行缓存;如果是已加载的模块,则直接从__webpack_module_cache__[moduleId]
取;__webpack_require__.d
: 定义 getter 方法__webpack_require__.o
:Object.prototype.hasOwnProperty
__webpack_require__.r
: 注入 ESM 标注属性__webpack_exports__
: 是一个对象,存放导出的内容;
__webpack_require__.r
注入 ESM 标注属性
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
代码解析:
- 在函数体内部,首先通过检查环境中是否存在
Symbol
并且Symbol.toStringTag
是否可用,来判断是否可以使用toStringTag
属性。这一部分代码可以说是对环境的兼容性检测。 - 如果环境中存在
Symbol
并且Symbol.toStringTag
可用,那么就会调用Object.defineProperty
方法,在exports
对象上定义Symbol.toStringTag
属性,属性值为'Module'
。这样做可以使得该模块在被打印或转换为字符串时能够显示为'[object Module]'
。 - 不管上述条件是否成立,都会调用
Object.defineProperty
方法,在exports
对象上定义__esModule
属性,属性值为true
。这个属性是为了表示该模块是一个 ES6 模块,并且在其他模块引入时可以进行相应的处理。
Symbol.toStringTag
Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。
Object.prototype.toString.call(exports) // '[object Module]'
__webpack_require__.d
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
//
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
- 定义
getter
方法:遍历definition
的key
,如果definition
有这个ke
y 而exports
没有,则在exports
的属性key
上挂载definition[key]
这个getter
方法; getter
方法的目的是:在访问某个导出特性的时候才去计算对应的值;- 这里的
definition
是传入的对象:
{
"default": () => (__WEBPACK_DEFAULT_EXPORT__),
"test": () => (/* binding */ test)
}
sum.js 源码与 webpack 运行时代码做对比分析
sum.js 源码
const sum = (a, b) => {
return a + b;
}
export default sum;
export const test = 'test';
webpack 运行时代码中的 sum 模块
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__),
/* harmony export */ "test": () => (/* binding */ test)
/* harmony export */ });
const sum = (a, b) => {
return a + b;
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (sum);
const test = 'test';
/***/ })
/******/ ])
通过 __webpack_require__.r
标记 __webpack_exports__
为 ESM 模块,然后通过 __webpack_require__.d
对 __webpack_exports__
的 default
、test
导出特性定义 getter
方法。
对整个过程进行分析
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
console.log((0,_sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
console.log("s", _sum__WEBPACK_IMPORTED_MODULE_0__)
})();
- 加载入口模块 index.js;
__webpack_require__.r(__webpack_exports__);
- 将 index.js 标记为 ESM
var _sum__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
-
加载 sum 模块
- 如果缓存中存在,则在缓存中取
- 将 sum 模块标记为 ESM
- 为 sum 模块的导出特性定义 getter 方法
-
将结果赋值给
_sum__WEBPACK_IMPORTED_MODULE_0__
console.log((0,_sum__WEBPACK_IMPORTED_MODULE_0__["default"])(6, 9));
console.log("test", _sum__WEBPACK_IMPORTED_MODULE_0__.test);
- 调用
default
对应的getter
方法 - 调用
test
对应的getter
方法
小结
本文主要对 webpack 打包含有 ESM 模块的运行时代码进行了分析,主要做了如下一些事情:
-
用 webpack 提供的模块加载函数加载入口模块;
-
加载入口模块所依赖的模块;
- 如果缓存中存在,则在缓存中取
- 将 import 导入的模块标记为 ESM(
__esModule
) - 为模块导出特性定义 getter 方法
-
运行模块中的代码。