最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文詳解Webpack中Tapable事件機制

 更新時間:2023年11月13日 08:27:55   作者:古茗前端團隊  
Webpack?是前端工程化常用的靜態(tài)模塊打包工具,在合適的時機通過?Webpack?提供的?API?改變輸出結果,使?Webpack?可以執(zhí)行更廣泛的任務,擁有更強的構建能力,本文將介紹?Tapable?的基本使用以及底層實現(xiàn),需要的朋友可以參考下

引言

Webpack 是前端工程化常用的靜態(tài)模塊打包工具。在合適的時機通過 Webpack 提供的 API 改變輸出結果,使 Webpack 可以執(zhí)行更廣泛的任務,擁有更強的構建能力。

Webpack 的插件機制本質(zhì)上是一種事件流的機制,它的工作流程就是將各個插件串聯(lián)起來,而實現(xiàn)這一切的核心就是Tapable,Webpack中最核心的負責編譯的Compiler和負責創(chuàng)建bundles的Compilation都是Tapable的實例。

本文將介紹 Tapable 的基本使用以及底層實現(xiàn)。

Tapable

Tapable 是一個類似于 Node.js 中的 EventEmitter 的庫,但它更專注于自定義事件的觸發(fā)和處理。通過 Tapable 我們可以注冊自定義事件,然后在適當?shù)臅r機去執(zhí)行自定義事件。這個和我們所熟知的生命周期函數(shù)類似,在特定的時機去觸發(fā)。

我們先看一個 簡單 Tapable 的 例子:

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

// 實例化 鉤子函數(shù) 定義形參
const syncHook = new SyncHook(["name"]);

//通過tap函數(shù)注冊事件
syncHook.tap("同步鉤子1", (name) => {
  console.log("同步鉤子1", name);
});

//同步鉤子 通過call 發(fā)布事件
syncHook.call("古茗前端");

通過上面的例子,我們大致可以將 Tapable 的使用分為以下三步:

  • 實例化鉤子函數(shù)
  • 事件注冊
  • 事件觸發(fā)

事件注冊

  • 同步的鉤子要用 tap 方法來注冊事件
  • 異步的鉤子可以像同步方式一樣用 tap 方法來注冊,也可以用 tapAsynctapPromise 異步方法來注冊。
    • tapAsync: 使用用 tapAsync 方法來注冊 hook 時,必須調(diào)用callback 回調(diào)函數(shù)。
    • tapPromise:使用 tapPromise 方法來注冊 hook 時,必須返回一個 pormise ,異步任務完成后 resolve

事件觸發(fā)

  • 同步的鉤子要用 call 方法來觸發(fā)
  • 異步的鉤子需要用 callAsyncpromise 異步方法來觸發(fā)。
    • callAsync:當我們用 callAsync 方法來調(diào)用 hook 時,第二個參數(shù)是一個回調(diào)函數(shù),回調(diào)函數(shù)的參數(shù)是執(zhí)行任務的最后一個返回值
    • promise:當我們用 promise 方法來調(diào)用 hook 時,需要使用 then 來處理執(zhí)行結果,參數(shù)是執(zhí)行任務的最后一個返回值。

Tapable Hook 鉤子

tapable 內(nèi)置了 9 種 hook 。分為 同步、異步 兩種執(zhí)行方式。異步執(zhí)行 Hook 又可以分為 串行 執(zhí)行和 并行 執(zhí)行。除此之外,hook 可以根據(jù)執(zhí)行機制分為 常規(guī) 瀑布模式 熔斷模式 循環(huán)模式 四種執(zhí)行機制。

同步鉤子

同步鉤子顧名思義:同步執(zhí)行,上一個鉤子執(zhí)行完才會執(zhí)行下一個鉤子。

示例代碼如下:

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

// 實例化 鉤子函數(shù) 定義形參
const syncHook = new SyncHook(["name"]);

//通過tap函數(shù)注冊事件
syncHook.tap("同步鉤子1", (name) => {
  console.log("同步鉤子1", name);
});

