原文首发于 blog.flqin.com。若有错误,请联系笔者。分析码字不易,转载请代表出处,谢谢!node
回到 seal
。执行:webpack
this.summarizeDependencies();
复制代码
获得 this.fileDependencies, this.contextDependencies, this.missingDependencies
后,触发了一系列处理资源,优化资源的钩子以后,回到 Compiler.js
的 compile
里的 compilation.seal
回调,执行:web
this.hooks.afterCompile.callAsync(compilation, err => {
//...
return callback(null, compilation);
});
复制代码
该钩子会触发插件 CachePlugin
相关的事件,给 compiler
的属性 _lastCompilationFileDependencies,_lastCompilationContextDependencies
分别赋值 fileDependencies,contextDependencies
。缓存
而后执行回调即 onCompiled
,方法里执行:async
this.emitAssets(compilation, err => {
//...
});
复制代码
进入 this.emitAssets
,emitAssets
负责的是构建资源输出的过程。在方法里触发了 Compiler.hooks
:emit
,在回调里执行:函数
//...
outputPath = compilation.getPath(this.outputPath); // 获取资源输出的路径
this.outputFileSystem.mkdirp(outputPath, emitFiles); // 递归建立输出目录并输出资源
复制代码
outputPath
为配置里的 output.path
,而后调用 mkdirp
建立文件夹。性能
建立目标文件夹后,执行回调 emitFiles
,在回调里经过 asyncLib.forEachLimit
并行执行对每一个 file
资源文件进行路径拼接后,将每一个 source
源码转换为 buffer
后(性能提高),写入真实路径的 file
:优化
asyncLib.forEachLimit(
compilation.getAssets(),
15,
({ name: file, source }, callback) => {
//...
const writeOut = err => {
//...
const targetPath = this.outputFileSystem.join(outputPath, targetFile); // 路径拼接,获得真实路径
if (this.options.output.futureEmitAssets) {
//...判断重写入 及 gc释放内存(this.assets相关重写SizeOnlySource)
} else {
//...
let content = source.source(); //source为 CachedSource 实例,content为获得的资源
if (!Buffer.isBuffer(content)) {
content = Buffer.from(content, 'utf8'); //buffer转换,在node中提高性能
}
//...写入文件
this.outputFileSystem.writeFile(targetPath, content, err => {
//...
this.hooks.assetEmitted.callAsync(file, content, callback);
});
}
};
// 若目标文件路径包含/或\,先建立文件夹再写入
if (targetFile.match(/\/|\\/)) {
const dir = path.dirname(targetFile);
this.outputFileSystem.mkdirp(this.outputFileSystem.join(outputPath, dir), writeOut);
} else {
writeOut();
}
},
// 遍历完成的回调函数
err => {
//...回调
}
);
复制代码
其中:ui
let content = source.source();
复制代码
source
为 CachedSource
实例,source.source
作了缓存判断,执行 this._source.source
, this._source
为 ConcatSource
实例,该方法会遍历 children
,若是子项不是字符串,则执行其 source
方法。this
对于 ReplaceSource
实例来讲,会执行其 _replaceString
方法,该方法里会循环处理替换在以前 资源的构建 -> 生成 chunk 资源 -> chunkTemplate -> 生成主体 chunk 代码 -> 生成每一个 module 代码
push
进去的 replacements
,获得替换后的字符串,合并返回 resultStr
。
全部文件都建立写入完成后而后执行回调:
this.hooks.afterEmit.callAsync(compilation, err => {
if (err) return callback(err);
return callback();
});
复制代码
在回调里触发 Compiler.afterEmit
:hooks
,在回调里执行 callback
即 this.emitAssets
的回调,即执行:
//...
this.emitRecords(err => {
//...
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
});
复制代码
执行 this.emitRecords
,而后在其回调里设置相关 stats
,而后在 Compiler.done
:hooks
的回调里执行 finalCallback
,即执行文件 webpack-cli/bin/cli.js
里的 compiler.run
的回调,即 compilerCallback
。
方法里清除缓存以后,执行:
const statsString = stats.toString(outputOptions);
const delimiter = outputOptions.buildDelimiter ? `${outputOptions.buildDelimiter}\n` : '';
if (statsString) stdout.write(`${statsString}\n${delimiter}`);
复制代码
在 cli
里打印出构建相关的信息。至此,构建所有结束,下一章分析打包后的文件!
source
中的 ReplaceSource
实例中的 replacements
,将其替换为真实字符串;stats
并打印构建信息。