細說webpack源碼之compile流程-入口函數run
Webpack是目前基于React和Redux開發(fā)的應用的主要打包工具。我想使用Angular 2或其他框架開發(fā)的應用也有很多在使用Webpack。
本節(jié)流程如圖:

現在正式進入打包流程,起步方法為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執(zhí)行了before-run事件流,該事件流的定義地點如下:
// NodeEnvironmentPlugin
compiler.plugin("before-run", (compiler, callback) => {
if (compiler.inputFileSystem === inputFileSystem)
inputFileSystem.purge();
callback();
});
在對compiler對象的文件系統(tǒng)方法的掛載插件中,注入了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) {
// 事件流出錯或者全部執(zhí)行完后調用回調函數
if (err) return callback(err);
i++;
if (i >= plugins.length) {
return callback();
}
// 執(zhí)行下一個事件
plugins[i].apply(_this, args);
}));
// 執(zhí)行第一個事件
plugins[0].apply(this, args);
};
當時在第八節(jié)沒有講這個系列的事件流觸發(fā)方式,這里簡單說下:
1、copyProperties用于對象屬性的拷貝,類似于Object.assign,然而在這里傳入的是兩個函數,一點用都沒有?。。。。?當時沒寫講解就是因為一直卡在這個對象拷貝方法在這里有什么毛用)
2、在webpack中,args為一個this,指向compiler的上下文
3、注入該事件流的事件必須要執(zhí)行callback方法(如上例),此時執(zhí)行的并不是外部的callback,而是next函數
4、有兩種情況下會執(zhí)行外部callback,中途出錯或者所有事件流執(zhí)行完畢
這樣就很明白了,注入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,用同樣的方式觸發(fā)了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 => {
// ...
});
});
在第一次觸發(fā)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();
// 依次觸發(fā)事件流
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);
});
});
});
});
}
編譯打包的核心流程已經一覽無遺,方法中依次觸發(fā)了before-compile、compile、make、after-compile事件流,最后調用了回調函數。
總結
以上所述是小編給大家介紹的webpack源碼之compile流程-入口函數run,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
相關文章
JavaScript中Number對象的toFixed() 方法詳解
下面小編就為大家?guī)硪黄狫avaScript中Number對象的toFixed() 方法詳解。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09
JavaScript數組方法之findIndex()的用法詳解
findIndex()方法是一個非常實用的數組方法,可以幫助我們快速查找符合某個條件的元素,本文給大家介紹JavaScript數組方法之findIndex()的用法,感謝的朋友跟隨小編一起看看吧2023-10-10