//該監(jiān)聽函數(shù)有返回值
syncHook.tap("同步鉤子2", (name) => {
  console.log("同步鉤子2", name);
});


//同步鉤子 通過call 發(fā)布事件
syncHook.call("古茗前端");

執(zhí)行結果如下所示:

異步鉤子

異步鉤子分為: 串行執(zhí)行和并行執(zhí)行。在串行執(zhí)行中,如果上一個鉤子沒有調(diào)用callback 回調(diào)函數(shù),下一個鉤子將不會觸發(fā)對應的事件監(jiān)聽。

示例代碼如下:

const { AsyncParallelHook, AsyncSeriesHook } = require("tapable");


const asyncParallelHook = new AsyncParallelHook(["name"]);

const asyncSeriesHook = new AsyncSeriesHook(["name"]);


//通過tap函數(shù)注冊事件
asyncParallelHook.tapAsync("異步并行鉤子1", (name, callback) => {
  setTimeout(() => {
    console.log("異步并行鉤子1", name);
  }, 3000);
});

//該監(jiān)聽函數(shù)有返回值
asyncParallelHook.tapAsync("異步并行鉤子2", (name, callback) => {
  setTimeout(() => {
    console.log("異步并行鉤子2", name);
  }, 1500);
});

//通過tap函數(shù)注冊事件
asyncSeriesHook.tapAsync("異步串行鉤子1", (name, callback) => {
  setTimeout(() => {
    console.log("異步串行鉤子1", name);
  }, 3000);
});

//該監(jiān)聽函數(shù)有返回值
asyncSeriesHook.tapAsync("異步串行鉤子2", (name, callback) => {
  setTimeout(() => {
    console.log("異步串行鉤子2", name);
  }, 1500);
});


// 異步并行鉤子 通過 callAsync 發(fā)布事件
asyncParallelHook.callAsync("古茗前端", () => {
  console.log("1111");
  return "1122";
});

// 異步串行鉤子 通過 callAsync 發(fā)布事件
asyncSeriesHook.callAsync("古茗前端", () => {
  console.log("1111");
  return "1122";
});

控制臺輸出結果如下圖所示:

串行鉤子1沒有調(diào)用callback, 所以串行鉤子2沒有觸發(fā)。添加callback后,控制臺輸出結果:

熔斷類

AsyncSeriesBailHook 是一個異步串行、熔斷類型的 Hook。在串行的執(zhí)行過程中,只要其中一個有返回值,后面的就不會執(zhí)行了。

示例代碼如下:

const { SyncBailHook, AsyncSeriesBailHook } = require("tapable");

const syncBailHook = new SyncBailHook(["name"]);
const asyncSeriesBailHook = new AsyncSeriesBailHook(["name"]);

syncBailHook.tap("保險類同步鉤子1", (name) => {
  console.log("保險類同步鉤子1", name);
});

syncBailHook.tap("保險類同步鉤子2", (name) => {
  console.log("保險類同步鉤子2", name);
  return "有返回值";
});

syncBailHook.tap("保險類同步鉤子3", (name) => {
  console.log("保險類同步鉤子3", name);
});

asyncSeriesBailHook.tapAsync("保險類異步串行鉤子1", (name, callback) => {
  setTimeout(() => {
    console.log("保險類異步串行鉤子1", name);
    callback();
  }, 3000);
});

asyncSeriesBailHook.tapAsync("保險類2步串行鉤子1", (name, callback) => {
  setTimeout(() => {
    console.log("保險類異步串行鉤子2", name);
    callback("有返回值");
  }, 2000);
});

asyncSeriesBailHook.tapAsync("保險類異步串行鉤子3", (name) => {
  setTimeout(() => {
    console.log("保險類異步串行鉤子3", name);
  }, 1000);
});

syncBailHook.call("古茗前端");
asyncSeriesBailHook.callAsync("古茗前端", (result) => {
  console.log("result", result);
});

控制臺輸出結果如下圖所示:

循環(huán)類

