tapable

391 阅读1分钟
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 => { /* ... */ });
}

参考资料:

github.com/webpack/tap…