在繼續(xù)看上節(jié)里react-script里的代碼:
const webpack = require('webpack');
function build(previousFileSizes) {
let compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
// ....
})
})
}
得到compiler之后,我們就開始正式編譯了亲桥,這個(gè)時(shí)候就是調(diào)用的compiler.run()
洛心。
之前說webpack就是定義了一系列勾子和調(diào)用一系列勾子,這里其實(shí)就是調(diào)用一系列勾子:
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
這里先調(diào)用了beforeRun勾子然后回調(diào)函數(shù)中調(diào)用run勾子两曼,接著是readRecords勾子皂甘,最終才調(diào)用了compile方法,并且傳入了一個(gè)回調(diào)函數(shù)onCompiled
:
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
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);
});
return;
}
this.emitAssets(compilation, err => {
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err => {
if (err) return finalCallback(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);
});
});
});
};
這里的流程也比較能理解悼凑,這里其實(shí)就是完成編譯之后需要做的事情偿枕,一般就是兩件事:
- 輸出打包后的資源;
- console出打包后的資源信息:大小耗時(shí)等户辫;
但是他是先調(diào)用了一個(gè)勾子shouldEmit渐夸,明確的返回了false后就不再輸出資源了。不然就是調(diào)用this.emitAssets()
輸出資源渔欢。
然后在emitAssets中的回調(diào)里又做了判斷墓塌,調(diào)用了一個(gè)勾子needAdditionalPass,如果為true就繼續(xù)調(diào)用additionalPass并且再次編譯奥额。否則繼續(xù)調(diào)用this.emitRecords()
輸出打包記錄苫幢,再最終的回調(diào)里再調(diào)用done的勾子,然后再finalCallback垫挨,也是我們最開始傳入的compiler.run
的callback韩肝。
這里是把回調(diào)講完了,但其實(shí)這是一層一層的調(diào)用九榔,先是this.compile()
回調(diào)里再 this.emitAssets()
回調(diào)里再 this.emitRecords()
哀峻。我們一個(gè)函數(shù)一個(gè)函數(shù)看。
emitRecords
emitRecords(callback) {
if (!this.recordsOutputPath) return callback();
const idx1 = this.recordsOutputPath.lastIndexOf("/");
const idx2 = this.recordsOutputPath.lastIndexOf("\\");
let recordsOutputPathDirectory = null;
if (idx1 > idx2) {
recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx1);
} else if (idx1 < idx2) {
recordsOutputPathDirectory = this.recordsOutputPath.substr(0, idx2);
}
const writeFile = () => {
this.outputFileSystem.writeFile(
this.recordsOutputPath,
JSON.stringify(this.records, undefined, 2),
callback
);
};
if (!recordsOutputPathDirectory) {
return writeFile();
}
this.outputFileSystem.mkdirp(recordsOutputPathDirectory, err => {
if (err) return callback(err);
writeFile();
});
}
沒啥好說的哲泊,更具系統(tǒng)拿到recordsOutput的相對(duì)路徑剩蟀,然后寫入文件。
emitAssets
源碼太長(zhǎng)了切威,這里其實(shí)拿到了conpiler編輯后得到的compilation育特,而compilation的assets就代表輸出的靜態(tài)資源對(duì)象數(shù)組,引用了一個(gè)庫const asyncLib = require("neo-async");
并行輸出資源先朦,其中如果配置了output.futureEmitAssets會(huì)啟用一個(gè)新的資源輸出邏輯且预,暫時(shí)還不是很明白這里的作用,輸出的具體內(nèi)容是調(diào)用了compilation.assets里對(duì)象的source方法得到content烙无,然后調(diào)用之前講的webpack的文件輸出系統(tǒng)輸出锋谐。
compile
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
compilation.finish(err => {
if (err) return callback(err);
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
首先調(diào)用了this.newCompilationParams這個(gè)方法,他的主要作用是創(chuàng)建了兩個(gè)工廠對(duì)象NormalModuleFactory截酷、ContextModuleFactory涮拗,然后并且把這倆對(duì)象作為參數(shù)傳入給了this.newCompilation
里,然后最好調(diào)用了一系列勾子make勾子,這個(gè)勾子實(shí)際上就是真正編譯的開始三热。
目前就閱讀到了這個(gè)地方鼓择,其實(shí)很多細(xì)節(jié)沒有講到,比如初始化NormalModuleFactory就漾、ContextModuleFactory呐能,還有Stats這個(gè)對(duì)象是怎么利用compilation生成信息的,其實(shí)都需要等看完了compilation是如何生成才能明白抑堡。 之前我們說過make是真正編譯的開始摆出,其實(shí)這個(gè)時(shí)候就改看哪些地方調(diào)用了make這個(gè)勾子做了什么操作,然后接下來才能順理成章首妖。