webpack是一系列插件的集合。webpack执行采用的是事件流机制,tapable则是webpack事件流机制所依赖的核心库。
tapable提供一系列钩子类,以供插件使用。
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");复制代码
示例:
定义:
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
brake: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
}
/* ... */
}复制代码
使用:
const myCar = new Car();
// Use the tap method to add a consument
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());复制代码
可以增加参数:
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));复制代码
异步执行的钩子:
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
// return a promise
return google.maps.findRoute(source, target).then(route => {
routesList.add(route);
});
});
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
myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => {
const cachedRoute = cache.get(source, target);
if(cachedRoute)
routesList.add(cachedRoute);
})复制代码
调用:
class Car {
/* ... */
setSpeed(newSpeed) {
this.hooks.accelerate.call(newSpeed);
}
useNavigationSystemPromise(source, target) {
const routesList = new List();
return this.hooks.calculateRoutes.promise(source, target, routesList).then(() => {
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());
});
}
}复制代码
钩子类型:
每个钩子可以添加一个或多个方法,它们的执行分为以下几种方式:
- 基础类型(名字不带“Waterfall”, “Bail” 或“Loop”)。调用时直接执行每个方法。
- Waterfall。执行每个方法,但会传递返回值到下一个方法。
- Bail。当返回值不为undefined时提前结束。
同样还可以分成异步钩子和同步钩子。
- Sync。同步钩子,只能用同步方法添加函数。(using myHook.tap())
- AsyncSeries。同步、回调和promise方式添加都可以(using myHook.tap(), myHook.tapAsync() and myHook.tapPromise())。它们会依次执行。
- AsyncParallel。同样可以同步、回调和promise方式添加(using myHook.tap(), myHook.tapAsync() and myHook.tapPromise())。但是它们会并发执行。
可以根据类名判断钩子类型。比如AsyncSeriesWaterfallHook代表异步按顺序执行并把返回值传递给下一个方法。
拦截器:
所以钩子都提供拦截器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 tap方法触发,提供不可改变的Tap对象。
register: (tap: Tap) => Tap | undefined 注册Tap时触发,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`.
// If no plugins use `context: true`, then `context` is undefined.
if (context) {
// Arbitrary properties can be added to `context`, which plugins can then access.
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:
一个钩子map的辅助类
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 => { /* ... */ });
}复制代码
参考资料: