const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
複製代碼
Tapable提供了上述9中hook
。詳細的api方法能夠查看Tapable文檔javascript
Tapable主要由兩個重要部分組成java
下面以SyncHook
爲例,咱們看看Hook處理的整個流程。SyncHook
是Tapable中最容易理解的Hook,所以做爲Demo進行分析。webpack
Demo代碼以下:git
class Car {
constructor() {
this.hooks = {
accelerate: new SyncHook(["newSpeed"]),
};
}
setSpeed(newSpeed) {
this.hooks.accelerate.call(newSpeed);
}
}
const myCar = new Car();
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed1 => {
console.log(`${newSpeed1}`)
});
myCar.hooks.accelerate.tap("LoggerPlugin2", newSpeed2 => {
console.log(`${newSpeed2}`)
})
myCar.hooks.accelerate.tap("LoggerPlugin3", newSpeed3 => {
console.log(`${newSpeed3}`)
})
myCar.setSpeed(100);
複製代碼
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed1 => {
console.log(`${newSpeed1}`)
});
複製代碼
首先咱們一塊兒看看tap
方法(代碼通過部分刪減和轉換)。github
tap(options, fn) {
if (typeof options === "string") options = { name: options };
options = Object.assign({ type: "sync", fn: fn }, options);
this.taps.push(item);
}
複製代碼
tap方法主要是把輸入的兩個參數(plugin的名稱和plugin的主要邏輯)組成一個帶有type的對象,而後存放到taps
數組中。web
taps
數組中存放的對象以下所示。api
{
name: "LoggerPlugin1",
type: "sync",
fn: (newSpeed1) => {
console.log(`${newSpeed1}`)
}
}
複製代碼
接下來咱們一塊兒看看hook的call方法作了些什麼。數組
this.hooks.accelerate.call(newSpeed)
複製代碼
其實call是一個閉包。完成了把註冊好的plugin按照必定的規則執行。而這個執行的規則則是由_createCall
建立。_createCall
會調用compile
方法,compile
是由Hook
的子類進行實現(這裏就是由SyncHook
來實現)。閉包
class Hook {
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.call = this._call;
this._x = undefined;
}
_createCall(type) {
return this.compile({
taps: this.taps,
args: this._args,
type: type
});
}
tap(options, fn) {...}
...
}
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
this[name] = this._createCall(type);
return this[name](...args);
};
}
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
})
複製代碼
每一個Hook都有一個對應的HookCodeFactory
,HookCodeFactory
的做用就是建立一個根據規則建立待執行plugin的函數。HookCodeFactory
裏面大部分代碼是都是在拼接函數。異步
const factory = new SyncHookCodeFactory();
class SyncHook extends Hook {
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
複製代碼
如下我將簡化SyncHookCodeFactory
代碼,代碼和源代碼並不一致,只是爲了說明code是怎樣生成的。
HookCodeFactory,是用動態Function
構建Hook觸發的Plugin執行方法。
爲何要用new Function?
由於create的過程是動態的,不可能預先寫好方法,所以用動態的Function也是一種解決方案。
class SyncHookCodeFactory {
constructor() {
this.options = undefined;
this._args = [];
}
create(options) {
this.init(options);
const fn = new Function(
this.args(),
this.content()
);
return fn;
}
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
init(options) {
this.options = options;
this._args = options.args.slice();
}
content() {
let code = '"use strict";\nvar _x = this._x;\n';
if (this.options.taps.length === 0) { return code; }
for (let j = this.options.taps.length - 1; j >= 0; j--) {
code += `var _fn${j} = ${this.getTapFn(j)};\n`;
code += `_fn${j}(${this.args()});\n`;
}
return code;
}
args() {
return this._args.join(', ');
}
getTapFn(idx) {
return `_x[${idx}]`;
}
}
複製代碼
在本例子中(串行鉤子),執行factory的create方法後,會返回一個函數,參數即爲call
方法傳入的參數:
function anonymous(newSpeed) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(newSpeed);
var _fn1 = _x[1];
_fn1(newSpeed);
var _fn2 = _x[2];
_fn2(newSpeed);
}
複製代碼
須要說一下,這裏的_x
,其實由taps.map(t => t.fn)
獲得的。簡單來講就是註冊的plugin列表。 下面簡單地把_x
數組所表明的內容列出來。
// 以_x[0]爲例子
_x[0] = newSpeed1 => {
console.log(`${newSpeed1}`)
}
複製代碼
由於在一篇博文中看到, AsyncParallelHook
和AsyncSeriesHook
兩個執行異步的方法(文中是settimeout),執行時間是不一致的。AsyncParallelHook
和它名字同樣,是並行執行的;相反AsyncSeriesHook
是串行執行的。
因爲名字都是帶async的,給人的錯覺是都是異步並行。因而作了Demo驗證一下。
class Car {
constructor() {
this.hooks = {
// 這裏是AsyncParallelHook與AsyncSeriesHook切換
// calculateRoutes: new AsyncParallelHook(["name"])
calculateRoutes: new AsyncSeriesHook(["name"])
};
}
useNavigationSystemAsync(name) {
this.hooks.calculateRoutes.callAsync(name, err => {
console.log(err);
});
}
}
const myCar = new Car();
myCar.hooks.calculateRoutes.tapAsync("TapAsync1", (name, cb) => {
console.log(name, 1);
cb();
});
myCar.hooks.calculateRoutes.tapAsync("TapAsync2", (name, cb) => {
console.log(name, 2);
cb();
});
myCar.useNavigationSystemAsync('webpack')
複製代碼
AsyncSeriesHookFactory產生的代碼以下
function anonymous(name, _callback) {
"use strict";
function _next0() {
const _fn1 = _x[1];
_fn1(name, _err1 => {
if (_err1) {
_callback(_err1);
} else {
_callback();
}
});
}
const _fn0 = _x[0];
_fn0(name, _err0 => {
if (_err0) {
_callback(_err0);
} else {
_next0();
}
});
}
複製代碼
AsyncParallelHookFactory產生的代碼以下
function anonymous(name, _callback) {
"use strict";
do {
var _counter = 2;
var _done = () => {
_callback();
};
if (_counter <= 0) { break; }
const _fn0 = _x[0];
_fn0(name, _err0 => {
if (_err0) {
if (_counter > 0) {
_callback(_err0);
_counter = 0;
}
} else if (--_counter === 0) { _done(); }
});
if (_counter <= 0) { break; }
const _fn1 = _x[1];
_fn1(name, _err1 => {
if (_err1) {
if (_counter > 0) {
_callback(_err1);
_counter = 0;
}
} else if (--_counter === 0) { _done(); }
});
if (_counter <= 0) { break; }
} while (false);
}
複製代碼
首先,咱們能夠得知若是在callback中傳入參數,後續的插件都都不會執行。
_fn0
(即第一個插件)後,纔會調用_next0()
執行_fn1
。err => {
console.log(err);
}
複製代碼
Tapable的寫法與傳統的事件驅動機制不太同樣,但它作的事情都是差很少。都是須要有一個訂閱「事件」方法,和觸發「事件」方法。
雖說機制比較類似,但提供了9種基本的觸發策略的Tapable能夠說更增強大。
先說說它們之間類似的地方,以SyncHook
爲例來對比的話,SyncHook
基本能夠用EventEmitter實現。
Tapable的tap
做用至關於EventEmitter的on
;而call
做用就至關於emit
;
// SyncHook
const accelerateHook = new SyncHook(["newSpeed"])
accelerateHook.tap("LoggerPlugin", newSpeed => {
console.log(`${newSpeed}`)
});
accelerateHook.call(100);
// Node EventEmitter
const eventEmitter = new EventEmitter();
eventEmitter.on("accelerate", newSpeed1 => {
console.log(`${newSpeed1}`)
});
eventEmitter.emit("accelerate", 100);
複製代碼
EventEmitter事件訂閱者之間是無感知的,相互沒法影響的。WebpackTapable的事件訂閱者之間便可以是無感知也能夠是相互影響。
舉個例子說明,好比SyncWaterfallHook
中前一個訂閱者的回調返回值會做爲後一個訂閱者的輸入參數。
const swfh = new SyncWaterfallHook(['param']);
swfh.tap('a', function (param) {
console.log(param);
return param + 1;
});
swfh.tap('b', function (param) {
console.log(param);
return param + 2;
});
swfh.tap('c', function (param) {
console.log(param);
});
swfh.call(1);
// console
/* 1 2 4 */
複製代碼
不只如此,Tapable還提供Interception
,Context
,HookMap
和MultiHook
等玩法。