Webpack-Tapable

TIP

Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 Tapable,webpack 中最核心的负责编译的 Compiler 和负责创建 bundle 的 Compilation 都是 Tapable 的实例

一 简介

  • tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理
  • webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在

1 导出的钩子类

# 一共九个钩子
const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

2 触发方式

类型说明
Sync同步钩子,Sync开头的Hook类只能用tap方法注册事件回调
AsyncParallel异步并行,同时开始执行注册的所有事件回调函数
AsyncSeries异步串行,按照顺序依次执行注册的所有事件回调函数

3 运行逻辑

类型说明
Basic基础类型,单纯的调用注册的事件回调,不关心内部的运行逻辑
Bail保险类型,当一个事件回调在运行时返回值不为undefined时,停止后面事件回调的执行
Loop循环类型,如果当前执行的事件回调的发回执不是undefined,那么重新从第一个注册的事件回调处执行,直到全部执行完成
Waterfall瀑布类型,如果当前执行的事件回调的返回值不为undefined,那么就把下一个事件回调的第一个参数替换成这个值

tapable

4 具体分类

按同步异步分类

  • Hook 类型可以分为同步Sync和异步Async,异步又分为并行和串行

tapable

按返回值分类

tapable

二 使用

  • 所有的构造函数都接收一个可选参数,参数是一个参数名的字符串数组
  • 参数的名字可以任意填写,但是参数数组的长数必须要根实际接受的参数个数一致
  • 如果回调函数不接受参数,可以传入空数组
  • 在实例化的时候传入的数组长度,长度有用,值没有用途
  • 执行 call 时,参数个数和实例化时的数组长度有关
  • 回调的时候是按先入先出的顺序执行的,先放的先执行

1 SyncHook

  • 回调函数是按顺序执行的,全部执行完毕
const {SyncHook} = require('tapable');
const hook = new SyncHook(['name', 'age']);

hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.tap('4', (name, age) => {
	console.log(4, name, age);
})

hook.call('test', 10);
1 'test' 10
2 'test' 10
3 'test' 10
4 'test' 10

2 SyncBailHook

  • 回调函数是按顺序执行的,当返回值 不等于 undefined的时候,就停止调用后续的回调,然后结束
const {SyncBailHook} = require('tapable');
const hook = new SyncBailHook(['name', 'age']);

hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
	return null; // 返回值不为undefined,跳到结束
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.tap('4', (name, age) => {
	console.log(4, name, age);
})

hook.call('test', 10);
1 'test' 10
2 'test' 10

3 SyncWaterfallHook

  • 回调函数是按顺序执行的,表示如果上一个回调函数返回结果 不等于 undefined,则会作为下一个回调函数的第一个参数,直到全部执行完毕,然后结束
const {SyncWaterfallHook} = require('tapable');
const hook = new SyncWaterfallHook(['name', 'age']);

hook.tap('1', (name, age) => {
	console.log(1, name, age);
	return '1';
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
	return '3';
})

hook.tap('4', (name, age) => {
	console.log(4, name, age);
})

hook.call('test', 10);
1 'test' 10
2 '1' 10
3 '1' 10
4 '3' 10

4 SyncLoopHook

  • 特点是不停的循环执行回调函数,直到每个回调函数结果等于 undefined,就进行下一个,如果不是则从头开始循环
  • 要注意的是每次循环都是从头开始循环的
const {SyncLoopHook} = require('tapable');
const hook = new SyncLoopHook(['name', 'age']);

let counter1 = 0;
let counter2 = 0;
let counter3 = 0;
hook.tap('1', (name, age) => {
	console.log(1, 'counter1', counter1);
	if (++counter1 == 1) {
		counter1 = 0;
		return;
	}
	return true;
})

hook.tap('2', (name, age) => {
	console.log(2, 'counter2', counter2);
	if (++counter2 == 2) {
		counter2 = 0;
		return;
	}
	return true;
})

hook.tap('3', (name, age) => {
	console.log(3, 'counter3', counter3);
	if (++counter3 == 3) {
		counter3 = 0;
		return;
	}
	return true;
})

hook.call('test', 10);
1 'counter1' 0
2 'counter2' 0
1 'counter1' 0
2 'counter2' 1
3 'counter3' 0

1 'counter1' 0
2 'counter2' 0
1 'counter1' 0
2 'counter2' 1
3 'counter3' 1

1 'counter1' 0
2 'counter2' 0
1 'counter1' 0
2 'counter2' 1
3 'counter3' 2

5 AsyncParallelHook

  • 异步并行钩子,所有注册的回调同时开始执行
  • 分三种注册方式:同步注册、异步回调注册、异步promise注册
  • 不管有无返回值,都会全部执行完毕

(1) tap:同步注册

const {AsyncParallelHook} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);

console.time('cost');

// 同步注册
hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.callAsync('test', 10, (err) => {
	console.log(err);
    console.timeEnd('cost');
});
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 3.786ms

(2) tapAsync:异步注册

const {AsyncParallelHook} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);

console.time('cost');

// 异步回调注册
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback();
	}, 1000);
})

hook.tapAsync('2', (name, age, callback) => {
	setTimeout(() => {
		console.log(2, name, age);
		callback();
	}, 2000);
})

hook.tapAsync('3', (name, age, callback) => {
	setTimeout(() => {
		console.log(3, name, age);
		callback();
	}, 3000);
})

hook.callAsync('test', 10, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 3002.394ms

(3) tapPromise:异步注册

const {AsyncParallelHook} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);

console.time('cost');

// 异步promise回调
hook.tapPromise('1', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve();
		}, 1000);
	})
})

hook.tapPromise('2', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(2, name, age);
			resolve();
		}, 2000);
	})
})

hook.tapPromise('3', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(3, name, age);
			resolve();
		}, 3000);
	})
})

hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 3002.135ms

6 AsyncParallelBailHook

  • 带保险的异步并行钩子,所有注册的回调同时开始执行,如果有一个任务返回值不为空,就直接结束

(1) tap:同步注册

const {AsyncParallelBailHook} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);

console.time('cost');

// 同步注册 - 如果有一个任务有返回值则调用最终的回调
hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
	return 'result';
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.callAsync('test', 10, (err) => {
	console.log(err);
    console.timeEnd('cost');
});
1 'test' 10
2 'test' 10
null
cost: 3.818ms

(2) tapAsync:异步注册

const {AsyncParallelBailHook} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);

console.time('cost');

// 异步回调注册 - 有一个任务回调函数中有返回值,就直接调最终的回调
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback();
	}, 1000);
})

hook.tapAsync('2', (name, age, callback) => {
	setTimeout(() => {
		console.log(2, name, age);
		callback('result');
	}, 2000);
})

hook.tapAsync('3', (name, age, callback) => {
	setTimeout(() => {
		console.log(3, name, age);
		callback();
	}, 3000);
})

hook.callAsync('test', 10, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
result
cost: 2004.420ms
3 'test' 10

(3) tapPromise:异步注册

const {AsyncParallelBailHook} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);

console.time('cost');

// 异步promise回调 - 只要有一个任务有 resolve 或者 reject 值,不管成功失败都结束
hook.tapPromise('1', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve();
		}, 1000);
	})
})

hook.tapPromise('2', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(2, name, age);
			resolve('成功');
			// reject('失败');
		}, 2000);
	})
})

hook.tapPromise('3', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(3, name, age);
			resolve();
		}, 3000);
	})
})

hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
成功
cost: 2002.886ms
3 'test' 10

7 AsyncSeriesHook

  • 异步串行钩子,任务一个一个执行,执行完上一个执行下一个

(1) tap:同步注册

const {AsyncSeriesHook} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);

console.time('cost');

// 同步注册
hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.callAsync('test', 10, (err) => {
	console.log(err);
    console.timeEnd('cost');
});
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 3.779ms

(2) tapAsync:异步注册

const {AsyncSeriesHook} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);

console.time('cost');

// 异步回调注册
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback();
	}, 1000);
})

hook.tapAsync('2', (name, age, callback) => {
	setTimeout(() => {
		console.log(2, name, age);
		callback();
	}, 2000);
})

hook.tapAsync('3', (name, age, callback) => {
	setTimeout(() => {
		console.log(3, name, age);
		callback();
	}, 3000);
})

hook.callAsync('test', 10, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 6006.636ms

(3) tapPromise:异步注册

const {AsyncSeriesHook} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);

console.time('cost');

// 异步promise回调
hook.tapPromise('1', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve();
		}, 1000);
	})
})

hook.tapPromise('2', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(2, name, age);
			resolve();
		}, 2000);
	})
})

hook.tapPromise('3', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(3, name, age);
			resolve();
		}, 3000);
	})
})

hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
3 'test' 10
undefined
cost: 6006.171ms

8 AsyncSeriesBailHook

  • 带保险的异步串行钩子,只要有一个返回了不为 undefined 的值就直接结束

(1) tap:同步注册

const {AsyncSeriesBailHook} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);

console.time('cost');

// 同步注册
hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
	return 'result';
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.callAsync('test', 10, (err) => {
	console.log(err);
    console.timeEnd('cost');
});
1 'test' 10
2 'test' 10
null
cost: 3.820ms

(2) tapAsync:异步注册

const {AsyncSeriesBailHook} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);

console.time('cost');

// 异步回调注册
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback();
	}, 1000);
})

hook.tapAsync('2', (name, age, callback) => {
	setTimeout(() => {
		console.log(2, name, age);
		callback('result');
	}, 2000);
})

hook.tapAsync('3', (name, age, callback) => {
	setTimeout(() => {
		console.log(3, name, age);
		callback();
	}, 3000);
})


hook.callAsync('test', 10, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
result
cost: 3007.767ms

(3) tapPromise:异步注册

const {AsyncSeriesBailHook} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);

console.time('cost');

// 异步promise回调
hook.tapPromise('1', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve();
		}, 1000);
	})
})

hook.tapPromise('2', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(2, name, age);
			// resolve('成功');
			reject('失败');
		}, 2000);
	})
})

hook.tapPromise('3', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(3, name, age);
			resolve();
		}, 3000);
	})
})
//Promise.all();
hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
}, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 'test' 10
失败
cost: 3005.142ms

9 AsyncSeriesWaterfallHook

  • 瀑布模型的异步串行钩子,按照顺序依次执行任务,上一个函数的返回值为下一个函数的第一个参数

(1) tap:同步注册

const {AsyncSeriesWaterfallHook} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

console.time('cost');

// 同步注册
hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
	return '结果2';
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.callAsync('test', 10, (err) => {
	console.log(err);
    console.timeEnd('cost');
});
1 'test' 10
2 'test' 10
3 '结果2' 10
null
cost: 3.723ms

(2) tapAsync:异步注册

const {AsyncSeriesWaterfallHook} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

console.time('cost');

// 异步回调注册
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback(null, 1);
	}, 1000);
})

hook.tapAsync('2', (number, age, callback) => {
	setTimeout(() => {
		console.log(2, number, age);
		callback(null, ++number);
	}, 2000);
})

hook.tapAsync('3', (number, age, callback) => {
	setTimeout(() => {
		console.log(3, number, age);
		callback(null, ++number);
	}, 3000);
})

hook.callAsync('test', 10, (err, data) => {
	console.log(err, data);
    console.timeEnd('cost');
})
1 'test' 10
2 1 10
3 2 10
null 3
cost: 6007.065ms

(3) tapPromise:异步注册

const {AsyncSeriesWaterfallHook} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

console.time('cost');

// 异步promise回调
hook.tapPromise('1', (name, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve(1);
		}, 1000);
	})
})

hook.tapPromise('2', (number, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(2, number, age);
			resolve(++number);
		}, 2000);
	})
})

hook.tapPromise('3', (number, age) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(3, number, age);
			resolve(++number);
		}, 3000);
	})
})
//Promise.all();
hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
}, err => {
	console.log(err);
    console.timeEnd('cost');
})
1 'test' 10
2 1 10
3 2 10
3
cost: 6004.874ms

三 源码

1 SyncHook

  • 原理:先放入一个数组中,然后执行得时候利用new Function编译出一个临时的函数,然后执行,得到最终的效果
  • 关键点:
    1. 所有的构造函数都接收一个可选参数,参数是一个参数名的字符串数组
    2. 参数的名字可以任意填写,但是参数数组的长数必须要根实际接受的参数个数一致
    3. 如果回调函数不接受参数,可以传入空数组
    4. 在实例化的时候传入的数组长度长度有用,值没有用途
    5. 执行 call 时,参数个数和实例化时的数组长度有关
    6. 回调的时候是按先入先出的顺序执行的,先放的先执行
  • 原理图:

tapable

使用

const {SyncHook} = require('./tapable');
const hook = new SyncHook(['name', 'age']);

hook.tap('1', (name, age) => {
	console.log(1, name, age);
})

hook.tap('2', (name, age) => {
	console.log(2, name, age);
})

hook.tap('3', (name, age) => {
	console.log(3, name, age);
})

hook.call('test', 10);

临时函数

(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);
})

代码

  • tapable\index.js
// 引入模块
let SyncHook = require('./SyncHook');
exports.SyncHook = SyncHook;
  • tapable\SyncHook.js
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncHookCodeFactory extends HookCodeFactory {
	content({onDone}){
        return this.callTapsSeries({onDone})
    }
}

let factory = new SyncHookCodeFactory();
class SyncHook extends Hook {
	compile(options) {
        factory.setup(this, options);
        return factory.create(options);
	}
}

// 导出构造函数
module.exports = SyncHook;
  • tapable\Hook.js
class Hook {
	constructor(args) {
		if (!Array.isArray(args)) args = [];
		this._args = args; // 存放着形参数组 ['name','age']
		this.taps = []; // 存放所有回调函数的数组
		this.call = CALL_DELEGATE; // 惰性函数
	}

	tap(options, fn) {
		this._tap('sync', options, fn);
	}

	_tap(type, options, fn) {
		// 如果是字符串,需要转化为对象
		if (typeof options === 'string') {
			options = {name: options};
		}
		let tapInfo = {...options, type, fn};
		this._insert(tapInfo);
	}

	_resetCompilation(){
		this.call = CALL_DELEGATE;
	}

	_insert(tapInfo) {
		// 每次需要重置call方法
		this._resetCompilation();
		// 放入数组中
		this.taps.push(tapInfo);
	}

	compile(options) {
		throw new Error("Abstract: should be overridden");
	}
	
	_createCall(type) {
		// 编译
		return this.compile({
			taps: this.taps,
			args: this._args,
			type
		});
	}
}

//创建一个call的代理方法
const CALL_DELEGATE = function(...args) {
    //动态的创建call方法,并且赋给this.call
    this.call = this._createCall("sync");
    //执行动态创建出来的call方法
	return this.call(...args);
};

module.exports = Hook;
  • tapable\HookCodeFactory.js
class HookCodeFactory {

	// 初始化
	setup(hookInstance, options) {
		// 映射出来一个回调函数的数组赋给 hooks实例的_x属性
		// _x才是真正回调函数的数组
		hookInstance._x = options.taps.map(item => item.fn);
	}
	// 初始化
	init(options) {
		this.options = options;
	}
	// 删除
	deinit() {
		this.options = null;
	}
	// 处理参数
	args() {
		if (Array.isArray(this.options.args))
			return this.options.args.join(',');
		return '';
	}
	// 拼接头部
	header() {
		let code = "";
		code += "var _x = this._x;\n";//_x是回调函数的数组
		return code;
	}
	create(options) {
		this.init(options);
		let fn;
		switch(this.options.type) {
			case 'sync':
				fn = new Function(
					this.args(),
					this.header() + this.content({
						onDone: () => ''
					})
				);
			break;
		}
		this.deinit();
		return fn;
	}
	// 简单写法
	// callTapsSeries() {
	// 	let code = this.options.taps.map((item, index) => `
	// 		var _fn${index} = _x[${index}];
	// 		_fn${index}(${this.args()});
	// 	`).join('\n');
	// 	return code;
	// }
	// 源码写法
	callTapsSeries({onDone}) {
		if (this.options.taps.length === 0) {
			return onDone();
		}
		let code = '';
		let current = onDone;
		for(let j = this.options.taps.length - 1; j >= 0; j--) {
			const content = this.callTap(j, {onDone: current});
			current = () => content; 
		}
		code += current();
		return code;
	}
	callTap(tapIndex, {onDone}) {
		let code = '';
		code += `var _fn${tapIndex} = _x[${tapIndex}];`;
		let tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case 'sync' :
				code += `_fn${tapIndex}(${this.args()});`
				if (onDone) {
					code += onDone();
				}
				break;
			default:
				break;
		}
		return code;
	}
}

module.exports = HookCodeFactory;

2 AsyncParallelHook-callAsync

  • 原理:利用计数器,在每个函数执行完毕完毕后,都调用一次计数器,判断计数器为0的时候,就执行最终的回调函数

使用

const {AsyncParallelHook} = require('./tapable');
const hook = new AsyncParallelHook(['name', 'age']);

console.time('cost');

// 异步回调注册
hook.tapAsync('1', (name, age, callback) => {
	setTimeout(() => {
		console.log(1, name, age);
		callback();
	}, 1000);
})

hook.tapAsync('2', (name, age, callback) => {
	setTimeout(() => {
		console.log(2, name, age);
		callback();
	}, 2000);
})

hook.tapAsync('3', (name, age, callback) => {
	setTimeout(() => {
		console.log(3, name, age);
		callback();
	}, 3000);
})

// 异步调用
hook.callAsync('test', 10, err => {
	console.log(err);
    console.timeEnd('cost');
})

临时函数

(function anonymous(name, age, _callback) {
    var _x = this._x;
    var _counter = 3; // 计数器

    var _done = function () {
        _callback();
    };

    var _fn0 = _x[0];
     _fn0(name, age, function (_err0) {
		if (_err0) {
			_callback(_err0);
		} else {
			if (--_counter === 0) _done();
		}
	});
	
	var _fn1 = _x[1]; 
	_fn1(name, age, function (_err1) {
		if (_err1) {
			_callback(_err1);
		} else {
			if (--_counter === 0) _done();
		}
	});
	
	var _fn2 = _x[2]; 
	_fn2(name, age, function (_err2) {
		if (_err2) {
			_callback(_err2);
		} else {
			if (--_counter === 0) _done();
		}
	});
})

代码

  • tapable\index.js
// 引入模块
let SyncHook = require('./SyncHook');
exports.SyncHook = SyncHook;

// 异步钩子实现
let AsyncParallelHook = require('./AsyncParallelHook');
exports.AsyncParallelHook = AsyncParallelHook;
  • tapable\AsyncParallelHook.js
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncHookCodeFactory extends HookCodeFactory {
	content({onDone}){
		// 并行
		return this.callTapsParallel({onDone})
	}
}

let factory = new SyncHookCodeFactory();
class AsyncParallelHook extends Hook {
	compile(options) {
        factory.setup(this, options);
        return factory.create(options);
	}
}

// 导出构造函数
module.exports = AsyncParallelHook;
  • tapable\Hook.js
class Hook {
	constructor(args) {
		if (!Array.isArray(args)) args = [];
		this._args = args; // 存放着形参数组 ['name','age']
		this.taps = []; // 存放所有回调函数的数组
		this.call = CALL_DELEGATE;
		// 添加异步方法
		this.callAsync = CALL_ASYNC_DELEGATE;
	}

	tap(options, fn) {
		this._tap('sync', options, fn);
	}
	
	tapAsync(options, fn) {
		this._tap('async', options, fn);
	}

	_tap(type, options, fn) {
		// 如果是字符串,需要转化为对象
		if (typeof options === 'string') {
			options = {name: options};
		}
		let tapInfo = {...options, type, fn};
		this._insert(tapInfo);
	}

	_resetCompilation(){
        this.call = CALL_DELEGATE;
    }

	_insert(tapInfo) {
		// 每次需要重置call方法
		this._resetCompilation();
		this.taps.push(tapInfo);
	}

	compile(options) {
		throw new Error("Abstract: should be overridden");
	}
	
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			args: this._args,
			type
		});
	}
}

//创建一个call的代理方法
const CALL_DELEGATE = function(...args) {
    //动态的创建call方法,并且赋给this.call
    this.call = this._createCall("sync");
    //执行动态创建出来的call方法
	return this.call(...args);
};

const CALL_ASYNC_DELEGATE = function(...args) {
	this.callAsync = this._createCall("async");
	return this.callAsync(...args);
};

module.exports = Hook;
  • tapable\HookCodeFactory.js
class HookCodeFactory {

	// 初始化
	setup(hookInstance, options) {
		// 映射出来一个回调函数的数组赋给 hooks实例的_x属性
        // _x才是真正回调函数的数组
		hookInstance._x = options.taps.map(item => item.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];
		if (allArgs.length > 0) {
			return allArgs.join(', ');
		}
		return '';
	}
	header() {
		let code = "";
		code += "var _x = this._x;\n";//_x是回调函数的数组
		return code;
	}
	create(options) {
		this.init(options);
		let fn;
		switch(this.options.type) {
			case 'sync':
				fn = new Function(
					this.args(),
					this.header() + this.content({
						onDone: () => ''
					})
				);
				break;
			case 'async':
				fn = new Function(
                    this.args({after:'_callback'}),
                    this.header()+this.content({
                        onDone:()=>"_callback();\n"
                    })
                )
				break;
		}


		this.deinit();
		return fn;
	}

	callTapsSeries({onDone}) {
		if (this.options.taps.length === 0) {
			return onDone();
		}
		let code = '';
		let current = onDone;
		for(let j = this.options.taps.length - 1; j >= 0; j--) {
			const content = this.callTap(j, {onDone: current});
			current = () => content; 
		}
		code += current();
		// 顺序需要调试一下,
		// console.log(code);
		return code;
	}
	callTapsParallel({onDone}) {
		let code = `var _counter = ${this.options.taps.length};\n`;
		if (onDone) {
			code += `
				var _done = function() {
					${onDone()}
				}
			`;
		}
		for (let i = 0; i < this.options.taps.length; i++) {
			const done = () => `if (--_counter === 0) _done()`;
			code += this.callTap(i, {onDone: done});
		}
		return code;
	}
	callTap(tapIndex, {onDone}) {
		let code = '';
		code += `var _fn${tapIndex} = _x[${tapIndex}];`;
		let tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case 'sync' :
				code += `_fn${tapIndex}(${this.args()});`
				if (onDone) {
					code += onDone();
				}
				break;
			case 'async':
				let cbCode = `
				function (_err${tapIndex}) {
					if (_err${tapIndex}) {
						_callback(_err${tapIndex});
					} else {
						${onDone()}
					}
				}
				`;
				code += `_fn${tapIndex}(${this.args({after:cbCode})});`;
				break;
			default:
				break;
		}
		console.log(code);
		return code;
	}
}

module.exports = HookCodeFactory;

3 AsyncParallelHook-callPromise

  • 原理:利用计数器,在每个函数执行完毕完毕后,都调用一次计数器,判断计数器为0的时候,就执行最终的回调函数
  • 不同点在于 promise是全部包裹在一个promise函数中,类似Promise.all方法,全部成功了才调用resolve

使用

const {AsyncParallelHook} = require('./tapable');
const hook = new AsyncParallelHook(['name', 'age']);

console.time('cost');

// 异步promise回调
hook.tapPromise('1', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(1, name, age);
			resolve();
		}, 1000);
	})
})

hook.tapPromise('2', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(2, name, age);
			resolve();
		}, 2000);
	})
})

hook.tapPromise('3', (name, age) => {
	return new Promise(resolve => {
		setTimeout(() => {
			console.log(3, name, age);
			resolve();
		}, 3000);
	})
})

hook.promise('test', 10).then(result => {
	console.log(result);
    console.timeEnd('cost');
})

临时函数

(function anonymous(name, age) {
    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, age);
        _promise0.then(
            function () {
                if (--_counter === 0) _done();
            }
        );

        var _fn1 = _x[1];
        var _promise1 = _fn1(name, age);
        _promise1.then(
            function () {
                if (--_counter === 0) _done();
            }
        );

        var _fn2 = _x[2];
        var _promise2 = _fn0(name, age);
        _promise2.then(
            function () {
                if (--_counter === 0) _done();
            }
        );
        
    });
});

代码

  • tapable\index.js
// 引入模块
let SyncHook = require('./SyncHook');
exports.SyncHook = SyncHook;

// 异步钩子实现
let AsyncParallelHook = require('./AsyncParallelHook');
exports.AsyncParallelHook = AsyncParallelHook;
  • tapable\AsyncParallelHook.js
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncHookCodeFactory extends HookCodeFactory {
	content({onDone}){
		// 并行
		return this.callTapsParallel({onDone})
	}
}

let factory = new SyncHookCodeFactory();
class AsyncParallelHook extends Hook {
	compile(options) {
        factory.setup(this, options);
        return factory.create(options);
	}
}

// 导出构造函数
module.exports = AsyncParallelHook;
  • tapable\Hook.js
class Hook {
	constructor(args) {
		if (!Array.isArray(args)) args = [];
		this._args = args; // 存放着形参数组 ['name','age']
		this.taps = []; // 存放所有回调函数的数组
		this.call = CALL_DELEGATE;
		// 添加异步方法
		this.callAsync = CALL_ASYNC_DELEGATE;
		this.promise = PROMISE_DELEGATE;
	}

	tap(options, fn) {
		this._tap('sync', options, fn);
	}
	
	tapAsync(options, fn) {
		this._tap('async', options, fn);
	}

	_tap(type, options, fn) {
		// 如果是字符串,需要转化为对象
		if (typeof options === 'string') {
			options = {name: options};
		}
		let tapInfo = {...options, type, fn};
		this._insert(tapInfo);
	}

	_resetCompilation(){
        this.call = CALL_DELEGATE;
    }

	_insert(tapInfo) {
		// 每次需要重置call方法
		this._resetCompilation();
		this.taps.push(tapInfo);
	}

	compile(options) {
		throw new Error("Abstract: should be overridden");
	}
	
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			args: this._args,
			type
		});
	}
}

//创建一个call的代理方法
const CALL_DELEGATE = function(...args) {
    //动态的创建call方法,并且赋给this.call
    this.call = this._createCall("sync");
    //执行动态创建出来的call方法
	return this.call(...args);
};

const CALL_ASYNC_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;
  • tapable\HookCodeFactory.js
class HookCodeFactory {

	// 初始化
	setup(hookInstance, options) {
		// 映射出来一个回调函数的数组赋给 hooks实例的_x属性
        // _x才是真正回调函数的数组
		hookInstance._x = options.taps.map(item => item.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];
		if (allArgs.length > 0) {
			return allArgs.join(', ');
		}
		return '';
	}
	header() {
		let code = "";
		code += "var _x = this._x;\n";//_x是回调函数的数组
		return code;
	}
	create(options) {
		this.init(options);
		let fn;
		switch(this.options.type) {
			case 'sync':
				fn = new Function(
					this.args(),
					this.header() + this.content({
						onDone: () => ''
					})
				);
				break;
			case 'async':
				fn = new Function(
                    this.args({after:'_callback'}),
                    this.header()+this.content({
                        onDone:()=>"_callback();\n"
                    })
                )
				break;
			case 'promise':
				/* fn = new Function(this.args(),this.header()
				+`return Promise.all(_x.map(item=>item(${this.args()})));`); */
				let content = this.content({
					onDone:()=>" _resolve();\n"
				});
				content = `return new Promise(function (_resolve, _reject) {
					${content}
				})`;
				fn = new Function(
					this.args(),
					this.header() + content
				)
				break;
		}

		this.deinit();
		return fn;
	}

	callTapsSeries({onDone}) {
		if (this.options.taps.length === 0) {
			return onDone();
		}
		let code = '';
		let current = onDone;
		for(let j = this.options.taps.length - 1; j >= 0; j--) {
			const content = this.callTap(j, {onDone: current});
			current = () => content; 
		}
		code += current();
		// 顺序需要调试一下,
		// console.log(code);
		return code;
	}
	callTapsParallel({onDone}) {
		let code = `var _counter = ${this.options.taps.length};\n`;
		if (onDone) {
			code += `
				var _done = function() {
					${onDone()}
				}
			`;
		}
		for (let i = 0; i < this.options.taps.length; i++) {
			const done = () => `if (--_counter === 0) _done()`;
			code += this.callTap(i, {onDone: done});
		}
		return code;
	}
	callTap(tapIndex, {onDone}) {
		let code = '';
		code += `var _fn${tapIndex} = _x[${tapIndex}];`;
		let tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case 'sync' :
				code += `_fn${tapIndex}(${this.args()});`
				if (onDone) {
					code += onDone();
				}
				break;
			case 'async':
				let cbCode = `
				function (_err${tapIndex}) {
					if (_err${tapIndex}) {
						_callback(_err${tapIndex});
					} else {
						${onDone()}
					}
				}
				`;
				code += `_fn${tapIndex}(${this.args({after:cbCode})});`;
				break;
			case 'promise':
				code = `
					var _fn${tapIndex} = _x[${tapIndex}];
					var _promise${tapIndex} = _fn${tapIndex}(${this.args()});
					_promise${tapIndex}.then(
						function () {
							${onDone()}
						}
					);
				`;
				break;
			default:
				break;
		}
		return code;
	}
}

module.exports = HookCodeFactory;

4 interceptor

  • 所有钩子提供的额外的拦截器API,有三个:
    1. register 每添加一个tap的回调函数都会触发一次
    2. tap: 每个钩子执行之前(多个钩子执行多个),就会触发这个函数
    3. call 当你的钩子触发之前,(就是call()之前),就会触发这个函数,多个钩子执行一次
  • Context(上下文) 插件和拦截器都可以选择加入一个可选的 context对象, 这个可以被用于传递随意的值到队列中的插件和拦截器

使用

const {SyncHook} = require('tapable');
const syncHook = new SyncHook(["name","age"]);

// 第一个拦截器
syncHook.intercept({
	context: true, // 上下文对象
	register: (tapinfo) => { // 当注册一个新的回调函数的时候调用一次
		console.log(`${tapinfo.name}-1 注册`);
		tapinfo.register1 = 'register1';
		return tapinfo;
	},
	tap: (context, tapinfo) => { // 每个回调函数都会触发一次
		console.log('开始触发1', context);
		if (context) {
			context.name1 = 'name1';
		}
	},
	call: (context, name, age) => { // 每个call触发,所有的回调只会总共触发一次
		console.log('开始调用1', context, name, age);
	}
});

// 第二个拦截器
syncHook.intercept({
	context: true, // 上下文对象
	register: (tapinfo) => { // 当注册一个新的回调函数的时候调用一次
		console.log(`${tapinfo.name}-2 注册`);
		tapinfo.register2 = 'register2';
		return tapinfo;
	},
	tap: (context, tapinfo) => { // 每个回调函数都会触发一次
		console.log('开始触发2', context);
		if (context) {
			context.name2 = 'name2';
		}
	},
	call: (context, name, age) => { // 每个call触发,所有的回调只会总共触发一次
		console.log('开始调用2', context, name, age);
	}
});

// 注册
syncHook.tap({name: 'tap1函数A', context: true}, (name, age) => {
	console.log('回调1', name, age);
})

syncHook.tap({name: 'tap2函数B', context: true}, (name, age) => {
	console.log('回调2', name, age);
})

// 执行
syncHook.call('test', 10);
// 执行结果
 =register=
tap1函数A-1 注册
tap1函数A-2 注册

tap2函数B-1 注册
tap2函数B-2 注册

=call=
开始调用1 {} test 10
开始调用2 {} test 10

=tap=
开始触发1 {}
开始触发2 { name1: 'name1' }
回调1 { name1: 'name1', name2: 'name2' } test

开始触发1 { name1: 'name1', name2: 'name2' }
开始触发2 { name1: 'name1', name2: 'name2' }
回调2 { name1: 'name1', name2: 'name2' } test
// 需要构建的临时函数
(function anonymous(name, age) {
	var _x = this._x;
	var _taps = this.taps;

	var _interceptors = this.interceptors;
	_interceptors[0].call(name, age);
	_interceptors[1].call(name, age);

	var _tap0 = _taps[0];
	_interceptors[0].tap(_tap0);
	_interceptors[1].tap(_tap0);
	var _fn0 = _x[0];
	_fn0(name, age);

	var _tap1 = _taps[1];
	_interceptors[0].tap(_tap1);
	_interceptors[1].tap(_tap1);
	var _fn1 = _x[1];
	_fn1(name, age);
});

实现

  • tapable/Hook.js
class Hook {
	constructor(args) {
		if (!Array.isArray(args)) args = [];
		this._args = args; // 存放着形参数组 ['name','age']
		this.taps = []; // 存放所有回调函数的数组
+		this.interceptors = [];//存放着所有的拦截器
		this.call = CALL_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this.promise = PROMISE_DELEGATE;
	}

