Tapable是一個爲插件創造鉤子的庫,他也是webpack的核心庫。Tapable v1以後的版本跟以前的用法差異很是大,主要區別是之前用繼承class A extends tapable, 如今直接在類裏面定義私有成員this.hooks. 貌似網上不少都是老版本的用法,鑑於立刻就要v2了,翻譯走一波,順便點一下目前1.1版本的坑
原文github.com/webpack/tap…
tapable提供不少鉤子類(Hook classes),他們能夠被用來爲插件創造鉤子。javascript
const {
SyncHook, // 同步鉤子
SyncBailHook, // 同步早退鉤子
SyncWaterfallHook, // 同步瀑布鉤子
SyncLoopHook, // 同步循環鉤子
AsyncParallelHook, // 異步併發鉤子
AsyncParallelBailHook, // 異步併發可早退鉤子
AsyncSeriesHook, // 異步順序鉤子
AsyncSeriesBailHook, // 異步順序可早退鉤子
AsyncSeriesWaterfallHook // 異步順序瀑布鉤子
} = require("tapable");複製代碼
npm install --save tapable複製代碼
全部的鉤子類的構造器都接受一個可選參數,它是一個 這個鉤子所接受參數的參數名數組。java
const hook = new SyncHook(["arg1", "arg2", "arg3"]);複製代碼
最佳作法是一次性在hooks屬性裏面定義好所用的鉤子:webpack
class Car {
constructor() {
this.hooks = {
// 如下分別是油門,剎車,計算路線鉤子
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
}
/* ... */
}複製代碼
其餘人如今就能使用以上的鉤子了:git
const myCar = new Car();
// 使用tap方法添加具體的執行邏輯
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on()); // 亮燈插件,邏輯爲剎車時亮燈複製代碼
爲了定位你的插件,一個合適的名字(上面WarningLampPlugin)是必須的。github
你定義的函數能夠接收參數web
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));複製代碼
對於同步的鉤子,tap是僅有的添加插件的有效方法。異步鉤子還支持異步插件,除了tap外,還有tapPromise,tapAsync等方法。typescript
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
// 谷歌的找路線的異步方法返回promise
return google.maps.findRoute(source, target).then(route => {
routesList.add(route);
});
});
myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => {
// bing的找路線異步方法用的callback方式
bing.findRoute(source, target, (err, route) => {
if(err) return callback(err);
routesList.add(route);
// call the callback
callback();
});
});
// 異步鉤子也能夠使用同步方法,好比下例取出緩存的版本
myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => {
const cachedRoute = cache.get(source, target);
if(cachedRoute)
routesList.add(cachedRoute);
})複製代碼
而後聲明瞭這些鉤子的類須要用他們時:npm
class Car {
/* 我做爲一輛車,我只在意我有如下功能,但這些功能的具體實現交給了第三方, * 我給這些第三方提供能修改邏輯的權限就行了 */
setSpeed(newSpeed) {
// 下面的call沒有返回值
this.hooks.accelerate.call(newSpeed);
}
useNavigationSystemPromise(source, target) {
const routesList = new List();
return this.hooks.calculateRoutes.promise(source, target, routesList).then((res) => {
// res是undefined
return routesList.getRoutes();
});
}
useNavigationSystemAsync(source, target, callback) {
const routesList = new List();
this.hooks.calculateRoutes.callAsync(source, target, routesList, err => {
if(err) return callback(err);
callback(null, routesList.getRoutes());
});
}
}複製代碼
(注:此處例子用的是SyncHook和AsyncParallelHook, 因此他們是沒有返回值的,即便你返回了也只能獲得undefined。要想獲得返回值請用SyncWaterfallHook和AsyncSeriesWaterfallHook!並且注意waterfall鉤子總會返回值(即便你不return))數組
咱們會用最高效的方式編譯一個運行你提供的插件的方法,生成的代碼取決於:promise
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
}
})複製代碼
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` 從一個空對象開始若是至少有一個插件裏寫了 `context: true`.
// 若是沒有插件定義 `context: true`, 那麼 `context` 是 undefined.
if (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!");
}
});複製代碼
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 => { /* ... */ });
}複製代碼
公有的
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
}複製代碼
這是一個像鉤子的類,用來重定向鉤子的插件到其餘鉤子:
const { MultiHook } = require("tapable");
this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);複製代碼