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 consumentmyCar.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 pluginsmyCar.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 => { /* ... */ });}复制代码
参考资料: