之前很長一段時間,對webpack有一種畏懼的心理不铆,覺得里面配置復(fù)雜蝌焚,稍微寫錯某個地方就會導(dǎo)致本地服務(wù)跑不起來,樣式加載錯誤誓斥,less沒有被編譯只洒,ES6語法沒有被編譯。劳坑。都是一把辛酸淚毕谴。后來慢慢弄清entry,output距芬,然后慢慢加一些loader涝开,比如要開啟CSS module功能,比如要對某個資源文件編譯成JS框仔,學(xué)會用在webpack里面加上plugin舀武,讓項目的webpack功能更加強(qiáng)大。但是這一切都是停留在簡單使用上离斩,現(xiàn)在我終于嘗試去探究webpack內(nèi)部機(jī)制银舱,去利用webpack強(qiáng)大的可配置性做一些定制化的插件。
本文用詞比較通俗跛梗,如果表述過于小白寻馏,敬請見諒。實在是因為webpack整套流程和機(jī)制過于精妙核偿,我難窺一二诚欠。
怎么去了解和嘗試寫一個webpack插件
- 首先要寫一個工廠函數(shù),將這個函數(shù)的實例傳入webpack的plugins屬性
const Plugin = require('./plugin')
module.exports = {
plugins: [
new Plugin
]
}
- 這個工廠函數(shù)需要有apply方法漾岳,以下為es6寫法的該構(gòu)建函數(shù)
export default class DefPlugin {
constructor(name) {}
apply(compiler) {}
}
- 重點(diǎn)在于這個compiler對象了聂薪,它是webpack在構(gòu)建時傳入插件的實例對象
代表了webpack完整的構(gòu)建流程。該對象在啟動webpack時就被一次性創(chuàng)建蝗羊,由webpack組合所有的配置項(包括原始配置藏澳,加載器和插件)構(gòu)建生成。當(dāng)在webpack環(huán)境中應(yīng)用一個插件時耀找,插件會收到compiler的引用翔悠,通過使用compiler业崖,插件就可以訪問到整個webpack的環(huán)境(包括原始配置,加載器和插件)蓄愁。
然后它有很多事件鉤子双炕,類似生命周期,在構(gòu)建的某個生命周期會觸發(fā)對應(yīng)的事件鉤子撮抓,而我們寫插件無非就是對使用webpack打包的文件進(jìn)行自定義的處理妇斤,那我們要在合適的事件鉤子上注冊事件,在compiler對象上注冊事件的方法為plugin丹拯,當(dāng)然這是tapable0.2的寫法站超,對1.x寫法有興趣的可以查詢文檔,方法如下:
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
// ...
})
}
- 好乖酬,已經(jīng)知道compiler了死相,那么compilation又是個什么東西...
compilation--構(gòu)建過程:compilation對象在compiler的compile方法里創(chuàng)建,它代表了一次單一的版本構(gòu)建以及構(gòu)建生成資源的匯總咬像,compilation對象負(fù)責(zé)組織整個打包過程算撮,包含了每個構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對應(yīng)的方法。該對象內(nèi)部存放著所有module县昂、chunk肮柜、生成的assets以及用來生成最后打包文件的template的信息
當(dāng)運(yùn)行webpack-dev-server時,每當(dāng)檢測到一個文件變化倒彰,就會創(chuàng)建一次新的編譯素挽,從而生成一組新的編譯資源。
webpack專門提供了一個compilation鉤子狸驳,在這里可以獲取到compilation對象,我們可以通過compilation對象上的屬性做太多事情了缩赛,所有項目里的module在這里耙箍,chunk在這里...比如我要給改變我的某個module的值,我可以這樣做
// 講真這就是一個完整的自定義插件了酥馍,它可以把叫做test.js的module的內(nèi)容的world字段全局替換為lynn
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
compilation.moduleTemplate.plugin('module', (source, module, options, dependencyTemplates) => {
if (/test.js/.test(module.request)) {
let newSource = source.source().replace(/world/g, 'lynn')
return newSource
} else {
return source
}
return module
})
})
}
- 好吧...又出現(xiàn)新名詞ModuleTemplate辩昆,所有的module資源都是要經(jīng)過模板包裝一下輸出,compilation上有四個子類旨袒,子類上有相應(yīng)的方法汁针,可以監(jiān)聽事件來出來輸出的資源
image.png
比如我想改變某個module的代碼,可以這樣砚尽,每一個module都會觸發(fā)一次moduleTemplate的module事件施无,那么直接在這里做一個判斷,對想要改變的module進(jìn)行source改造(這里不顯示返回module和source是不行的)
image.png
我也可以在mainTemplate中改變整個bundle文件的源碼
image.png
compiler事件鉤子
compilation事件鉤子
關(guān)于tapable
webpack 的插件架構(gòu)主要基于Tapable實現(xiàn)的必孤,Tapable 是 webpack 項目組的一個內(nèi)部庫猾骡,主要是抽象了一套插件機(jī)制瑞躺,而tapable的精妙之處在于可以玩轉(zhuǎn)任何插件。
- webpack 源代碼中的一些 Tapable 實例都繼承或混合了 Tapable 類兴想。Tapable 能夠讓我們?yōu)?javaScript 模塊添加并應(yīng)用插件幢哨。 它可以被其它模塊繼承或混合。
- 它類似于 NodeJS 的 EventEmitter 類嫂便,專注于自定義事件的觸發(fā)和操作捞镰。 除此之外, Tapable 允許你通過回調(diào)函數(shù)的參數(shù)訪問事件的生產(chǎn)者。
- webpack中很多對象實例都繼承了tapable類毙替,暴露了一個plugin方法岸售,可以用來監(jiān)聽某個事件
- 自定義插件需要有一個apply方法,因為插件實例傳入webpack后蔚龙,webpack會調(diào)用實例的apply方法冰评,傳入compiler對象,拿到這個對象就可以訪問webpack構(gòu)建過程中的信息了木羹,比如options甲雅,module應(yīng)有盡有,其中在合適的事件里可以獲取到compilation對象坑填,它代表了本次編譯過程抛人,其中又有很多事件鉤子供我們?nèi)燧d事件
敲黑板劃重點(diǎn)
如果沒有在文檔中有可能找不到合適的事件鉤子,那么這時可以去閱讀源碼找到鉤子
關(guān)于webpack的構(gòu)建流程 還要繼續(xù)學(xué)習(xí)
附上淘寶神圖一張webpack構(gòu)建流程圖和傳送門一個鏈接