webpack插件淺析

通過webpack插件倘要,使用階段式的構建回調信峻,開發(fā)者可以webpack構建流程中引入自己操作行為敛腌,也就是說我們首先需要了解webpack構建不同階段中的回調鉤子

webpack的構建背景知識

1、webpack的構建流程的下三階段

* 初始化:啟動構建图毕,讀取夷都、合并配置文件和shell語句中的配置參數(shù),執(zhí)行配置文件中的插件實例化語句new Plugin()予颤,實例化全局唯一的Compiler囤官,調用插件的apply 方法點加載Plugin厢破,讓插件可以監(jiān)聽相應鉤子的調用。

* 編譯:根據(jù)用戶配置的Entry(入口文件)開始治拿,搜索查找入口文件,根據(jù)文件類型匹配配置中的Loader笆焰,若是同一種文件類型配置了多個loader則從右到左串行調用它們劫谅,來對文件內容進行轉換,入口文件中的依賴模塊會遞歸的進行同樣的處理嚷掠。

* 輸出:將編譯后的模塊組合成Chunk捏检,將Chunk轉換成文件,輸出到文件系統(tǒng)中 不皆。

2贯城、compiler和compilation的介紹

**compiler:** 對象代表了完整的webpack環(huán)境配置,是webpack的支柱引擎霹娄,Compiler對象在啟動webpack時被一次性建立(可以理解為Webpack實例能犯,全局唯一),并配置好所有可操作的設置犬耻,包括 options踩晶,loader 和 plugin。它擴展(extend)自Tapable類枕磁,以便注冊和調用插件渡蜻。大多數(shù)面向用戶的插件,首先會在 Compiler 上注冊计济。當在webpack環(huán)境中應用一個插件時茸苇,插件將收到此compiler對象的引用,使用它來訪問 webpack 的主環(huán)境沦寂。

**compilation:** 對象代表了一次資源版本構建学密。當運行webpack開發(fā)環(huán)境中間件時,每當檢測到一個文件變化凑队,就會創(chuàng)建一個新的compilation则果,從而生成一組新的編譯資源。一個compilation對象表現(xiàn)了當前的模塊資源漩氨、編譯生成資源西壮、變化的文件、以及被跟蹤依賴的狀態(tài)信息叫惊。compilation對象也提供了很多關鍵時機的回調款青,以供插件做自定義處理時選擇使用。

編寫插件

1霍狰、插件的結構

插件是一個構造函數(shù)抡草,它的prototype上必須要定義了一個apply方法饰及。這個apply方法在安裝插件時,會被 webpack compiler調用一次康震,同時它接收一個compiler對象的引用燎含,從而使在回調函數(shù)中可以通過compiler對象訪問webpack的主環(huán)境,在功能完成后調用webpack提供的回調腿短。一個簡單的插件結構如下:

```

// 構造函數(shù)

function RemoveStrictPlugin(options) {

? // 使用 options 設置插件實例……

? this.options = options;

}

// 在prototype上定義apply 方法屏箍,形參為compiler對象

RemoveStrictPlugin.prototype.apply = function(compiler) {

? ? // 將插件注冊到compilation鉤子

? ? compiler.plugin('compilation', function(compilation) {

? ? ? ? // 根據(jù)webpack提供api處理相應功能...


? ? ? ? // 操作完成后調用 webpack 提供的回調,以通知 Webpack

? ? ? ? // 如果不執(zhí)行 callback橘忱,運行流程將會一直卡在這里而不往后執(zhí)行

? ? ? ? callback();

? ? });

};

module.exports = RemoveStrictPlugin;

```

* compiler.plugin:將插件注冊到compilation鉤子赴魁。

* compilation:常用的一種鉤子,編譯(compilation)創(chuàng)建之后钝诚,執(zhí)行插件

* callback():插件中若存在異步操作颖御,就需要額外傳入一個callback回調函數(shù),并且在插件運行結束時凝颇,調用這個callback函數(shù)潘拱。

2、插件的使用

在webpack配置的 plugin 數(shù)組中添的插件實例將會被安裝:

var RemoveStrictPlugin = require('remove-strict');

var webpackConfig = {

? // ... 這里是其他配置 ...

? plugins: [

? ? new RemoveStrictPlugin({options: true})

? ]

};

根據(jù)上面的配置祈噪,在webpack的lib/webpack.js中泽铛,webpack通過調用插件的apply方法,這也就是為啥我們在開發(fā)插件時需要在prototype上定義個apply方法的原因:

