之前很長一段時(shí)間,對webpack有一種畏懼的心理,覺得里面配置復(fù)雜,稍微寫錯(cuò)某個(gè)地方就會導(dǎo)致本地服務(wù)跑不起來,樣式加載錯(cuò)誤,less沒有被編譯,ES6語法沒有被編譯。。都是一把辛酸淚。后來慢慢弄清e(cuò)ntry,output,然后慢慢加一些loader,比如要開啟CSS module功能,比如要對某個(gè)資源文件編譯成JS,學(xué)會用在webpack里面加上plugin,讓項(xiàng)目的webpack功能更加強(qiáng)大。但是這一切都是停留在簡單使用上,現(xiàn)在我終于嘗試去探究webpack內(nèi)部機(jī)制,去利用webpack強(qiáng)大的可配置性做一些定制化的插件。
本文用詞比較通俗,如果表述過于小白,敬請見諒。實(shí)在是因?yàn)閣ebpack整套流程和機(jī)制過于精妙,我難窺一二。
怎么去了解和嘗試寫一個(gè)webpack插件
- 首先要寫一個(gè)工廠函數(shù),將這個(gè)函數(shù)的實(shí)例傳入webpack的plugins屬性
const Plugin = require('./plugin')
module.exports = {
plugins: [
new Plugin
]
}
- 這個(gè)工廠函數(shù)需要有apply方法,以下為es6寫法的該構(gòu)建函數(shù)
export default class DefPlugin {
constructor(name) {}
apply(compiler) {}
}
- 重點(diǎn)在于這個(gè)compiler對象了,它是webpack在構(gòu)建時(shí)傳入插件的實(shí)例對象
代表了webpack完整的構(gòu)建流程。該對象在啟動webpack時(shí)就被一次性創(chuàng)建,由webpack組合所有的配置項(xiàng)(包括原始配置,加載器和插件)構(gòu)建生成。當(dāng)在webpack環(huán)境中應(yīng)用一個(gè)插件時(shí),插件會收到compiler的引用,通過使用compiler,插件就可以訪問到整個(gè)webpack的環(huán)境(包括原始配置,加載器和插件)。
然后它有很多事件鉤子,類似生命周期,在構(gòu)建的某個(gè)生命周期會觸發(fā)對應(yīng)的事件鉤子,而我們寫插件無非就是對使用webpack打包的文件進(jìn)行自定義的處理,那我們要在合適的事件鉤子上注冊事件,在compiler對象上注冊事件的方法為plugin,當(dāng)然這是tapable0.2的寫法,對1.x寫法有興趣的可以查詢文檔,方法如下:
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
// ...
})
}
- 好,已經(jīng)知道compiler了,那么compilation又是個(gè)什么東西...
compilation--構(gòu)建過程:compilation對象在compiler的compile方法里創(chuàng)建,它代表了一次單一的版本構(gòu)建以及構(gòu)建生成資源的匯總,compilation對象負(fù)責(zé)組織整個(gè)打包過程,包含了每個(gè)構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對應(yīng)的方法。該對象內(nèi)部存放著所有module、chunk、生成的assets以及用來生成最后打包文件的template的信息
當(dāng)運(yùn)行webpack-dev-server時(shí),每當(dāng)檢測到一個(gè)文件變化,就會創(chuàng)建一次新的編譯,從而生成一組新的編譯資源。
webpack專門提供了一個(gè)compilation鉤子,在這里可以獲取到compilation對象,我們可以通過compilation對象上的屬性做太多事情了,所有項(xiàng)目里的module在這里,chunk在這里...比如我要給改變我的某個(gè)module的值,我可以這樣做
// 講真這就是一個(gè)完整的自定義插件了,它可以把叫做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上有四個(gè)子類,子類上有相應(yīng)的方法,可以監(jiān)聽事件來出來輸出的資源
image.png
比如我想改變某個(gè)module的代碼,可以這樣,每一個(gè)module都會觸發(fā)一次moduleTemplate的module事件,那么直接在這里做一個(gè)判斷,對想要改變的module進(jìn)行source改造(這里不顯示返回module和source是不行的)
image.png
我也可以在mainTemplate中改變整個(gè)bundle文件的源碼
image.png
compiler事件鉤子

compilation事件鉤子

關(guān)于tapable
webpack 的插件架構(gòu)主要基于Tapable實(shí)現(xiàn)的,Tapable 是 webpack 項(xiàng)目組的一個(gè)內(nèi)部庫,主要是抽象了一套插件機(jī)制,而tapable的精妙之處在于可以玩轉(zhuǎn)任何插件。
- webpack 源代碼中的一些 Tapable 實(shí)例都繼承或混合了 Tapable 類。Tapable 能夠讓我們?yōu)?javaScript 模塊添加并應(yīng)用插件。 它可以被其它模塊繼承或混合。
- 它類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發(fā)和操作。 除此之外, Tapable 允許你通過回調(diào)函數(shù)的參數(shù)訪問事件的生產(chǎn)者。
- webpack中很多對象實(shí)例都繼承了tapable類,暴露了一個(gè)plugin方法,可以用來監(jiān)聽某個(gè)事件
- 自定義插件需要有一個(gè)apply方法,因?yàn)椴寮?shí)例傳入webpack后,webpack會調(diào)用實(shí)例的apply方法,傳入compiler對象,拿到這個(gè)對象就可以訪問webpack構(gòu)建過程中的信息了,比如options,module應(yīng)有盡有,其中在合適的事件里可以獲取到compilation對象,它代表了本次編譯過程,其中又有很多事件鉤子供我們?nèi)燧d事件
敲黑板劃重點(diǎn)
如果沒有在文檔中有可能找不到合適的事件鉤子,那么這時(shí)可以去閱讀源碼找到鉤子
關(guān)于webpack的構(gòu)建流程 還要繼續(xù)學(xué)習(xí)
附上淘寶神圖一張webpack構(gòu)建流程圖和傳送門一個(gè)鏈接


