本節流程如圖: 現在正式進入打包流程,起步方法為run: 為什麼不介紹compiler對象?因為構造函數中並沒有一個初始化的方法,只是普通的變數聲明,沒啥好講的。 在run方法中,首先是調用了tapable的applyPluginsAsync執行了before-run事件流,該事件流的定義地點如下: ...
本節流程如圖:
現在正式進入打包流程,起步方法為run:
Compiler.prototype.run = (callback) => { const startTime = Date.now(); const onCompiled = (err, compilation) => { /**/ }; this.applyPluginsAsync("before-run", this, err => { if (err) return callback(err); this.applyPluginsAsync("run", this, err => { if (err) return callback(err); this.readRecords(err => { if (err) return callback(err); this.compile(onCompiled); }); }); }); }
為什麼不介紹compiler對象?因為構造函數中並沒有一個初始化的方法,只是普通的變數聲明,沒啥好講的。
在run方法中,首先是調用了tapable的applyPluginsAsync執行了before-run事件流,該事件流的定義地點如下:
// NodeEnvironmentPlugin compiler.plugin("before-run", (compiler, callback) => { if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge(); callback(); });
在對compiler對象的文件系統方法的掛載插件中,註入了before-run這個事件流,這裡首先看一下applyPluginsAsync(做了小幅度的修改以適應webpack源碼):
// tapable Tapable.prototype.applyPluginsAsync = (name, ...args, callback) => { var plugins = this._plugins[name]; if (!plugins || plugins.length === 0) return callback(); var i = 0; var _this = this; // args為[args,next函數] args.push(copyProperties(callback, function next(err) { // 事件流出錯或者全部執行完後調用回調函數 if (err) return callback(err); i++; if (i >= plugins.length) { return callback(); } // 執行下一個事件 plugins[i].apply(_this, args); })); // 執行第一個事件 plugins[0].apply(this, args); };
當時在第八節沒有講這個系列的事件流觸發方式,這裡簡單說下:
1、copyProperties用於對象屬性的拷貝,類似於Object.assign,然而在這裡傳入的是兩個函數,一點用都沒有!!!!!(當時沒寫講解就是因為一直卡在這個對象拷貝方法在這裡有什麼毛用)
2、在webpack中,args為一個this,指向compiler的上下文
3、註入該事件流的事件必須要執行callback方法(如上例),此時執行的並不是外部的callback,而是next函數
4、有兩種情況下會執行外部callback,中途出錯或者所有事件流執行完畢
這樣就很明白了,註入before-run中的函數形參的意義如下:
// before-run // compiler => this // callback => next (compiler, callback) => { if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge(); callback(); }
由於before-run中只有一個事件,所以在調用內部callback的next方法後,會由於i大於事件長度而直接調用外部callback。
這裡的purge方法之前見過,這裡複習下內容:
// NodeEnvironmentPlugin compiler.inputFileSystem = new CachedInputFileSystem(new NodeJsInputFileSystem(), 60000); // CachedInputFileSystem CachedInputFileSystem.prototype.purge = function(what) { this._statStorage.purge(what); this._readdirStorage.purge(what); this._readFileStorage.purge(what); this._readlinkStorage.purge(what); this._readJsonStorage.purge(what); }; // CachedInputFileSystem => Storage Storage.prototype.purge = function(what) { if (!what) { this.count = 0; clearInterval(this.interval); this.nextTick = null; this.data.clear(); this.levels.forEach(function(level) { level.clear(); }); } else if (typeof what === "string") { /**/ } else { /**/ } };
一句話概括就是:清除所有打包中緩存的數據。
由於假設是第一次,所以這裡並沒有什麼實際操作,接著調用外部callback,用同樣的方式觸發了run事件流。
run事件流也只有一個方法,來源於CachePlugin插件:
Compiler.plugin("run", (compiler, callback) => { // 這個屬性我暫時也不知道是啥 反正直接callback了 if (!compiler._lastCompilationFileDependencies) return callback(); const fs = compiler.inputFileSystem; const fileTs = compiler.fileTimestamps = {}; asyncLib.forEach(compiler._lastCompilationFileDependencies, (file, callback) => { // ... }, err => { // ... }); });
在第一次觸發run事件流時,那個屬性是undefined,所以會直接跳過,因為我是邊看源碼邊解析,所以也不知道是啥,哈哈。
接下來下一個callback是這個:
this.readRecords(err => { if (err) return callback(err); this.compile(onCompiled); });
這是另一個原型方法,源碼如下:
Compiler.prototype.readRecords = (callback) => { // 這個屬性也沒有 if (!this.recordsInputPath) { this.records = {}; return callback(); } this.inputFileSystem.stat(this.recordsInputPath, err => { // ... }); }
這裡第一次也會跳過並直接callback,看源碼大概是傳入一個路徑並讀取裡面的文件信息緩存到records中。
這下連跳兩步,直接進入原型方法compile中,預覽一下這個函數:
Compiler.prototype.compile = (callback) => { const params = this.newCompilationParams(); // 依次觸發事件流 this.applyPluginsAsync("before-compile", params, err => { if (err) return callback(err); this.applyPlugins("compile", params); const compilation = this.newCompilation(params); this.applyPluginsParallel("make", compilation, err => { if (err) return callback(err); compilation.finish(); compilation.seal(err => { if (err) return callback(err); this.applyPluginsAsync("after-compile", compilation, err => { if (err) return callback(err); return callback(null, compilation); }); }); }); }); }
編譯打包的核心流程已經一覽無遺,方法中依次觸發了before-compile、compile、make、after-compile事件流,最後調用了回調函數。
從下一節開始詳細講解每一步的流程(不懂的地方肯定會跳過啦)。