SyncLoopHook 是一個同步、循環(huán)類型的 Hook。循環(huán)類型的含義是不停的循環(huán)執(zhí)行事件函數(shù),直到所有函數(shù)結果 result === undefined,不符合條件就調(diào)頭重新開始執(zhí)行。

示例代碼:

const { SyncLoopHook } = require("tapable");

const syncLoopHook = new SyncLoopHook(["name"]);

let count = 4;

syncLoopHook.tap("循環(huán)鉤子1", (name) => {
  console.log("循環(huán)鉤子1", count);
  return count <= 3 ? undefined : count--;
});

syncLoopHook.tap("循環(huán)鉤子2", (name) => {
  console.log("循環(huán)鉤子2", count);
  return count <= 2 ? undefined : count--;
});

syncLoopHook.tap("循環(huán)鉤子3", (name) => {
  console.log("循環(huán)鉤子3", count);
  return count <= 1 ? undefined : count--;
});


syncLoopHook.call();

控制臺輸出結果:

瀑布類

AsyncSeriesWaterfallHook 是一個異步串行、瀑布類型的 Hook。如果前一個事件函數(shù)的結果 result !== undefined,則 result 會作為后一個事件函數(shù)的第一個參數(shù)(也就是上一個函數(shù)的執(zhí)行結果會成為下一個函數(shù)的參數(shù))。

示例代碼:

const { AsyncSeriesWaterfallHook, SyncWaterfallHook } = require("tapable");

const syncWaterfallHook = new SyncWaterfallHook(["name"]);

const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(["name"]);

syncWaterfallHook.tap("瀑布式同步鉤子1", (name) => {
  console.log("瀑布式同步鉤子1", name);

  return "古茗前端1";
});

syncWaterfallHook.tap("瀑布式同步鉤子2", (name) => {
  console.log("瀑布式同步鉤子2", name);
});

syncWaterfallHook.tap("瀑布式同步鉤子3", (name) => {
  console.log("瀑布式同步鉤子3", name);

  return "古茗前端3";
});

asyncSeriesWaterfallHook.tapAsync("瀑布式異步串行鉤子1", (name, callback) => {
  setTimeout(() => {
    console.log("瀑布式異步串行鉤子1", name);

    callback();
  }, 1000);
});

asyncSeriesWaterfallHook.tapAsync("瀑布式異步串行鉤子2", (name, callback) => {
  console.log("瀑布式異步串行鉤子2", name);
  setTimeout(() => {
    callback();
  }, 2000);
});

asyncSeriesWaterfallHook.tapAsync("瀑布式異步串行鉤子3", (name, callback) => {
  console.log("瀑布式異步串行鉤子3", name);
  setTimeout(() => {
    callback("古茗前端3");
  }, 3000);
});

syncWaterfallHook.call("古茗前端");

asyncSeriesWaterfallHook.callAsync("古茗前端", (result) => {
  console.log("result", result);
});


控制臺輸出結果:

Tapable 高級特性

Intercept

除了通常的 tap/call 之外,所有 hook 鉤子都提供額外的攔截API。— intercept 接口。

intercept 支持的中間件如下圖所示:

intercept類型描述
call(...args) => void 當鉤子被觸發(fā)時,向攔截器添加調(diào)用將被觸發(fā)。您可以訪問hooks參數(shù)
tap(tap: Tap) 將tap添加到攔截器將在插件點擊鉤子時觸發(fā)。提供的是Tap對象。無法更改Tap對象
loop(...args) => void 向攔截器添加循環(huán)將觸發(fā)循環(huán)鉤子的每個循環(huán)。
register(tap: Tap) => Tap 或 undefined將注冊添加到攔截器將觸發(fā)每個添加的Tap,并允許對其進行修改。

context

插件和攔截器可以選擇訪問可選的上下文對象,該對象可用于向后續(xù)插件和攔截器傳遞任意值。

我們通過下面的小案例,來幫助我們理解。示例代碼如下:

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

// 實例化 鉤子函數(shù) 定義形參
const syncHook = new SyncHook(["name"]);

syncHook.intercept({
  context: true,
  register(context, name) {
    console.log("every time tap", context, name);
  },
  call(context, name) {
    console.log("before call", context, name);
  },
  loop(context, name) {
    console.log("before loop", context, name);
  },
  tap(context, name) {
    console.log("before tap", context, name);
  },
});

//通過tap函數(shù)注冊事件
syncHook.tap("同步鉤子1", (name) => {
  console.log("同步鉤子1", name);
});

//通過tap函數(shù)注冊事件
syncHook.tap("同步鉤子2", (name) => {
  console.log("同步鉤子2", name);
});

//同步鉤子 通過call 發(fā)布事件
syncHook.call("古茗前端");
syncHook.call("古茗前端 call2");

控制臺輸入結果如圖所示:

由上面的案例結果,我們可以知道。intercept 中的 register 會在每一次的 tap 觸發(fā)。 有幾個 tap 就會觸發(fā)幾次 register。然后依次執(zhí)行鉤子里面的 calltap.

intercept 特性在 webpack 內(nèi)主要被用作進度提示,如 webpack/lib/ProgressPlugin 插件中,分別對 compiler.hooks.emit 、compiler.hooks.afterEmit 鉤子應用了記錄進度的中間件函數(shù)。

HookMap

HookMap HookMap是具有Hooks的Map的輔助類.提供了一種集合操作能力,能夠降低創(chuàng)建與使用的復雜度,用法比較簡單:

const { SyncHook, HookMap } = require("tapable"); 
const syncMap = new HookMap(() => new SyncHook()); 

// 通過 for 函數(shù)過濾集合中的特定鉤子 
syncMap.for("some-key").tap("MyPlugin", (arg) => { /* ... */ });
syncMap.for("some-key").tapAsync("MyPlugin", (arg, callback) => { /* ... */ });
syncMap.for("some-key").tapPromise("MyPlugin", (arg) => { /* ... */ });

// 觸發(fā) guming-test 類型的鉤子 
syncMap.get("guming-test").call();

Tapable 底層原理

我們先將 Tapable 工程源碼克隆到本地, 執(zhí)行如下指令:

$ git clone https://github.com/webpack/tapable.git

Tapable 源碼的 lib 目錄結構, 如下所示:

lib
├─ AsyncParallelBailHook.js
├─ AsyncParallelHook.js
├─ AsyncSeriesBailHook.js
├─ AsyncSeriesHook.js
├─ AsyncSeriesLoopHook.js
├─ AsyncSeriesWaterfallHook.js
├─ Hook.js
├─ HookCodeFactory.js
├─ HookMap.js
├─ MultiHook.js
├─ SyncBailHook.js
├─ SyncHook.js
├─ SyncLoopHook.js
├─ SyncWaterfallHook.js
├─ index.js
└─ util-browser.js

除了上面我們所提及的基本 hooks 函數(shù)、HookMap高級特性,還會有一些 HookCodeFactory、Hook 這些文件。我們簡單過一下 hooks 函數(shù)內(nèi)的內(nèi)容, 會發(fā)現(xiàn)所有的 hooks 函數(shù)都會引用 HookCodeFactoryHook 這兩個文件所導出的對象實例。

我們以 syncHook 鉤子函數(shù)為例, 如下圖所示:

我們大致能夠知道 一個 hooks 函數(shù) 會由一個 CodeFactory 代碼工廠 以及 Hook 實例組成。 Hook 實例會針對不同場景的 hooks 函數(shù), 更改其對應的 注冊鉤子(tapAsync ,tap, tapPromise )事件觸發(fā)鉤子( call , callAsync ), 編譯函數(shù)(complier)。 Complier 函數(shù)會由我們 HookCodeFactory 實現(xiàn)。

接下來,我們將通過分析 HookCodeFactoryHook 的內(nèi)部實現(xiàn)來了解 Tapable 的內(nèi)部實現(xiàn)機制。

Hook 實例

