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
等玩法。