webapck 打包原理

通常我们会讲所有文件打包成一个bundle文件,看一个简单的例子:

// index.js
import {a1} from './util/tool1';
import {a2} from './util/tool2';

a1();
a2();

// util/tool1.js
export function a1 () {
    console.log('a1');
}

// util/tool2.js
export function a2 () {
    console.log('a2');
}

// webpack.config.js
import path from 'path';
import webpack from 'webpack';

const config: webpack.Configuration = {
    mode: 'development',
    entry: './src/index.js',
    devtool: 'inline-source-map',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    }
};

export default config;
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

执行打包后结果如下:

(() => {
  "use strict";
  
  // 模块映射表,以模块路径为key,模块内容包装在一个函数中
  var __webpack_modules__ = ({

    "./src/util/tool1.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

      __webpack_require__.r(__webpack_exports__);
      
      // 模块中暴露出去的属性
      __webpack_require__.d(__webpack_exports__, {
        "a1": () => (/* binding */ a1)
      });
      function a1 () {
          console.log('a1');
      }
    }),

    "./src/util/tool2.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        "a2": () => (/* binding */ a2)
      });
      function a2 () {
          console.log('a2');
      }
    })

  });
  
  // 用于缓存模块
  var __webpack_module_cache__ = {};

	function __webpack_require__(moduleId) {
		// 如果缓存中有,则直接使用
		var cachedModule = __webpack_module_cache__[moduleId];
		if (cachedModule !== undefined) {
			return cachedModule.exports;
		}
		// 创建一个新的模块对象
		var module = __webpack_module_cache__[moduleId] = {
			// no module.id needed
			// no module.loaded needed
			exports: {}
		};
	
		// 执行对应模块的方法,初始化module.exports对象
		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
	
		// 返回模块的exports
		return module.exports;
	}
	
 	(() => {
 		// 将definition对象上的属性全都挂载到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] });
 				}
 			}
 		};
 	})();
 	
 	// 判断prop是否是obj上的非原型属性
 	(() => {
 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
 	})();
 	
 	// 为exports打上模块类型标记
 	(() => {
 		// 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__ = {};
  // 执行入口文件
  (() => {
    __webpack_require__.r(__webpack_exports__);
    var _util_tool1__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util/tool1 */ "./src/util/tool1.js");
    var _util_tool2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./util/tool2 */ "./src/util/tool2.js");

    (0,_util_tool1__WEBPACK_IMPORTED_MODULE_0__.a1)();
    (0,_util_tool2__WEBPACK_IMPORTED_MODULE_1__.a2)();
  })();
})();
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

可以看到,最终会生成一张模块映射表__webpack_modules__,以模块路径为key(即模块id),模块内容则通过一层函数包装。并且webpack自己定义了一个模块引用方法__webpack_require__,接收一个模块id,并且维护了一个缓存列表__webpack_module_cache__,每个模块只会初始化一次。__webpack_require__做了以下几件事:

  1. 查找缓存中是否已存在该模块,存在则直接返回缓存中的模块信息
  2. 为该模块创建一个新的模块对象module,并添加一个exports属性
  3. 执行__webpack_modules__中对应模块的函数,获取模块暴露的信息初始化module.exports

我们再来看下__webpack_modules__中存储的模块信息是怎样的:

"./src/util/tool1.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

      __webpack_require__.r(__webpack_exports__);
      
      // 模块中暴露出去的属性
      __webpack_require__.d(__webpack_exports__, {
        "a1": () => (/* binding */ a1)
      });
      function a1 () {
          console.log('a1');
      }
    }),
1
2
3
4
5
6
7
8
9
10
11
12

每个模块函数都会执行__webpack_require__.r__webpack_require__.d方法,这里就要再介绍下__webpack_require__的三个静态属性。

__webpack_require__除了作为普通函数调用外,还定义了三个静态方法:

  • d(exports, definition) => void:将definition对象上的属性全都挂载到exports上
  • o(obj, prop) => boolean:判断prop是否是obj上的非原型属性
  • r(exports) => void:为exports打上模块类型标记__esModule|Module

通过上面代码可以看出每个模块被__webpack_require__引用时都会为该模块打上模块类型标记,并将该模块export出去的属性全都赋值到对应的module.exports对象上。如果有多个同名属性,只会取第一个读到的,后面的将被忽略。

最后入口文件没有在__webpack_modules__中定义,而是直接放在了一个自执行函数中立即执行。这样在文件加载的时候就可以立即执行入口文件。