Hook 實例會生成我們 hooks 鉤子函數(shù)通用的 事件注冊事件觸發(fā)。核心邏輯,我們大致可以分為以下三個部分:

  • 實例初始化構造函數(shù)
  • 事件注冊 的實現(xiàn)
  • 事件觸發(fā)的實現(xiàn)

構造函數(shù)

構造函數(shù)會對實例屬性初始化賦值。代碼如下圖所示:

注冊事件

注冊事件主要分為兩塊,一塊是 適配器注冊調(diào)用, 第二塊是 觸發(fā)事件注冊。核心邏輯在 _tap 函數(shù)內(nèi)部實現(xiàn),代碼如下圖所示:

適配器調(diào)用

在這里會對攜帶 register 函數(shù)的適配器進行調(diào)用,更改 options 配置,返回新的 options 配置。代碼如下圖所示:

觸發(fā)事件注冊

Hook 實例的 taps 會存儲我們的注冊事件, 同時會根據(jù),注冊事件配置的執(zhí)行順序去存儲對應的注冊事件。

觸發(fā)事件

觸發(fā)事件會通過調(diào)用內(nèi)部 的 _createCall 函數(shù),函數(shù)內(nèi)部會調(diào)用實例的 compile 函數(shù)。我們會發(fā)現(xiàn):Hook 實例內(nèi)部不會去實現(xiàn) complier的邏輯, 不同鉤子的 complier 函數(shù)會通過通過對應的 繼承 HookCodeFactory 的實例去實現(xiàn)。代碼如下圖所示:

接下來,我們繼續(xù) 探究 HookCodeFactory 實例,了解 Tapable 事件觸發(fā)的邏輯。

HookCodeFactory 實例

HookCodeFactory 實例會根據(jù)我們傳入的事件觸發(fā)類型 (sync, async, promise)以及我們的觸發(fā)機制類型 (常規(guī) 瀑布模式 保險模式 循環(huán)模式), 生成事件觸發(fā)函數(shù)的函數(shù)頭, 函數(shù)體。通過 new Function 構造出事件觸發(fā)函數(shù)。

Tapable 事件觸發(fā)的執(zhí)行,是動態(tài)生成執(zhí)行代碼, 包含我們的參數(shù),函數(shù)頭,函數(shù)體,然后通過 new Function 來執(zhí)行。相較于我們通常的遍歷/遞歸調(diào)用事件,這無疑讓 webpack 的整個事件機制的執(zhí)行有了一個更高的性能優(yōu)勢。

由上面我們可知, Hook 實例 的 complier 函數(shù)是 HookCodeFactory 實例 create 函數(shù) 的返回。

接下來,我們就從 create 函數(shù) 一步步揭秘 Tapable 的動態(tài)生成執(zhí)行函數(shù)的核心實現(xiàn)。

create

create 函數(shù)通過對應的 函數(shù)參數(shù)函數(shù) header, 函數(shù) content方法構造出我們事件觸發(fā)的函數(shù)的內(nèi)容, 通過 new Function 創(chuàng)建出我們的觸發(fā)函數(shù)。

create 函數(shù)會根據(jù)事件的觸發(fā)類型 ( sync、async、promise),進行不同的邏輯處理。代碼如下圖所示:

每一種觸發(fā)機制,都會由 this.argsthis.headerthis.contentWithInterceptors 三個函數(shù)去實現(xiàn) 動態(tài)函數(shù)的 code。代碼如下圖所示:

接下來我們看一看 this.contentWithInterceptors 函數(shù)如何生成我們事件觸發(fā)函數(shù)的函數(shù)體。

contentWithInterceptors

contentWithInterceptors 函數(shù)里包含兩個模塊, 一個是適配器 (interceptor), 一個 content 生成函數(shù)。 同時,HookCodeFactory 實例本身不會去實現(xiàn) content 函數(shù)的邏輯,會由繼承的實例去實現(xiàn)。整體結構代碼如下圖所示:

