在前面的文章webpack不適合多頁面應(yīng)用?你寫的插件還不夠多中提到過述暂,webpack核心使用了Tapable實(shí)現(xiàn)事件的發(fā)布訂閱處理的插件架構(gòu)(Tapable中文文檔)畦韭,今天就具體來分析下webpack基于Tapable的插件架構(gòu)
找到代碼入口
- 想必你已經(jīng)使用過
npm install webpack
命令下載過webpack艺配,那么在你的node_modules目錄下找到webpack转唉。 - npm模塊的入口文件可以通過package.json中的
"main": "lib/webpack.js"
找到稳捆,當(dāng)你通過reqire引用模塊的時(shí)候,其實(shí)定位到的就是這個(gè)文件砖织。一般情況下侧纯,我們會(huì)在命令行直接使用webpack命令去執(zhí)行打包茂蚓,這個(gè)時(shí)候執(zhí)行的就是bin/webpack.js
了,這個(gè)命令只是在調(diào)用lib/webpack.js
之前處理一些命令行參數(shù)聋涨,殊途同歸。 - 打開lib/webpack.js牍白。webpack.js除了使用exportPlugins導(dǎo)出很多插件類(方便外部調(diào)用)脊凰,最重要的事情就是創(chuàng)建compiler對(duì)象(如果options是數(shù)組的話茂腥,每個(gè)元素創(chuàng)建一個(gè))
//創(chuàng)建compiler對(duì)象
compiler = new Compiler();
//后面這幾句代碼狸涌,得看了compiler再回來看看了
compiler.options = options;
compiler.options = new WebpackOptionsApply().process(options, compiler);
new NodeEnvironmentPlugin().apply(compiler);
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
這個(gè)時(shí)候我們的視線得轉(zhuǎn)移到compiler上了
植入Tapable
打開lib/Compiler.js
function Compiler() {
//傳入作用域最岗,調(diào)用Tapable的構(gòu)造函數(shù)
Tapable.call(this);
this.outputPath = "";
this.outputFileSystem = null;
this.inputFileSystem = null;
this.recordsInputPath = null;
this.recordsOutputPath = null;
this.records = {};
this.fileTimestamps = {};
this.contextTimestamps = {};
this.resolvers = {
normal: new Resolver(null),
loader: new Resolver(null),
context: new Resolver(null)
};
this.parser = new Parser();
this.options = {};
}
module.exports = Compiler;
//復(fù)制一份Tapable的原型
Compiler.prototype = Object.create(Tapable.prototype);
Compiler.prototype.constructor = Compiler;
如果你閱讀過Tapable中文文檔帕胆,你應(yīng)該對(duì)這個(gè)mix的方式不會(huì)陌生,tapable的原理其實(shí)也不復(fù)雜
聲明一個(gè)全局的變量
this._plugins = {}
懒豹,插件中使用plugin(name, fn)
方法給事件name
注冊(cè)處理方法fn
,多次注冊(cè)形成了事件name
的監(jiān)聽鏈,當(dāng)事件name
觸發(fā)的時(shí)候蝴乔,執(zhí)行這些處理方法片酝。處理方法的執(zhí)行順序和執(zhí)行方式依據(jù)事件name
的觸發(fā)方式的不同而不同
這個(gè)時(shí)候compiler已經(jīng)具備Tapable的所有屬性和方法了,我們?cè)倩氐絣ib/webpack.js來看看創(chuàng)建了Compiler對(duì)象后的幾行代碼
說實(shí)話不太欣賞這種在對(duì)象外面初始化的設(shè)計(jì)模式钠怯,讀代碼的時(shí)候你得跳來跳去,我更傾向于通過構(gòu)造函數(shù)傳入options鞠鲜,在對(duì)象內(nèi)進(jìn)行初始化工作宁脊。(僅代表個(gè)人的想法)
//給對(duì)象參數(shù)賦值
compiler.options = options;
//傳入options和compiler執(zhí)行WebpackOptionsApply的process想法
//這個(gè)方法對(duì)參數(shù)進(jìn)行了處理,并且注入了大量的插件
compiler.options = new WebpackOptionsApply().process(options, compiler);
//注冊(cè)nodeEveironmentPlugin插件
new NodeEnvironmentPlugin().apply(compiler);
//觸發(fā)environment和after-environment事件
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
webpack很多核心功能本身就是以插件的形式開發(fā)的贤姆,打開lib/WebpackOptionsApply就會(huì)發(fā)現(xiàn)榆苞,在這個(gè)方法中,除了處理參數(shù)霞捡,就是把一個(gè)個(gè)插件注冊(cè)到compiler中
compiler.applyPlugins("environment")
以一種最簡(jiǎn)單的并行處理的方式去去觸發(fā)事件environment事件
坐漏,所有注冊(cè)的處理方法并行執(zhí)行,相互獨(dú)立互不干擾,并且不需要給處理方法傳入?yún)?shù)赊琳。
而有些事件的觸發(fā)方式要復(fù)雜一些街夭,例如complier觸發(fā)emit
的方式
this.applyPluginsAsync("emit", compilation, function(err) {
if(err) return callback(err);
outputPath = compilation.getPath(this.outputPath);
this.outputFileSystem.mkdirp(outputPath, emitFiles.bind(this));
}.bind(this));
這段代碼:
觸發(fā)事件emit
,傳入?yún)?shù)compilation對(duì)象躏筏,串行的調(diào)用注冊(cè)在事件emit
上的處理函數(shù)(先入先出)板丽,倘若某一個(gè)處理函數(shù)報(bào)錯(cuò),則執(zhí)行傳入的function(err)
趁尼,后續(xù)的處理函數(shù)將不被執(zhí)行埃碱,否則最后一個(gè)處理函數(shù)調(diào)用function()
。插件注冊(cè)此類事件酥泞,處理函數(shù)需要調(diào)用callback砚殿,這樣才能保證監(jiān)聽鏈的正確執(zhí)行。所以為了在寫自定義插件的時(shí)候能正確的監(jiān)聽事件芝囤,非常有必要仔細(xì)讀Tapable中文文檔(雖然本文已經(jīng)多次提到這個(gè)文檔似炎,但是還是有必要再進(jìn)行一次提醒)
webpack中另外一個(gè)重要的對(duì)象compilation使用了同樣的方式植入了tapable
總結(jié)
咱們分析webpack源碼主要有兩個(gè)原因:一是為了學(xué)習(xí)優(yōu)秀的代碼的設(shè)計(jì)方法,二是為了編寫webpack自定義插件的時(shí)候能夠游刃有余凡人。不管從哪一點(diǎn)來說名党,Tapable面向切面的插件思想,都是值得我們琢磨的(在我的一個(gè)項(xiàng)目中還真的從webpack把Tapable借鑒過來了)
后續(xù)會(huì)繼續(xù)更新對(duì)webpack源碼的進(jìn)一步分析挠轴,歡迎關(guān)注传睹,共同學(xué)習(xí)。有問題請(qǐng)?jiān)u論或發(fā)簡(jiǎn)信岸晦,如果你覺得文章對(duì)你有所幫助欧啤,請(qǐng)不要吝惜你的喜歡,當(dāng)然启上,給我打賞我也不會(huì)客氣的~~邢隧。