搜索引擎搜索tapable中文文檔,你會看見各類翻譯,點進去一看,確實是官方的文檔翻譯過來的,可是webpack的文檔確實還有不少須要改進的地方,既然是開源的爲何不去github上的tapable庫看呢,一看,確實,比webpack文檔上的描述得清楚得多.node
tapable 是一個相似於nodejs 的EventEmitter 的庫, 主要是控制鉤子函數的發佈與訂閱,控制着webpack的插件系.webpack的本質就是一系列的插件運行.webpack
Tapable庫 提供了不少的鉤子類, 這些類能夠爲插件建立鉤子git
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable");
npm install --save tapable
全部的鉤子構造函數,都接受一個可選的參數,(這個參數最好是數組,不是tapable內部也把他變成數組),這是一個參數的字符串名字列表github
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
最好的實踐就是把全部的鉤子暴露在一個類的hooks屬性裏面:web
class Car { constructor() { this.hooks = { accelerate: new SyncHook(["newSpeed"]), brake: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"]) }; } /* ... */ }
其餘開發者如今能夠這樣用這些鉤子npm
const myCar = new Car(); // Use the tap method to add a consument // 使用tap 方法添加一個消費者,(生產者消費者模式) myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
這須要你傳一個名字去標記這個插件:數組
你能夠接收參數promise
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
在同步鉤子中, tap 是惟一的綁定方法,異步鉤子一般支持異步插件異步
// promise: 綁定promise鉤子的API myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => { // return a promise return google.maps.findRoute(source, target).then(route => { routesList.add(route); }); }); // tapAsync:綁定異步鉤子的API myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => { bing.findRoute(source, target, (err, route) => { if(err) return callback(err); routesList.add(route); // call the callback callback(); }); }); // You can still use sync plugins // tap: 綁定同步鉤子的API myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => { const cachedRoute = cache.get(source, target); if(cachedRoute) routesList.add(cachedRoute); })
類須要調用被聲明的那些鉤子async
class Car { /* ... */ setSpeed(newSpeed) { // call(xx) 傳參調用同步鉤子的API this.hooks.accelerate.call(newSpeed); } useNavigationSystemPromise(source, target) { const routesList = new List(); // 調用promise鉤子(鉤子返回一個promise)的API return this.hooks.calculateRoutes.promise(source, target, routesList).then(() => { return routesList.getRoutes(); }); } useNavigationSystemAsync(source, target, callback) { const routesList = new List(); // 調用異步鉤子API this.hooks.calculateRoutes.callAsync(source, target, routesList, err => { if(err) return callback(err); callback(null, routesList.getRoutes()); }); } }
tapable會用最有效率的方式去編譯(構建)一個運行你的插件的方法,他生成的代碼依賴於一下幾點:
這些肯定了儘量快的執行.
每個鉤子均可以tap 一個或者多個函數, 他們如何運行,取決於他們的鉤子類型
此外,鉤子能夠是同步的,也能夠是異步的,Sync, AsyncSeries 和 AsyncParallel ,從名字就能夠看出,哪些是能夠綁定異步函數的
myHook.tap()
, myHook.tapAsync()
和 myHook.tapPromise()
.).他會按順序的調用每一個方法.全部鉤子都提供額外的攔截器API
// 註冊一個攔截器 myCar.hooks.calculateRoutes.intercept({ call: (source, target, routesList) => { console.log("Starting to calculate routes"); }, register: (tapInfo) => { // tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... } console.log(`${tapInfo.name} is doing its job`); return tapInfo; // may return a new tapInfo object } })
call:(...args) => void
當你的鉤子觸發以前,(就是call()以前),就會觸發這個函數,你能夠訪問鉤子的參數.多個鉤子執行一次
tap: (tap: Tap) => void
每一個鉤子執行以前(多個鉤子執行多個),就會觸發這個函數
loop:(...args) => void
這個會爲你的每個循環鉤子(LoopHook, 就是類型到Loop的)觸發,具體何時沒說
register:(tap: Tap) => Tap | undefined
每添加一個Tap
都會觸發 你interceptor上的register,你下一個攔截器的register 函數獲得的參數 取決於你上一個register返回的值,因此你最好返回一個 tap 鉤子.
插件和攔截器均可以選擇加入一個可選的 context對象, 這個能夠被用於傳遞隨意的值到隊列中的插件和攔截器.
myCar.hooks.accelerate.intercept({ context: true, tap: (context, tapInfo) => { // tapInfo = { type: "sync", name: "NoisePlugin", fn: ... } console.log(`${tapInfo.name} is doing it's job`); // `context` starts as an empty object if at least one plugin uses `context: true`. // 若是最少有一個插件使用 `context` 那麼context 一開始是一個空的對象 // If no plugins use `context: true`, then `context` is undefined // 如過tap進去的插件沒有使用`context` 的 那麼內部的`context` 一開始就是undefined if (context) { // Arbitrary properties can be added to `context`, which plugins can then access. // 任意屬性均可以添加到`context`, 插件能夠訪問到這些屬性 context.hasMuffler = true; } } }); myCar.hooks.accelerate.tap({ name: "NoisePlugin", context: true }, (context, newSpeed) => { if (context && context.hasMuffler) { console.log("Silence..."); } else { console.log("Vroom!"); } });
一個 HookMap是一個Hooks映射的幫助類
const keyedHook = new HookMap(key => new SyncHook(["arg"]))
keyedHook.tap("some-key", "MyPlugin", (arg) => { /* ... */ }); keyedHook.tapAsync("some-key", "MyPlugin", (arg, callback) => { /* ... */ }); keyedHook.tapPromise("some-key", "MyPlugin", (arg) => { /* ... */ });
const hook = keyedHook.get("some-key"); if(hook !== undefined) { hook.callAsync("arg", err => { /* ... */ }); }
Public(權限公開的):
interface Hook { tap: (name: string | Tap, fn: (context?, ...args) => Result) => void, tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void, tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void, intercept: (interceptor: HookInterceptor) => void } interface HookInterceptor { call: (context?, ...args) => void, loop: (context?, ...args) => void, tap: (context?, tap: Tap) => void, register: (tap: Tap) => Tap, context: boolean } interface HookMap { for: (key: any) => Hook, tap: (key: any, name: string | Tap, fn: (context?, ...args) => Result) => void, tapAsync: (key: any, name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void, tapPromise: (key: any, name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void, intercept: (interceptor: HookMapInterceptor) => void } interface HookMapInterceptor { factory: (key: any, hook: Hook) => Hook } interface Tap { name: string, type: string fn: Function, stage: number, context: boolean }
Protected(保護的權限),只用於類包含的(裏面的)鉤子
interface Hook { isUsed: () => boolean, call: (...args) => Result, promise: (...args) => Promise<Result>, callAsync: (...args, callback: (err, result: Result) => void) => void, } interface HookMap { get: (key: any) => Hook | undefined, for: (key: any) => Hook }
把其餘的Hook 重定向(轉化)成爲一個 MultiHook
const { MultiHook } = require("tapable"); this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);
OK 全部的內容我都已翻譯完成.
其中有不少不是直譯,這樣寫下來感受就是按照原文的脈絡從新寫了一遍....,應該能更清楚明白,要不是怕丟臉我就給個原創了,哈哈.
以後, 我還會寫一篇完整的原創解析,直擊源碼,搞定tapable, 徹底瞭解webpack插件系統(webpack原本就是一個插件的事件流), 很久沒寫原創了. 我本身也很期待.