Tapable v1.1文档翻译+简单解释

Tapable是一个为插件创造钩子的库,他也是webpack的核心库。Tapable v1以后的版本跟以前的用法差异很是大,主要区别是之前用继承class A extends tapable, 如今直接在类里面定义私有成员this.hooks. 貌似网上不少都是老版本的用法,鉴于立刻就要v2了,翻译走一波,顺便点一下目前1.1版本的坑
原文 
github.com/webpack/tap…

Tapable

tapable提供不少钩子类(Hook classes),他们能够被用来为插件创造钩子。javascript

const {
	SyncHook,                    // 同步钩子
	SyncBailHook,                // 同步早退钩子
	SyncWaterfallHook,           // 同步瀑布钩子
	SyncLoopHook,                // 同步循环钩子
	AsyncParallelHook,           // 异步并发钩子
	AsyncParallelBailHook,       // 异步并发可早退钩子
	AsyncSeriesHook,             // 异步顺序钩子
	AsyncSeriesBailHook,         // 异步顺序可早退钩子
	AsyncSeriesWaterfallHook     // 异步顺序瀑布钩子
 } = require("tapable");复制代码

安装

npm install --save tapable复制代码

使用

全部的钩子类的构造器都接受一个可选参数,它是一个 这个钩子所接受参数的参数名数组。java

const hook = new SyncHook(["arg1", "arg2", "arg3"]);复制代码

最佳作法是一次性在hooks属性里面定义好所用的钩子:webpack

class Car {
	constructor() {
		this.hooks = {
                        // 如下分别是油门,刹车,计算路线钩子
			accelerate: new SyncHook(["newSpeed"]),
			brake: new SyncHook(),
			calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
		};
	}

	/* ... */
}复制代码

其余人如今就能使用以上的钩子了:git

const myCar = new Car();

// 使用tap方法添加具体的执行逻辑
myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on()); // 亮灯插件,逻辑为刹车时亮灯复制代码

为了定位你的插件,一个合适的名字(上面WarningLampPlugin)是必须的。github

你定义的函数能够接收参数web

myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));复制代码

对于同步的钩子,tap是仅有的添加插件的有效方法。异步钩子还支持异步插件,除了tap外,还有tapPromise,tapAsync等方法。typescript

myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
	// 谷歌的找路线的异步方法返回promise
	return google.maps.findRoute(source, target).then(route => {
		routesList.add(route);
	});
});

myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => {
        // bing的找路线异步方法用的callback方式
	bing.findRoute(source, target, (err, route) => {
		if(err) return callback(err);
		routesList.add(route);
		// call the callback
		callback();
	});
});

// 异步钩子也可使用同步方法,好比下例取出缓存的版本
myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => {
	const cachedRoute = cache.get(source, target);
	if(cachedRoute)
		routesList.add(cachedRoute);
})复制代码

而后声明了这些钩子的类须要用他们时:npm

class Car {
	/* 我做为一辆车,我只在意我有如下功能,但这些功能的具体实现交给了第三方, * 我给这些第三方提供能修改逻辑的权限就行了 */

	setSpeed(newSpeed) {
                // 下面的call没有返回值
		this.hooks.accelerate.call(newSpeed);
	}

	useNavigationSystemPromise(source, target) {
		const routesList = new List();
		return this.hooks.calculateRoutes.promise(source, target, routesList).then((res) => {
		    // res是undefined 
                    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());
		});
	}
}复制代码

:此处例子用的是SyncHook和AsyncParallelHook, 因此他们是没有返回值的,即便你返回了也只能获得undefined。要想获得返回值请用SyncWaterfallHook和AsyncSeriesWaterfallHook!并且注意waterfall钩子总会返回值(即便你不return))数组

咱们会用最高效的方式编译一个运行你提供的插件的方法,生成的代码取决于:promise

  • 注册插件的数量(0,1,多个)
  • 注册插件的类型(同步,异步回调,异步promise)
  • 使用的调用方法(call, promise,callAsync)
  • 参数的数量
  • 是否用拦截器
这点特性保证了最快的执行。

钩子类型

每一个钩子能够关联多个函数,它们怎么执行取决于钩子类型:
  • 基础钩子(名字里没有waterfall, bail, loop的):这种钩子简单地按顺序调用每一个添加的函数。
  • 瀑布钩子(waterfall):也会按顺序调用函数,不一样的是,他会传递每一个函数的返回值到下一个函数。若是你不显式地return值,那么函数会返回你的第一个参数当返回值,因此记得总要返回一个值(我会return 'defined';)!
  • 早退钩子(bail):当有任何添加的函数返回了任何值,这种钩子就会中止执行后面的函数。
  • 循环钩子(loop):还在开发中...
另外钩子还分为同步或异步:
  • 同步(sync):同步钩子只能添加同步函数(使用myHook.tap())
  • 异步序列(AsyncSeries):能够添加同步方法,基于回调的异步方法,基于promise的异步方法(使用.tap(), .tapAsync(), .tapPromise())。按出现的顺序调用添加的异步方法。
  • 异步平行(AsyncParallel):跟上面同样,只不过并发的调用添加的异步方法。
你能够经过这些钩子类的名字判断他们的模型, 好比AsyncSeriesWaterfallHook表明按顺序执行异步方法,而且按顺序传递返回值。

拦截器

全部的钩子都提供拦截器接口:
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对象,但只读。
register: (tap: Tap) => Tap | undefined 当你的自定义插件被添加进钩子时触发,此时你能够访问这个tap对象,可修改并返回新的tap对象。
loop: (...args) => void 循环钩子的每一个循环都会被触发。

上下文

插件和拦截器能够可选地访问上下文对象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` 从一个空对象开始若是至少有一个插件里写了 `context: true`.
		// 若是没有插件定义 `context: true`, 那么 `context` 是 undefined.
		if (context) {
			// 你能够添加任意值,以后的插件都能访问到.
			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

这是一个钩子的字典帮助类,比起你直接用js的字典类new Map([['key', hook]]),这个类可能用起来更简单:
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 => { /* ... */ });
}复制代码

Hook/HookMap接口

公有的

interface Hook {
	tap: (name: string | Tap, fn: (context?, ...args) => Result) => void,
	tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
	tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void,
	intercept: (interceptor: HookInterceptor) => void
}

interface HookInterceptor {
	call: (context?, ...args) => void,
	loop: (context?, ...args) => void,
	tap: (context?, tap: Tap) => void,
	register: (tap: Tap) => Tap,
	context: boolean
}

interface HookMap {
	for: (key: any) => Hook,
	tap: (key: any, name: string | Tap, fn: (context?, ...args) => Result) => void,
	tapAsync: (key: any, name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
	tapPromise: (key: any, name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void,
	intercept: (interceptor: HookMapInterceptor) => void
}

interface HookMapInterceptor {
	factory: (key: any, hook: Hook) => Hook
}

interface Tap {
	name: string,
	type: string
	fn: Function,
	stage: number,
	context: boolean
}复制代码

protected(定义钩子的类才能用的)

interface Hook {
	isUsed: () => boolean,
	call: (...args) => Result,
	promise: (...args) => Promise<Result>,
	callAsync: (...args, callback: (err, result: Result) => void) => void,
}

interface HookMap {
	get: (key: any) => Hook | undefined,
	for: (key: any) => Hook
}复制代码

MultiHook类

这是一个像钩子的类,用来重定向钩子的插件到其余钩子:

const { MultiHook } = require("tapable");

this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);复制代码
相关文章
相关标签/搜索