webpack源码分析-编译的seal和emit阶段

webpack的编译分几个阶段进行,分别为创建对象阶段run、解析获取module阶段make、module分包阶段seal、输出打包后文件阶段emit

seal阶段

seal阶段主要对modules做分拣、拆包、封装。

查看文件/node_modules/webpack/lib/Compiler.js第675行:

1
2
3
4
5
6
7
8
9
10
compile(callback) {
// make 阶段开始
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
// make 阶段结束
compilation.finish(err => {
if (err) return callback(err);
// seal 阶段开始
compilation.seal(err => {
...

分包优化

查看文件/node_modules/webpack/lib/Compilation.js中第1283行seal方法

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
seal(callback) {
// 开始进行分包优化操作
this.hooks.optimize.call();
// 一系列的分包优化操作...
while (
this.hooks.optimizeModulesBasic.call(this.modules) ||
this.hooks.optimizeModules.call(this.modules) ||
this.hooks.optimizeModulesAdvanced.call(this.modules)
) {
/* empty */
}
this.hooks.afterOptimizeModules.call(this.modules);

while (
this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
) {
/* empty */
}
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);

this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
if (err) {
return callback(err);
}

this.hooks.afterOptimizeTree.call(this.chunks, this.modules);

while (
this.hooks.optimizeChunkModulesBasic.call(this.chunks, this.modules) ||
this.hooks.optimizeChunkModules.call(this.chunks, this.modules) ||
this.hooks.optimizeChunkModulesAdvanced.call(this.chunks, this.modules)
) {
/* empty */
}
this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);

const shouldRecord = this.hooks.shouldRecord.call() !== false;

this.hooks.reviveModules.call(this.modules, this.records);
this.hooks.optimizeModuleOrder.call(this.modules);
this.hooks.advancedOptimizeModuleOrder.call(this.modules);
this.hooks.beforeModuleIds.call(this.modules);
this.hooks.moduleIds.call(this.modules);
this.applyModuleIds();
this.hooks.optimizeModuleIds.call(this.modules);
this.hooks.afterOptimizeModuleIds.call(this.modules);

this.sortItemsWithModuleIds();

this.hooks.reviveChunks.call(this.chunks, this.records);
this.hooks.optimizeChunkOrder.call(this.chunks);
this.hooks.beforeChunkIds.call(this.chunks);
this.applyChunkIds();
this.hooks.optimizeChunkIds.call(this.chunks);
this.hooks.afterOptimizeChunkIds.call(this.chunks);
...
}

compilation调用createModuleAssets方法

webpack调用createModuleAssets方法来提取资产文件最后输出到dist目录中,如css中引入的图片等,css-loader会将图片当成一个个module,用url-loaderfile-loader提取出来当成css模块中资产文件输出到dist

compilation调用createChunkAssets方法

查看文件/node_modules/webpack/lib/Compilation.js中第1395行seal方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
seal(callback) {
// 分包优化操作
...
// 开始分拣 module 中的资产文件
this.createModuleAssets();
...
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
// 开始分拣当前 chunk 中的资产文件
this.createChunkAssets();
...
});
});
});
}

方法createChunkAssets用来分拣dist/main.js中的资产文件

判断Chunk是否包含runtime代码

查看文件/node_modules/webpack/lib/Compilation.js中第2111行createChunkAssets方法

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
createChunkAssets() {
// 遍历所有的 chunk
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
chunk.files = [];
let source;
let file;
let filenameTemplate;
try {
// 判断 chunk 是否包含 runtime 代码并获取 chunk 模版
const template = chunk.hasRuntime()
? this.mainTemplate
: this.chunkTemplate;
// 调用 chunk 模版的 getRenderManifest 方法获取当前 chunk 的所有资产文件
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
}); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
// 遍历所有的资产清单
for (const fileManifest of manifest) {
...
// 开始执行渲染,获取最后能被浏览器识别的代码
source = fileManifest.render();
...
// 开始提交最终打包完毕的资产信息
this.emitAsset(file, source, assetInfo);
...
}
}
}
}

webpack判断chunk是否是runtime,即webpack启动代码(最终能被浏览器识别的importrequireexport,默认为webpackjsonp)。

如果包含runtime则用mainTemplate生成最终代码,不包含则用chunkTemplate生成。

二者区别仅为是否包含runtime代码

至此,seal阶段结束。

emit阶段

emit阶段根据配置输出文件

seal阶段结束,代码回到/node_modules/webpack/lib/Compiler.js660行run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
compile(callback) {
...
// seal 阶段开始
compilation.seal(err => {
if (err) return callback(err);
// seal 阶段结束
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
// 回到 run方法
return callback(null, compilation);
});
});
...
}
//run方法
run(callback) {const onCompiled = (err, compilation) => {
// seal 结束后回到了 run 方法,并准备执行 emitAssets
this.emitAssets(compilation, err => {
...
});
};
}

compiler.emitAssets输出打包过后文件

查看文件/node_modules/webpack/lib/Compiler.jsemitAssets方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
emitAssets(compilation, callback) {
let outputPath; // 输出目录地址
const emitFiles = err => {
// 遍历所有的资产文件
asyncLib.forEachLimit(
compilation.getAssets(),
15,
({ name: file, source }, callback) => {
let targetFile = file;
...
// 使用 webpack 的文件输出系统开始写文件到 outputPath
this.outputFileSystem.writeFile(targetPath, content, err => {
if (err) return callback(err);
this.hooks.assetEmitted.callAsync(file, content, callback);
});
...
}

至此,emit阶段结束

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: