tapable基本概念
tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在。
Tapable分类
- 按照同步和异步分类可以分成:
- 同步和异步
- 异步又可以分为异步串行和异步并行
- 按照返回值分成:
- basic 不关心返回值,有没有返回值都无所谓
- SyncHook 同步
- AsyncParallelHook 异步并行
- AsyncSeriesHook 异步串行
- Waterfall 当前事件参数是上一个事件的返回值,如果上一个事件没有返回值会取在上一层的
- SyncWaterfallHook 同步瀑布
- AsyncSeriesWaterfallHook 异步串行瀑布
- bail 返回值如果非undefined就终止下一个事件的执行
- SyncBailHook 同步带保险
- AsyncParallelBailHook 异步并行带保险
- AsyncSeriesBailHook 异步串行带保险
- loop 循环执行直到返回值是非undefined,如果返回值不是undefined,就会从头继续向下执行,再次遇到非undefined则继续从头执行
- SyncLoopHook 同步循环
- basic 不关心返回值,有没有返回值都无所谓
tapable钩子函数的基本使用
1. SyncHook(同步)
- SyncHook是一个类(构造函数)
- 参数是一个数组,用来指定事件回调函数的参数列表,里面变量名没有用,但是数组的长度是有用的(如果数组只有一项,但是实际的时候传递两项的话,后面的值就无法接收,为undefined)
- 如果没有参数,可以传一个空数组
- 回调函数的执行是按照注册顺序执行的,先注册先执行
const { SyncHook } = require('tapable');const hook = new SyncHook(['name', 'age']);hook.tap('1', (name, age) => { console.log(name, age, 1) }) // => 'test name', 'test age', 1; 可以没有返回值,不关心返回值hook.tap('2', (name, age) => { console.log(name, age, 2) }) // => 'test name', 'test age', 2;hook.call('test name', 'test age');
2. SyncBailHook(同步带保险)
- 当事件的回调函数返回值是undefined的话,就会继续执行后续事件函数
- 如果事件的回调函数返回的是非undefined的话,就会停止后续的事件函数执行
const { SyncBailHook } = require('tabable');const hook = new SyncBailHook(['name', 'age']);hook.tap('1', (name, age) => { console.log(name, age, 1); return undefined }); // => 'test name', 'test age', 1;hook.tap('2', (name, age) => { console.log(name, age, 2); return 2}; // => 'test name', 'test age', 2; 当返回值非undefined的时候,就会停止后续事件函数的执行hook.tap('3', (name, age) => { console.log(name, age, 3); return 3});hook.call('test name', 'test age');
3. SyncWaterfaillHoos(同步瀑布)
- 下一次的参数会采用上一次的返回值,如果上一次没有返回值会采用再上一次的返回值用来当参数 ```javascript const { SyncWaterfaillHook } = require(‘tapable’); const hook = new SyncWaterfaillHoos([‘name’, ‘age’]);
hook.tap(‘1’, (name, age) => { console.log(name, age, 1); return ‘A’}); // => ‘test name’, ‘test age’, 1; hook.tap(‘2’, (name, age) => { console.log(name, age, 2); }); // => ‘A’, ‘test age’, 2; hook.tap(‘3’, (name, age) => { console.log(name, age, 3); return ‘C’}); // => ‘A’, ‘test age’, 1;
hook.call(‘test name’, ‘test age’);
<a name="vqRAc"></a>### 4. SyncLoopHook(同步循环)- 遇到返回值非false的情况下就会从头执行```javascriptconst { SyncLoopHook } = require('tapable');const hook = new SyncLoopHook();let counter1 = 0, counter2 = 0, counter3 = 0;hook.tap('1', () => {console.log('counter1', counter1);if (++counter1 === 1) {counter1 = 0;return}return true})hook.tap('2', () => {console.log('counter2', counter2);if (++counter2 === 2) {counter2 = 0;return}return true})hook.tap('3', () => {console.log('counter3', counter3);if (++counter3 === 3) {counter3 = 0;return}return true})hook.call('test,', '29')// => 最终输出15次, 00010, 00011,00012
5. AsyncParallelHoos(异步并行)
- 异步并行,类似于Promise.all执行时长取决于耗时最长的任务
- 有两种写法,第一种hook.tapAsync和hook.callAsync,第二种是hook.tapPromise和hook.promise
第一种写法
const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);
console.time('cost')
hook.tapAsync('1', (name, callback) => {
console.log(name, 1);
setTimeout(() => {
callback();
}, 1000);
})
hook.tapAsync('1', (name, callback) => {
console.log(name, 2);
setTimeout(() => {
callback();
}, 2000);
})
hook.tapAsync('1', (name, callback) => {
console.log(name, 3);
setTimeout(() => {
callback();
}, 3000);
});
hook.callAsync('test', () => {
console.timeEnd('cost')
});
第二种写法
const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);
console.time('cost')
hook.tapPromise('1', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1, name);
resolve();
}, 1000);
})
});
hook.tapPromise('2', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(2, name);
resolve();
}, 2000);
})
});
hook.tapPromise('3', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(3, name);
resolve();
}, 3000);
})
});
hook.promise('test name').then(res => {
console.timeEnd('cost')
})
6. AsyncParallelBailHoos(异步并行保险)
- 如果resolve中传递参数,就终止后面的事件执行 ```javascript const { AsyncParallelBailHook } = require(‘tapable’); const hook = new AsyncParallelBailHook([‘name’]);
console.time(‘cost’)
hook.tapPromise(‘1’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(1); }, 1000); }) });
hook.tapPromise(‘2’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 2000); }) });
hook.tapPromise(‘3’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 3000); }) });
hook.promise(‘test name’).then(res => { console.timeEnd(‘cost’) })
<a name="q3APT"></a>
### 7. AsyncSeriesHook(异步串行)
- 与并行不同,会一个一个执行,先执行第一个在执行第二个在执行第三个,耗时取决于所有事件时间加到一起
```javascript
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['name']);
console.time('cost')
hook.tapPromise('1', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1, name);
resolve();
}, 1000);
})
});
hook.tapPromise('2', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(2, name);
resolve();
}, 2000);
})
});
hook.tapPromise('3', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(3, name);
resolve();
}, 3000);
})
});
hook.promise('test name').then(res => {
console.timeEnd('cost')
})
8. AsyncSeriesBailHook(异步串行保险)
- 和上面的类型,只要有返回值,就终止下面事件的执行 ```javascript const { AsyncSeriesBailHook } = require(‘tapable’); const hook = new AsyncSeriesBailHook([‘name’]);
console.time(‘cost’)
hook.tapPromise(‘1’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(1, name); resolve(); }, 1000); }) });
hook.tapPromise(‘2’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(2, name); resolve(1); }, 2000); }) });
hook.tapPromise(‘3’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(3, name); resolve(); }, 3000); }) });
hook.promise(‘test name’).then(res => { console.timeEnd(‘cost’) });
// => 1 ‘test name’ 2 ‘test name’ cost: 3021.173ms
<a name="qTw9i"></a>
### <br />
<a name="DguUQ"></a>
### 9. AsyncSeriesWaterfallHook(异步串行瀑布)
- 串行执行,事件时间取合集
- 参数会根据返回值来做下一个事件函数的参数
```javascript
const { AsyncSeriesWaterfallHook } = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name']);
console.time('cost')
hook.tapPromise('1', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(1, name);
resolve();
}, 1000);
})
});
hook.tapPromise('2', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(2, name);
resolve(1);
}, 2000);
})
});
hook.tapPromise('3', (name) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(3, name);
resolve();
}, 3000);
})
});
hook.promise('test name').then(res => {
console.timeEnd('cost')
});
// => 1 'test name'
2 'test name'
3 1
cost: 6017.594ms
tapable钩子函数的基本实现
/SyncHook.js(同步)
const Hook = require('./Hook');
const HookCodeFactory = require('./HookCodeFactory');
const factory = new HookCodeFactory();
class SyncHook extends Hook {
compile(options) {
factory.setup(this, options);
return facrory.create(options);
}
}
module.export = SyncHook
/AsyncParallelHook.js(异步并行)
const Hook = require("./hook");
const HookCodeFactory = require('./HookCodeFactory');
class AsyncParallelHookCodeFactory extends HookCodeFactory {
// 子类实现的
// onDone 是为了方便里面处理不同的钩子的执行函数 比如_callback或_resolve
content({ onDone }) {
return this.callTapsParallel({ onDone });
}
}
let factory = new AsyncParallelHookCodeFactory();
class AsyncParallelHook extends Hook {
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
module.exports = AsyncParallelHook;
/Hook.js
class Hook {
constructor(args) {
// 事件回调参数的函数列表数组
this.args = args;
// 放置所有的事件函数对象
this.taps = [];
this._x = null;
// 同步
this.call = CALL_DELEGATE;
// 异步
this.callAsync = ASYNC_CALL_DELEGATE;
// Promise
this.promise = PROMISE_DELEGATE;
}
// 同步
tap(options, fn) {
this._tap('sync', options, fn);
}
// 异步
tapAsync(options, fn) {
this._tap('async', options, fn);
}
// promise
tapPromise(options, fn) {
this._tap('promise', options, fn);
}
_tap(type, options, fn) {
if (typeof options === 'string') {
options = { name: options }
}
let tapInfo = { ...options, type, fn };
this._insert(tapInfo);
}
_insert (tapInfo) {
this.taps.push(tapInfo)
}
_createCall(type) {
// compail 是子类实现的方法
return this.compile({
taps: this.taps,
args: this.args,
type
});
}
}
// 动态创建call方法
const CALL_DELEGATE = function(...args) {
this.call = this._createCall('sync');
return this.call(...args);
}
const ASYNC_CALL_DELEGATE = function(...args) {
this.callAsync = this._createCall('async');
return this.callAsync(...args);
}
const PROMISE_DELEGATE = function(...args) {
this.promise = this._createCall('promise');
return this.promise(...args);
}
module.exports = Hook
/HookCodeFactory.js
class HookCodeFactory {
setup(hook, options) {
hook._x = options.taps.map(tap => tap.fn);
}
init(options) {
this.options = options;
}
deInit() {
this.options = null;
}
args(options = {}) {
let { before, after } = options;
let allArgs = this.options.args;
if (before) allArgs = [ before, ...allArgs ];
if (after) allArgs = [ ...allArgs, after ];
return allArgs.join(',')
}
header() {
let code = '';
code += `var _x = this._x;\n`;
return code
}
create(options) {
this.init(options);
let { type } = options;
let fn;
switch (type) {
case 'sync':
fn = new Function(
this.args(),
this.header() + this.content()
)
break;
case 'async':
fn = new Function(
this.args({ after: '_callback' }),
// 这里的content是子类实现的
this.header() + this.content({ onDone: () => `_callback(); \n` })
)
break;
case 'promise':
let tapsCount = this.content({ onDone: () => `_resolve(); \n` });
let content = `
return new Promise(function(_resolve){
${tapsCount}
})`;
fn = new Function(
this.args(),
this.header() + content;
)
break;
default:
break;
}
return fn
}
// 同步生成函数
callTapsSeries() {
let { taps } = this.options;
if (taps.length === 0) return '';
let code = '';
for (let i = 0; i < taps.length; i++) {
let content = this.callTap(i);
code += content;
}
return code
}
// 异步生成函数
callTapsParallel({ onDone }) {
let taps = this.options.taps;
let code = `var _counter = ${taps.length}; \n`;
code += `
var _done = (function(){
${onDone()}
})
`;
for (let i = 0; i < taps.length; i++) {
let content = this.callTap(i);
code += content;
}
return code
}
callTap(tapIndex) {
let code = '';
code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
let tapInfo = this.options.taps[tapIndex];
switch (tapInfo.type) {
// 如果是同步
case 'sync':
code += `_fn${tapIndex}(${this.args()});\n`;
break;
case 'async':
code += `
_fn${tapIndex}(${this.args()}, (function(){
if (--_counter === 0) _done();
})); `;
break;
case 'promise':
code += `
var _promise${tapIndex} = _fn${tapIndex}(${this.args()});
_promise${tapIndex}.then((function() {
if (--_counter === 0) _done();
}));`;
break;
default:
break;
}
return code
}
}
module.exports = HookCodeFactory;
生成的函数文件
SyncHook 执行最终生成的执行文件
(function anonymous(name,age
) {
var _x = this._x;
var _fn0 = _x[0];
_fn0(name,age);
var _fn1 = _x[1];
_fn1(name,age);
var _fn2 = _x[2];
_fn2(name,age);
})
AsyncParallelHook 执行最终生成的执行文件(简化后的,去除了报错处理)
(function anonymous(name, _callback) {
var _x = this._x;
var _counter = 3;
var _done = (function () {
_callback();
})
var _fn0 = _x[0];
_fn0(name, (function () {
if (--_counter === 0) _done();
})); var _fn1 = _x[1];
_fn1(name, (function () {
if (--_counter === 0) _done();
})); var _fn2 = _x[2];
_fn2(name, (function () {
if (--_counter === 0) _done();
}));
})
AsyncParallelHook promise 写法生成最终的执行文件(简化后的,去除了报错处理)
(function anonymous(name) {
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _counter = 3;
var _done = (function () {
_resolve();
});
var _fn0 = _x[0];
var _promise0 = _fn0(name);
_promise0.then((function () {
if (--_counter === 0) _done();
}));
var _fn1 = _x[1];
var _promise1 = _fn1(name);
_promise1.then((function () {
if (--_counter === 0) _done();
}));
var _fn2 = _x[2];
var _promise2 = _fn2(name);
_promise2.then((function () {
if (--_counter === 0) _done();
}));
}));
})
拦截器 - interceptor
基本概念
所有的钩子都提供额外的拦截器API
- call: (…args) => void
- 当你的钩子也就是call函数触发前触发此函数,你可以访问钩子的参数,多个钩子执行一次
- tap: (tap: Tap) => void
- 每个钩子函数执行前触发此函数,多个钩子触发多次
- register: (tap: Tap) => Tap | undefined
- 每添加一个Tap都会触发此函数,你下一个拦截器的register 函数得到的参数 取决于你上一个register返回的值,所以你最好返回一个 tap 钩子
基本使用
const { SyncHook } = require('tapable');
const hook = new SyncHook(['name', 'age']);
hook.intercept({
register: () => {} // => 当你注册一个tap回调函数触发
call: () => {} // => 当执行call方法之前触发
tap: () => {} // => 每个tap函数会触发一次
});
hook.tap('name', () => {});
hook.call();
基本实现
/Hook.js
实现注册拦截器方法
class Hook {
constructor {
...
this.interceptors = [];
}
// 注册一个拦截器就放入数组中一个
intercept(interceptor) {
this.interceptors.push(interceptor);
}
_tap(tapInfo) {
...
// 实现注册拦截器函数
this._runRegisterInterceptors(tapInfo) // => tapInfo = { type: 'sync', fn: fn(), options: {name: options}}
}
// 执行注册的拦截器方法
_runRegisterInterceptors(tapInfo) {
for (const intercept of this.interceptors) {
if (intercept.register) {
// 如果拦截器中有注册拦截器就执行此函数 并且把参数传进去
intercept.register(tapInfo)
}
}
}
_create(type) {
return this.compile({
taps: this.taps,
args: this.args,
type,
// 把拦截器对象传递给工厂类
interceptors: this.interceptors
});
}
}
/HookCodeFactory.js
- 实现call拦截器方法和tap拦截器方法
- 本质还是拼接成一个可以执行的函数
class HookCodeFactory {
header() {
let code = '';
code += `var _x = this._x;\n`;
// 添加call拦截器函数
if (this.options.interceptors.length) {
code += `var _taps = this.taps;\n`;
code += `var _interceptors = this.interceptors;\n`;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor=this.options.interceptors[i];
if (interceptor.call) {
code += `_interceptors[${i}].call(${this.args()});\n`;
}
}
}
return code
}
callTap(tapIndex) {
let code = '';
// 添加tap拦截器函数
if (this.options.interceptors.length) {
code += `var _tap${tapIndex} = _taps[${tapIndex}];`;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor=this.options.interceptors[i];
if (interceptor.tap) {
code += `_interceptors[${i}].tap(_tap${tapIndex});`;
}
}
}
return code
}
}
HookMap
HookMap的基本使用
const { SyncHook, HookMap } = require('tapable');
const hook = new HookMap(() => new SyncHook(['name']));
hook.for('first').tap('name', () => {
console.log('第一个');
})
hook.for('first').tap('name', () => {
console.log(‘第二个’);
})
const map1 = hook.get('first');
map1.call('zz');
=> 第一个
第二个
HookMap的基本实现
class HookMap {
constructor(createHookFactory) {
this._createHookFactory = createHookFactory;
this._map = new Map();
}
for(key) {
// 先从map对象中取出,如果有直接返回
const hook = this._map.get(key);
if (hook) return hook;
// 没有创建对应的Hook实例,并存到map中,并且返回
let newHook = this._createHookFactory();
this._map.set(key, newHook);
return newHook
}
get(key) {
return this._map.get(key)
}
}
