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
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
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__
做了以下几件事:
- 查找缓存中是否已存在该模块,存在则直接返回缓存中的模块信息
- 为该模块创建一个新的模块对象module,并添加一个exports属性
- 执行
__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
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__
中定义,而是直接放在了一个自执行函数中立即执行。这样在文件加载的时候就可以立即执行入口文件。