The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
All Hook constructors take one optional argument, which is a list of argument names as strings.
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
The best practice is to expose all hooks of a class in a hooks
property:
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
break: new SyncHook(),
calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
};
}
/* ... */
}
Other people can now use these hooks:
const myCar = new Car();
// Use the tap method to add a consument
myCar.hooks.break.tap("WarningLampPlugin", () => warningLamp.on());
It's required to pass a name to identify the plugin/reason.
You may receive arguments:
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
For sync hooks tap
is the only valid method to add a plugin. Async hooks also support async plugins:
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);
})
The class declaring these hooks need to call them:
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());
});
}
}
The Hook will compile a method with the most efficient way of running your plugins. It generates code depending on:
- The number of registered plugins (none, one, many)
- The kind of registered plugins (sync, async, promise)
- The used call method (sync, async, promise)
- The number of arguments
- Whether interception is used
This ensures fastest possible execution.
All Hooks offer an additional interception API:
myCar.hooks.calculateRoutes.intercept({
call: (source, target, routesList) => {
console.log("Starting to calculate routes");
},
tap: (tapInfo) => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log(`${tapInfo.name} is doing it's job`);
return tapInfo; // may return a new tapInfo object
}
})