	tap(options, fn) {
		this._tap('sync', options, fn);
	}
	tapAsync(options, fn) {
		this._tap('async', options, fn);
	}
	tapPromise(options, fn) {
		this._tap('promise', options, fn);
	}

	_tap(type, options, fn) {
		// 如果是字符串,需要转化为对象
		if (typeof options === 'string') {
			options = {name: options};
		}
		let tapInfo = {...options, type, fn};
		// 执行拦截器
+		tapInfo=this._runRegisterInterceptors(tapInfo);

		this._insert(tapInfo);
	}

+	_runRegisterInterceptors(tapInfo){
+		for(const interceptor of this.interceptors){
+			if (interceptor.register) {
+				let newTapInfo = interceptor.register(tapInfo);
+				if (newTapInfo) {
+					tapInfo = newTapInfo;
+				}
+			}
+		}
+		return tapInfo;
+	}

	// 注册拦截器
	intercept(interceptor){
        this.interceptors.push(interceptor);
	}

	_resetCompilation(){
        this.call = CALL_DELEGATE;
    }

	_insert(tapInfo) {
		// 每次需要重置call方法
		this._resetCompilation();
		this.taps.push(tapInfo);
	}

	compile(options) {
		throw new Error("Abstract: should be overridden");
	}
	
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			args: this._args,
+			interceptors:this.interceptors,
			type
		});
	}
}

//创建一个call的代理方法
const CALL_DELEGATE = function(...args) {
    //动态的创建call方法,并且赋给this.call
    this.call = this._createCall("sync");
    //执行动态创建出来的call方法
	return this.call(...args);
};

const CALL_ASYNC_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;
  • tapable/HookCodeFactory.js
class HookCodeFactory {

	// 初始化
	setup(hookInstance, options) {
		// 映射出来一个回调函数的数组赋给 hooks实例的_x属性
        // _x才是真正回调函数的数组
		hookInstance._x = options.taps.map(item => item.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];
		if (allArgs.length > 0) {
			return allArgs.join(', ');
		}
		return '';
	}
	needContext(){
        for(const tapInfo of this.options.taps){
            if(tapInfo.context) return true;
        }
	}
	
	header() {
		let code = "";
		code += "var _x = this._x;\n";//_x是回调函数的数组
		// 拼接拦截器
+		if (this.needContext()) {
+			code += `var _context = {};\n`;
+		} else {
+			code += `var _context;\n`;
+		}
+		if (this.options.interceptors.length>0) {
+			code += `var _taps = this.taps;\n`;
+			code += `var _interceptors = this.interceptors;\n`;
+		}
+		for (let k = 0; k < this.options.interceptors.length; k++) {
+			const interceptor = this.options.interceptors[k];
+			if (interceptor.call)
+				code += `_interceptors[${k}].call(${this.args({
+					before: interceptor.context ? "_context" : undefined
+			})});\n`;
+		}
		return code;
	}
	create(options) {
		this.init(options);
		let fn;
		switch(this.options.type) {
			case 'sync':
				fn = new Function(
					this.args(),
					this.header() + this.content({
						onDone: () => ''
					})
				);
				break;
			case 'async':
				fn = new Function(
                    this.args({after:'_callback'}),
                    this.header()+this.content({
                        onDone:()=>"_callback();\n"
                    })
                )
				break
			case 'promise':
				/* fn = new Function(this.args(),this.header()
				+`return Promise.all(_x.map(item=>item(${this.args()})));`); */
				let content = this.content({
					onDone:()=>" _resolve();\n"
				});
				content = `return new Promise(function (_resolve, _reject) {
					${content}
				})`;
				fn = new Function(
					this.args(),
					this.header() + content
				)
				break;
		}


		this.deinit();
		return fn;
	}
	callTapsSeries({onDone}) {
		if (this.options.taps.length === 0) {
			return onDone();
		}
		let code = '';
		let current = onDone;
		for(let j = this.options.taps.length - 1; j >= 0; j--) {
			const content = this.callTap(j, {onDone: current});
			current = () => content; 
		}
		code += current();
		// 顺序需要调试一下,
		// console.log(code);
		return code;
	}
	callTapsParallel({onDone}) {
		let code = `var _counter = ${this.options.taps.length};\n`;
		if (onDone) {
			code += `
				var _done = function() {
					${onDone()}
				}
			`;
		}
		for (let i = 0; i < this.options.taps.length; i++) {
			const done = () => `if (--_counter === 0) _done()`;
			code += this.callTap(i, {onDone: done});
		}
		return code;
	}
	callTap(tapIndex, {onDone}) {
		let code = '';
+		if (this.options.interceptors.length > 0) {
+			code += `var _tap${tapIndex} = _taps[${tapIndex}];`;
+			for (let i = 0; i < this.options.interceptors.length; i++) {
+				let interceptor = this.options.interceptors[i];
+				if(interceptor.tap){
+					code += `_interceptors[${i}].tap(${this.needContext() && '_context,'}_tap${tapIndex});`;
+				}
+			}
+		}

		code += `var _fn${tapIndex} = _x[${tapIndex}];`;
		let tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case 'sync' :
				code += `_fn${tapIndex}(${this.args()});`
				if (onDone) {
					code += onDone();
				}
				break;
			case 'async':
				let cbCode = `
				function (_err${tapIndex}) {
					if (_err${tapIndex}) {
						_callback(_err${tapIndex});
					} else {
						${onDone()}
					}
				}
				`;
				code += `_fn${tapIndex}(${this.args({after:cbCode})});`;
				break;
			case 'promise':
				code = `
					var _fn${tapIndex} = _x[${tapIndex}];
					var _promise${tapIndex} = _fn${tapIndex}(${this.args()});
					_promise${tapIndex}.then(
						function () {
							${onDone()}
						}
					);
				`;
				break;
			default:
				break;
		}
		return code;
	}
}

