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]);複製代碼
相關文章
相關標籤/搜索