每個 hooks 鉤子函數(shù)的 CodeFactory 實例會去實現(xiàn) content 函數(shù)。 content 函數(shù)會調(diào)用 HookCodeFactory 實現(xiàn)的不同運行機制的方法( callTap、callTapsSeries、callTapsLooping、callTapsParallel), 構造出最終的函數(shù)體。實現(xiàn)代碼如下圖所示:

接下來,就是不同運行機制,根據(jù)不同的調(diào)用方式 ( syncasyncpromise ) 生成對應的執(zhí)行代碼。

動態(tài)函數(shù)

我們通過下面一個簡單案例,看 New Function 輸入的內(nèi)容是什么?

實例代碼如下:

const { SyncHook, AsyncSeriesHook } = require("tapable");

// 實例化 鉤子函數(shù) 定義形參
const syncHook = new SyncHook(["name"]);
const asyncSeriesHook = new AsyncSeriesHook(["name"])

//通過tap函數(shù)注冊事件
syncHook.tap("同步鉤子1", (name) => {
  console.log("同步鉤子1", name);
});

//通過tap函數(shù)注冊事件
asyncSeriesHook.tapAsync("同步鉤子1", (name) => {
  console.log("同步鉤子1", name);
});

//同步鉤子 通過call 發(fā)布事件
syncHook.call("古茗前端sync");
asyncSeriesHook.callAsync("古茗前端async");
asyncSeriesHook.promise("古茗前端promise")

在 HookCodeFactory 的 create 打印 fn, 實例代碼如圖所示:

sync 同步調(diào)用的輸出結果如下:

 function anonymous(name
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(name);

}

async 異步調(diào)用的輸出結果如下:

function anonymous(name, _callback
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(name, (function(_err0) {
if(_err0) {
_callback(_err0);
} else {
_callback();
}
}));

}

promise 調(diào)用的輸出結果如下

 function anonymous(name
) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function(_resolve, _reject) {
var _sync = true;
function _error(_err) {
if(_sync)
_resolve(Promise.resolve().then((function() { throw _err; })));
else
_reject(_err);
};
var _fn0 = _x[0];
_fn0(name, (function(_err0) {
if(_err0) {
_error(_err0);
} else {
_resolve();
}
}));
_sync = false;
}));

}

三種不同方式調(diào)用,內(nèi)部代碼實現(xiàn)差異還是比較清晰的。async 調(diào)用相較于 sync 多了回調(diào)函數(shù)。 asyncpromise 的區(qū)別再去返回 promise 還是回調(diào)函數(shù)。

最后,我們來用一些結構圖來總結一下 Tapable 中事件觸發(fā)的邏輯。

HookCodeFactory 會根據(jù)我們觸發(fā)的方式,生成我們對應 new Function 里面的 content, args, header。

content 最終會由 callTapsSeriescallTapsLooping、callTapsParallel 生成。每種生成方式都會包含 Done 處理、Error 處理以及 Result 處理。

總結

其他機制的 Hooks 鉤子實現(xiàn)原理大致是相同的, 這里就不一一贅述了。Tapable 是一個非常優(yōu)秀的庫,靈活擴展性高,許多優(yōu)秀的開源項目的插件化設計都借鑒或采納了 Tapable 的設計思想,是一個非常值得推薦學習的一個開源工具庫。

以上就是一文詳解Webpack中Tapable事件機制的詳細內(nèi)容,更多關于Webpack Tapable事件機制的資料請關注腳本之家其它相關文章!

相關文章

最新評論

福州市| 汽车| 临高县| 安顺市| 长岭县| 松溪县| 天柱县| 陇川县| 维西| 泾川县| 于田县| 且末县| 固镇县| 武胜县| 洞口县| 比如县| 四会市| 昭平县| 得荣县| 沙坪坝区| 正宁县| 东方市| 福建省| 和硕县| 漯河市| 湘西| 中西区| 广宁县| 翁牛特旗| 怀柔区| 海丰县| 大理市| 太仆寺旗| 屏山县| 鄂托克前旗| 临颍县| 尖扎县| 茂名市| 富平县| 江华| 禄劝|