原文首發於 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
並打印構建信息。