webpack插件淺析

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

webpack的構(gòu)建背景知識

1、webpack的構(gòu)建流程的下三階段

* 初始化:啟動構(gòu)建,讀取、合并配置文件和shell語句中的配置參數(shù),執(zhí)行配置文件中的插件實例化語句new Plugin(),實例化全局唯一的Compiler,調(diào)用插件的apply 方法點加載Plugin,讓插件可以監(jiān)聽相應(yīng)鉤子的調(diào)用。

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

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

2、compiler和compilation的介紹

**compiler:** 對象代表了完整的webpack環(huán)境配置,是webpack的支柱引擎,Compiler對象在啟動webpack時被一次性建立(可以理解為Webpack實例,全局唯一),并配置好所有可操作的設(shè)置,包括 options,loader 和 plugin。它擴展(extend)自Tapable類,以便注冊和調(diào)用插件。大多數(shù)面向用戶的插件,首先會在 Compiler 上注冊。當(dāng)在webpack環(huán)境中應(yīng)用一個插件時,插件將收到此compiler對象的引用,使用它來訪問 webpack 的主環(huán)境。

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

編寫插件

1、插件的結(jié)構(gòu)

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

```

// 構(gòu)造函數(shù)

function RemoveStrictPlugin(options) {

? // 使用 options 設(shè)置插件實例……

? this.options = options;

}

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

RemoveStrictPlugin.prototype.apply = function(compiler) {

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

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

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


? ? ? ? // 操作完成后調(diào)用 webpack 提供的回調(diào),以通知 Webpack

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

? ? ? ? callback();

? ? });

};

module.exports = RemoveStrictPlugin;

```

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

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

* callback():插件中若存在異步操作,就需要額外傳入一個callback回調(diào)函數(shù),并且在插件運行結(jié)束時,調(diào)用這個callback函數(shù)。

2、插件的使用

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

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

var webpackConfig = {

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

? plugins: [

? ? new RemoveStrictPlugin({options: true})

? ]

};

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

```

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

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

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

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

? ? ? ? // 調(diào)用每個插件的apply,并傳入compiler對象,讓插件注冊到鉤子上

? ? ? ? plugin.apply(compiler);

? ? }

}

```

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

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

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

## 插件機制

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

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

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

「調(diào)用」—— webpack編譯過程中,會適時地觸發(fā)相應(yīng)鉤子,因此也就觸發(fā)了插件的方法。

#### 鉤子的作用

webpack代碼中的模塊/插件的調(diào)用都依賴于鉤子,webpack有很多的鉤子(180多種),在上面介紹的構(gòu)建三個階段中,每個階段會調(diào)用很多的鉤子事件,而插件會注冊到相應(yīng)的鉤子上(類似與監(jiān)聽事件),當(dāng)鉤子被調(diào)用時就會調(diào)用注冊在該鉤子上的插件。webpack的插件機制相較于loader有很大的不同,loader只是固定在轉(zhuǎn)換文件時被調(diào)用,而插件可以通過鉤子參與的webpack構(gòu)建的各個環(huán)節(jié)中,在構(gòu)建流程中能做的也就更多、更靈活。

#### 鉤子的類型

[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']);

```

#### 調(diào)用鉤子

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

```

sayHook.call(this.words);

```

#### 注冊插件

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

```

// 將插件注冊到compilation鉤子

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

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


? ? ? ? // 操作完成后調(diào)用 webpack 提供的回調(diào),以通知 Webpack

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

? ? ? ? callback();

? ? });

```

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

```

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

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

? ? });

};

```

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

```

// 構(gòu)造函數(shù)

function RemoveStrictPlugin(options) {

? // 使用 options 設(shè)置插件實例……

? this.options = options;

}

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

RemoveStrictPlugin.prototype.apply = function(compiler) {

? ? // 注冊compilation事件

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


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


? ? ? ? // 操作完成后調(diào)用 webpack 提供的回調(diào),以通知 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

>

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

>

> 參數(shù):compiler

>

* afterResolvers

> SyncHook

>

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

>

> 參數(shù):compiler

##### compilation的鉤子

* buildModule

> SyncHook

>

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

>

> 參數(shù):module

>

* rebuildModule

> SyncHook

>

> 在重新構(gòu)建一個模塊之前觸發(fā)。

>

> 參數(shù):module

>

* failedModule

> SyncHook

>

> 模塊構(gòu)建失敗時執(zhí)行。

>

> 參數(shù):module error

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

---

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

1)明確你的插件是要怎么調(diào)用的,需不需要傳遞參數(shù)(對應(yīng)著webpack.config.js中的配置);

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容