webpack 模块加载原理

Published: · LastMod: July 29, 2022 · 1264 words

webpack模块加载原理 🔗

webpack是前端开发中一个非常重要的工具,总体来说就是一个模块打包器,可以把项目中所用到的资源打包成一个或多个bundle

项目 🔗

新建项目

1
2
3
4
5
6
7
8
// webpack.config.js
module.exports = {
  mode: "development",
  entry: "./index.js",
  output: {
    filename: "bundle.js",
  },
};

入口文件

1
2
3
4
5
6
//index.js
import { bar, foo } from "./foo";

foo();

bar();

依赖文件

1
2
3
4
5
6
7
8
// foo.js
export const foo = () => {
  console.log("say foo");
};

export const bar = async () => {
  await Promise.resolve(123);
};

bundle结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 删减后的结果
 (function() { // webpackBootstrap
 	"use strict";
 	var __webpack_modules__ = ({
        "./foo.js": (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
            eval("");
        }),
        "./index.js": (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
            eval("");
        })

 	});

 	// 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 */
 	!function() {
 		// define getter functions for harmony exports
 		__webpack_require__.d = function(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 */
 	!function() {
 		__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
 	}();
 	
 	/* webpack/runtime/make namespace object */
 	!function() {
 		// 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 });
 		};
 	}();
 	
 	// startup
 	var __webpack_exports__ = __webpack_require__("./index.js");
 })();

首先看一下整体结构, 是一个自执行函数

1
2
3
(function(){
  // ...code
})()

我们来分析一下最终bundle出来的结果

  • 第一步,定义了一个__webpack_modules__对象,对象的key就是所价值的文件名,value是一个函数,内部用eval方法执行
  • __webpack_module_cache__内部定义一个模块的cache,看注释是模块缓存
  • 定义了一个方法__webpack_require__,以及函数上对应的多个其他方法
  • 执行 __webpack_require__("./index.js")入口方法

可以看出,执行了最后的入口方法,其实就是执行了最终index.jseval的字符串

__webpack_require__ 🔗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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_require__其实就做了一件事

  • 从缓存中寻找相关记录,如果有记录,就返回缓存
  • 没有缓存,则定义相关缓存
  • __webpack_modules__执行相关代码,并且把结果赋值给缓存,并返回结果

其他几个方法 🔗

__webpack_require__.d 🔗

在对象上定义相关方法,Object.defineProperty, 并且定义get方法

__webpack_require__.o 🔗

Object.hasOwnProperty方法的重新写,判断对象上是否含有属性

__webpack_require__.r 🔗

在对象上定义一个__esModule属性, 并把值设为true

eval内的值 🔗

index.js

1
"__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _foo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./foo */ \"./foo.js\");\n\n\n(0,_foo__WEBPACK_IMPORTED_MODULE_0__.foo)();\n\n(0,_foo__WEBPACK_IMPORTED_MODULE_0__.bar)();\n\n\n//# sourceURL=webpack://app2/./index.js?"

格式化后

1
2
3
4
5
6
__webpack_require__.r(__webpack_exports__);
/* harmony import */ 
var _foo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./foo */ "./foo.js");
(0,_foo__WEBPACK_IMPORTED_MODULE_0__.foo)();
(0,_foo__WEBPACK_IMPORTED_MODULE_0__.bar)();
//# sourceURL=webpack://app2/./index.js?
  • 先给模块定义__esModule属性
  • 引用foo.js模块

foo.js格式化后

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
  /* harmony export */
  bar: function () {
    return /* binding */ bar;
  },
  /* harmony export */
  foo: function () {
    return /* binding */ foo;
  },
  /* harmony export */
});
const foo = () => {
  console.log("say foo");
};
const bar = async () => {
  await Promise.resolve(123);
};
//# sourceURL=webpack://app2/./foo.js?

原理和上面类似