The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.
const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSequencialHook,
	AsyncSequencialBailHook,
	AsyncWaterfallHook
 } = 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
	}
})