module.exports = HookCodeFactory;

5 HookMap

  • 用对象存储钩子函数的帮助方法
const {SyncHook, HookMap} = require('tapable');

const keyedHookMap = new HookMap(() => new SyncHook(["name"]));
keyedHookMap.for('key1').tap('plugin1', (name) => {
	console.log(1, name);
});
keyedHookMap.for('key2').tap('plugin2', (name) => {
	console.log(2, name);
});
const hook1 = keyedHookMap.get('key1');
hook1.call('test1');

const hook2 = keyedHookMap.get('key2');
hook2.call('test2');

// 1 'test1'
// 2 'test2'
// 源码实现
class HookMap {
	constructor(factory) {
		this._map = new Map();
		this._factory = factory;
	}
	get(key) {
		return this._map.get(key);
	}
	for(key) {
		const hook = this.get(key);
		if (hook) return hook;
		let newHook = this._factory();
		this._map.set(key, newHook);
		return newHook;
	}
}

6 stage 和 before

  • 用于提高钩子函数的优先执行顺序
  • 如果同时存在stage和before,before优先
  • 实现原理就是在放入钩子回调的时候,利用算法进行顺序调整

使用

// stage
const {SyncHook} = require('tapable');

let hook = new SyncHook(['name']);

hook.tap({name: 'tap1', stage:1}, (name) => {
	console.log('tap1',name);
});
hook.tap({name: 'tap3', stage:3}, (name) => {
	console.log('tap3',name);
});
hook.tap({name: 'tap4', stage:4}, (name) => {
	console.log('tap4',name);
});
hook.tap({name: 'tap2', stage:2}, (name) => {
	console.log('tap2',name);
});

hook.call('test');

/**
tap1 test
tap2 test
tap3 test
tap4 test
 */
// before
const {SyncHook} = require('tapable');

let hook = new SyncHook(['name']);

hook.tap({name: 'tap1', stage:1}, (name) => {
	console.log('tap1',name);
});
hook.tap({name: 'tap3', stage:3}, (name) => {
	console.log('tap3',name);
});
hook.tap({name: 'tap4', stage:4}, (name) => {
	console.log('tap4',name);
});
hook.tap({name: 'tap2', stage:2, before:['tap1']}, (name) => {
	console.log('tap2',name);
});

hook.call('test');

/**
tap2 test
tap1 test
tap3 test
tap4 test
 */

实现

// tapable/Hook.js
class Hook {
	constructor(args) {
		if (!Array.isArray(args)) args = [];
		this._args = args; // 存放着形参数组 ['name','age']
		this.taps = []; // 存放所有回调函数的数组
		this.interceptors = [];//存放着所有的拦截器
		this.call = CALL_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this.promise = PROMISE_DELEGATE;
	}

	tap(options, fn) {
		this._tap('sync', options, fn);
	}
	tapAsync(options, fn) {
		this._tap('async', options, fn);
	}
	tapPromise(options, fn) {
		this._tap('promise', options, fn);
	}

	_tap(type, options, fn) {
		// 如果是字符串,需要转化为对象
		if (typeof options === 'string') {
			options = {name: options};
		}
		let tapInfo = {...options, type, fn};
		// 执行拦截器
		tapInfo=this._runRegisterInterceptors(tapInfo);

		this._insert(tapInfo);
	}

	_runRegisterInterceptors(tapInfo){
		for(const interceptor of this.interceptors){
			if (interceptor.register) {
				let newTapInfo = interceptor.register(tapInfo);
				if (newTapInfo) {
					tapInfo = newTapInfo;
				}
			}
		}
		return tapInfo;
	}

	// 注册拦截器
	intercept(interceptor){
        this.interceptors.push(interceptor);
	}

	_resetCompilation(){
        this.call = CALL_DELEGATE;
    }

	_insert(tapInfo) {
		// 每次需要重置call方法
		this._resetCompilation();

		// 优先级处理
+		let before;
+		if (typeof tapInfo.before === 'string') {
+			before = new Set([tapInfo.before]);
+		} else if (Array.isArray(tapInfo.before)) {
+			before = new Set(tapInfo.before);
+		}

+		let stage = 0;
+		if (typeof tapInfo.stage === 'number') {
+			stage = tapInfo.stage;
+		}
+		let i = this.taps.length;
+		while(i > 0) {
+			i--;
+			const x = this.taps[i];
+			this.taps[i+1] = x;
+			const xStage = x.stage || 0;
+			if (before) {
+				if (before.has(x.name)) {
+					before.delete(x.name);
+					continue;
+				}
+				if (before.size > 0) {
+					continue;
+				}
+			}
+			if (xStage > stage) {
+				continue;
+			}
+			i++;
+			break;
+		}
+		this.taps[i] = tapInfo;
	}

	compile(options) {
		throw new Error("Abstract: should be overridden");
	}
	
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			args: this._args,
			interceptors:this.interceptors,
			type
		});
	}
}

//创建一个call的代理方法
const CALL_DELEGATE = function(...args) {
    //动态的创建call方法,并且赋给this.call
    this.call = this._createCall("sync");
    //执行动态创建出来的call方法
	return this.call(...args);
};

const CALL_ASYNC_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;