```

// 獲取配置文件中的plugins屬性值

if (options.plugins && Array.isArray(options.plugins)) {

? ? // 遍歷配置文件中plugins數(shù)組

? ? for (const plugin of options.plugins) {

? ? ? ? // 調用每個插件的apply辑鲤,并傳入compiler對象盔腔,讓插件注冊到鉤子上

? ? ? ? plugin.apply(compiler);

? ? }

}

```

使用此配置文件進行構建的話,會發(fā)現(xiàn)控制臺中提示:

![image](https://img.58cdn.com.cn/escstatic/fecar/pmuse/chenli03/C0A4BE24-E8F2-4D6D-9AC8-746392A23382.png)

它告訴我們Tabable.plugin這種的調用形式已經(jīng)被廢棄了月褥,請使用新的API弛随,也就是.hooks來替代.plugin這種形式。 .hooks會在后面要說的鉤子部分進行講解宁赤。

## 插件機制

了解了插件的編寫之后舀透,那webpack實現(xiàn)插件機制是什么呢?大體如下:

「創(chuàng)建」—— webpack在其內部對象上創(chuàng)建各種鉤子决左;

「注冊」—— 插件將自己的方法注冊到對應鉤子上愕够,交給webpack;(也就是編寫插件時提到的注冊到鉤子)

「調用」—— webpack編譯過程中佛猛,會適時地觸發(fā)相應鉤子惑芭,因此也就觸發(fā)了插件的方法。

#### 鉤子的作用

webpack代碼中的模塊/插件的調用都依賴于鉤子继找,webpack有很多的鉤子(180多種)遂跟,在上面介紹的構建三個階段中,每個階段會調用很多的鉤子事件,而插件會注冊到相應的鉤子上(類似與監(jiān)聽事件)幻锁,當鉤子被調用時就會調用注冊在該鉤子上的插件凯亮。webpack的插件機制相較于loader有很大的不同,loader只是固定在轉換文件時被調用哄尔,而插件可以通過鉤子參與的webpack構建的各個環(huán)節(jié)中假消,在構建流程中能做的也就更多、更靈活岭接。

#### 鉤子的類型

[tapable](https://github.com/webpack/tapable) 這個小型 library 是 webpack 的一個核心工具置谦,但也可用于其他地方,以提供類似的插件接口亿傅。webpack中許多對象擴展自 Tapable 類(例如compiler對象)。通過Tapable瘟栖,可以快速創(chuàng)建各類鉤子葵擎。以下是各種鉤子的類函數(shù):

```

const {

SyncHook,

SyncBailHook,

SyncWaterfallHook,

SyncLoopHook,

AsyncParallelHook,

AsyncParallelBailHook,

AsyncSeriesHook,

AsyncSeriesBailHook,

AsyncSeriesWaterfallHook

} = require("tapable");

```

#### 創(chuàng)建鉤子

```

// 引用tapable 使用SyncHook類創(chuàng)建鉤子實例

const { SyncHook } = require('tapable');

let sayHook = new SyncHook(['params']);

```

#### 調用鉤子

使用call方法調用鉤子,這里的.call()的方法是Tapable提供的觸發(fā)鉤子的方法半哟,不是js中原生的call方法酬滤。

```

sayHook.call(this.words);

```

#### 注冊插件

前面編寫插件時我們講到在apply方法中,通過如下代碼可以注冊到相應的鉤子上:

```

// 將插件注冊到compilation鉤子

? ? compiler.plugin('compilation', function(compilation) {

? ? ? ? // 根據(jù)webpack提供api處理相應功能...


? ? ? ? // 操作完成后調用 webpack 提供的回調寓涨,以通知 Webpack

? ? ? ? // 如果不執(zhí)行 callback盯串,運行流程將會一直卡在這里而不往后執(zhí)行

? ? ? ? callback();

? ? });

```

而webpack推薦使用新的方式注冊鉤子:通過鉤子類暴露的tap,tapAsync和tapPromise方法,將配置文件中插件注冊到相應的鉤子上戒良,也就是在構建流程中注入了自定義的構建步驟体捏,這些步驟將在整個編譯過程中不同時機觸發(fā)。根據(jù)類型的鉤子糯崎,來可以選擇使用tap,tapAsync和tapPromise方法:

```

sayHook.tap('pluginName', compiler => {

? ? ? ? console.log('執(zhí)行插件內容');

? ? });

};

```

那么使用新的方式改寫插件代碼如下:

```

// 構造函數(shù)

function RemoveStrictPlugin(options) {

? // 使用 options 設置插件實例……

? this.options = options;

}

// 在prototype上定義apply 方法几缭,形參為compiler對象

RemoveStrictPlugin.prototype.apply = function(compiler) {

? ? // 注冊compilation事件

? ? compiler.hooks.compilation.tap('myCompilation', function(compilation) {


? ? ? ? // 根據(jù)webpack提供api處理相應功能...


? ? ? ? // 操作完成后調用 webpack 提供的回調,以通知 Webpack

? ? ? ? // 如果不執(zhí)行 callback沃呢,運行流程將會一直卡在這里而不往后執(zhí)行

? ? ? ? callback();

? ? });

};

```

* compiler.hooks:compiler對象上的一個屬性年栓,允許我們使用不同的鉤子函數(shù)。

* .compilation:hooks中常用的一種鉤子薄霜,編譯(compilation)創(chuàng)建之后某抓,執(zhí)行插件。

* .tap:表示可以注冊同步的鉤子和異步的鉤子惰瓜,而在此處因為done屬于異步AsyncSeriesHook類型的鉤子妈经,所以這里表示把插件注冊到compilation鉤子上。

#### 主要的鉤子:

##### compiler的鉤子

* entryOption:

>

> SyncBailHook

>

> 在 entry 配置項處理過之后艾凯,執(zhí)行插件扣典。

>

* afterPlugins

> SyncHook

>

> 設置完初始插件之后,執(zhí)行插件。

>

> 參數(shù):compiler

>

* afterResolvers

> SyncHook

>

> resolver 安裝完成之后痹届,執(zhí)行插件呻待。

>

> 參數(shù):compiler

##### compilation的鉤子

* buildModule

> SyncHook

>

> 在模塊構建開始之前觸發(fā)。

>

> 參數(shù):module

>

* rebuildModule

> SyncHook

>

> 在重新構建一個模塊之前觸發(fā)队腐。

>

> 參數(shù):module

>

* failedModule

> SyncHook

>

> 模塊構建失敗時執(zhí)行蚕捉。

>

> 參數(shù):module error

更詳細的說明可以參考:[compiler 鉤子](https://www.webpackjs.com/api/compiler-hooks/)、[compilation 鉤子](https://www.webpackjs.com/api/compilation-hooks/)

---

所以柴淘,現(xiàn)在你已經(jīng)知道開發(fā)webpack插件的關鍵了被迫淹?我們想要編寫一個插件,只需要這么幾步:

1)明確你的插件是要怎么調用的为严,需不需要傳遞參數(shù)(對應著webpack.config.js中的配置)敛熬;

2)創(chuàng)建一個構造函數(shù),以此來保證用它能創(chuàng)建一個個插件實例第股;

3)在構造函數(shù)原型對象上定義一個apply方法应民,并在其中利用tap, tapAsync 和 tapPromise 方法注冊我們的自定義插件。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末夕吻,一起剝皮案震驚了整個濱河市诲锹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涉馅,老刑警劉巖归园,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稚矿,居然都是意外死亡庸诱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門晤揣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偶翅,“玉大人,你說我怎么就攤上這事碉渡【鬯” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵滞诺,是天一觀的道長形导。 經(jīng)常有香客問我,道長习霹,這世上最難降的妖魔是什么朵耕? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮淋叶,結果婚禮上阎曹,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好处嫌,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布栅贴。 她就那樣靜靜地躺著,像睡著了一般熏迹。 火紅的嫁衣襯著肌膚如雪檐薯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天注暗,我揣著相機與錄音坛缕,去河邊找鬼。 笑死捆昏,一個胖子當著我的面吹牛赚楚,可吹牛的內容都是我干的。 我是一名探鬼主播骗卜,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼直晨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膨俐?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤罩句,失蹤者是張志新(化名)和其女友劉穎焚刺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體门烂,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡乳愉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屯远。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔓姚。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖慨丐,靈堂內的尸體忽然破棺而出坡脐,到底是詐尸還是另有隱情,我是刑警寧澤房揭,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布备闲,位于F島的核電站,受9級特大地震影響捅暴,放射性物質發(fā)生泄漏恬砂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一蓬痒、第九天 我趴在偏房一處隱蔽的房頂上張望泻骤。 院中可真熱鬧,春花似錦、人聲如沸狱掂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽符欠。三九已至嫡霞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間希柿,已是汗流浹背诊沪。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曾撤,地道東北人端姚。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像挤悉,于是被迫代替她去往敵國和親渐裸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349