Webpack本质上是一种事件流的机制,tapable是实现webpack将各个插件串联起来的最核心库,负责编译的 Compiler 和负责创建 bundles 的 Compilation 都是 tapable 构造函数的实例
本文将模拟tapable库 “钩子” 的事件处理机制,了解 tapable,可以为学习 Webpack 原理做一个铺垫。
在 Webpack 中,这些 “钩子” 的真正作用就是将通过配置文件读取的插件与插件、加载器与加载器之间进行连接,“并行” 或 “串行” 执行。
手写这些方法不仅熟悉tapable底层原理,也引起自己对于同步和异步分别对串行、并行的实现
。
tapable 核心库的类
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Sync 类型的钩子
SyncHook:串行同步执行
// 串行同步执行
class SyncHook{
constructor(instanseArgs){
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tap(taskName,task){
this.tasks.push(task)
}
call(...args){
// 也可在参数不足时抛出异常
if (args.length < this.instanseArgs.length) throw new Error("参数不足");
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length);
// 依次执行事件处理函数
this.tasks.forEach(task => task(...args));
}
}
// 创建实例
let syncHook = new SyncHook(["name", "age", 't']);
// let syncHook = new SyncHook(["name", "age", 't']); //会提示参数不足
// 注册事件
syncHook.tap("tast1", (name, age) => console.log("1", name, age));
syncHook.tap("tast2", (name, age) => console.log("2", name, age));
syncHook.tap("tast3", (name, age) => console.log("3", name, age));
// 触发事件,让监听函数执行
syncHook.call("hannie", 18);
>
1 hannie 18
2 hannie 18
3 hannie 18
SyncBailHook:串行同步执行,如果事件处理函数执行时有一个返回值不为空(即返回值不为 undefined),则跳过剩下未执行的事件处理函数
/**
* SyncBailHook 同样为串行同步执行,如果事件处理函数执行时有一个返回值不为空(即返回值不为 undefined),则跳过剩下未执行的事件处理函数
*/
// 串行同步执行
class SyncBailHook {
constructor(instanseArgs) {
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tap(taskName, task) {
this.tasks.push(task)
}
call(...args) {
// 也可在参数不足时抛出异常
if (args.length < this.instanseArgs.length) throw new Error("参数不足");
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length);
// 依次执行事件处理函数
let ret = undefined;
for (let i = 0; i < this.tasks.length; i++) {
ret = this.tasks[i](args)
if (ret) {
break;
}
}
// forEach不能用break
// this.tasks.forEach(function(task) {
// ret = task(args)
// if(!ret){
// break;
// }
// });
// 依次执行事件处理函数,如果返回值不为空,则停止向下执行
// let i = 0, ret;
// do {
// ret = this.tasks[i++](...args);
// } while (!ret);
}
}
// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);
// 注册事件
syncBailHook.tap("1", (name, age) => console.log("1", name, age));
syncBailHook.tap("2", (name, age) => {
console.log("2", name, age);
return "2";
});
// 跳过执行
syncBailHook.tap("3", (name, age) => console.log("3", name, age));
// 触发事件,让监听函数执行
syncBailHook.call("hannie", 18);
>
1 hannie 18
2 hannie 18
SyncLoopHook:串行循环执行
/**
* 事件处理函数返回 true 表示继续循环,即循环执行当前事件处理函数,返回 undefined 表示结束循环
* SyncBailHook 只决定是否继续向下执行后面的事件处理函数,而 SyncLoopHook 的循环是指循环执行每一个事件处理函数,直到返回 undefined 为止,才会继续向下执行其他事件处理函数,执行机制同理。
*/
class SyncLoopHook {
constructor(args) {
this.args = args;
this.tasks = [];
}
tap(name, task) {
this.tasks.push(task);
}
call(...args) {
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.args.length);
// 依次执行事件处理函数,如果返回值为 true,则继续执行当前事件处理函数
// 直到返回 undefined,则继续向下执行其他事件处理函数
this.tasks.forEach(task => {
let ret;
do {
ret = task(...args);
} while (ret === true || !(ret === undefined));
});
}
}
// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);
// 定义辅助变量
let total1 = 0;
let total2 = 0;
// 注册事件
syncLoopHook.tap("1", (name, age) => {
console.log("1", name, age, total1);
return total1++ < 2 ? true : undefined;
});
syncLoopHook.tap("2", (name, age) => {
console.log("2", name, age, total2);
return total2++ < 2 ? true : undefined;
});
syncLoopHook.tap("3", (name, age) => console.log("3", name, age));
// 触发事件,让监听函数执行
syncLoopHook.call("hannie", 18);
>
1 hannie 18 0
1 hannie 18 1
1 hannie 18 2
2 hannie 18 0
2 hannie 18 1
2 hannie 18 2
3 hannie 18
SyncWaterfallHook:串行同步执行
/**
* SyncWaterfallHook 为串行同步执行,上一个事件处理函数的返回值作为参数传递给下一个事件处理函数,依次类推,正因如此,只有第一个事件处理函数的参数可以通过 call 传递,而 call 的返回值为最后一个事件处理函数的返回值
*/
// 串行同步执行
class SyncWaterfallHook {
constructor(instanseArgs) {
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tap(taskName, task) {
this.tasks.push(task)
}
call(...args) {
// 也可在参数不足时抛出异常
if (args.length < this.instanseArgs.length) throw new Error("参数不足");
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length);
let [first, ...others] = this.tasks;
others.reduce((res, task) => {
return task(res)
}, first(...args))
}
}
// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);
// 注册事件
syncWaterfallHook.tap("1", (name, age) => {
console.log("1", name, age);
return "1";
});
syncWaterfallHook.tap("2", data => {
console.log("2", data);
return "2";
});
syncWaterfallHook.tap("3", data => {
console.log("3", data);
return "3"
});
// 触发事件,让监听函数执行
let ret = syncWaterfallHook.call("hannie", 18);
console.log("call", ret);
>
1 hannie 18
2 1
3 2
call undefined
Async 类型的钩子
AsyncParallelHook(setTimeout):并行异步执行
/**
* 并行异步执行
*/
class AsyncParallelHook{
constructor(instanseArgs){
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tapAsync(taskName,task){
this.tasks.push(task)
}
callAsync(...args){
let finalCallback = args.pop();
// 也可在参数不足时抛出异常
if (args.length < this.instanseArgs.length) throw new Error("参数不足");
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length); //["name", "age"]
// let i = 0;
// let done = () => {
// if (++i === this.tasks.length) {
// finalCallback();
// }
// };
// task是包含done回调函数的函数
this.tasks.forEach((task,index)=>{
// task(...args,done)
task(...args,()=>{
if(index===this.tasks.length){
finalCallback();
}
})
// if(index===this.tasks.length){
// finalCallback();
// } //在此处也是可以的,就是为了循环完就执行
})
}
}
// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
// 注册事件
console.time("time");
asyncParallelHook.tapAsync("1", (name, age, done) => {
setTimeout(() => {
console.log("1", name, age, new Date());
done();
}, 1000);
});
asyncParallelHook.tapAsync("2", (name, age, done) => {
setTimeout(() => {
console.log("2", name, age, new Date());
done();
}, 2000);
});
asyncParallelHook.tapAsync("3", (name, age, done) => {
setTimeout(() => {
console.log("3", name, age, new Date());
done();
console.timeEnd("time");
}, 3000);
});
// 触发事件,让监听函数执行
asyncParallelHook.callAsync("hannie", 18, () => {
console.log("complete");
});
>
1 hannie 18 2020-08-03T06:13:14.007Z
2 hannie 18 2020-08-03T06:13:15.001Z
3 hannie 18 2020-08-03T06:13:16.004Z
time: 3005.195ms
AsyncParallelHook (Promise):并行异步执行
/**
* 并行异步执行
*/
class AsyncParallelHook{
constructor(instanseArgs){
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tapPromise(taskName,task){
this.tasks.push(task)
}
promise(...args){
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length); //["name", "age"]
return Promise.all(this.tasks.map((task)=>{
return task(...args)
}))
// return Promise.all(this.tasks.map(task => task(...args)));
}
}
// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);
// 注册事件
console.time("time");
asyncParallelHook.tapPromise("1", (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("1", name, age, new Date());
resolve("1");
}, 1000);
});
});
asyncParallelHook.tapPromise("2", (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2", name, age, new Date());
resolve("2");
}, 2000);
});
});
asyncParallelHook.tapPromise("3", (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("3", name, age, new Date());
resolve("3");
console.timeEnd("time");
}, 3000);
});
});
// 触发事件,让监听函数执行
asyncParallelHook.promise("hannie", 18).then(ret => {
console.log(ret);
});
>
1 hannie 18 2020-08-03T06:22:47.198Z
2 hannie 18 2020-08-03T06:22:48.198Z
3 hannie 18 2020-08-03T06:22:49.197Z
time: 3004.218ms
[ '1', '2', '3' ]
AsyncSeriesHook (setTimeout):串行异步执行
/**
* 串行异步执行
* 一步一步执行
*/
class AsyncSeriesHook{
constructor(instanseArgs){
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tapAsync(taskName,task){
this.tasks.push(task)
}
callAsync(...args){
let finalCallback = args.pop();
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length); //["name", "age"]
// 递归next
let i = 0;
let next = () => {
let task = this.tasks[i++];
task ? task(...args, next) : finalCallback();
}
next()
// this.tasks.forEach((task,index)=>{
// task(...args, next)
// })
}
}
// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
// 注册事件
console.time("time");
asyncSeriesHook.tapAsync("1", (name, age, next) => {
setTimeout(() => {
console.log("1", name, age, new Date());
next();
}, 1000);
});
asyncSeriesHook.tapAsync("2", (name, age, next) => {
setTimeout(() => {
console.log("2", name, age, new Date());
next();
}, 2000);
});
asyncSeriesHook.tapAsync("3", (name, age, next) => {
setTimeout(() => {
console.log("3", name, age, new Date());
next();
console.timeEnd("time");
}, 3000);
});
// 触发事件,让监听函数执行
asyncSeriesHook.callAsync("hannie", 18, () => {
console.log("complete");
});
>
1 hannie 18 2020-08-03T06:32:51.094Z
2 hannie 18 2020-08-03T06:32:53.103Z
3 hannie 18 2020-08-03T06:32:56.106Z
complete
time: 6015.353ms
AsyncSeriesHook (Promise): 串行异步执行
/**
* 串行异步执行
*/
class AsyncSeriesHook{
constructor(instanseArgs){
this.instanseArgs = instanseArgs;
this.tasks = [];
}
// taskName插件名称,只是起到注释作用
tapPromise(taskName,task){
this.tasks.push(task)
}
promise(...args){
// 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
args = args.slice(0, this.instanseArgs.length); //["name", "age"]
let [first, ...others] = this.tasks;
return others.reduce((promise, task) => {
return promise.then(() => task(...args));
}, first(...args));
}
}
// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);
// 注册事件
console.time("time");
asyncSeriesHook.tapPromise("1", (name, age) => {
console.log( name, age);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("1", name, age, new Date());
resolve("1");
}, 1000);
});
});
asyncSeriesHook.tapPromise("2", (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("2", name, age, new Date());
resolve("2");
}, 2000);
});
});
asyncSeriesHook.tapPromise("3", (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("3", name, age, new Date());
resolve("3");
console.timeEnd("time");
}, 3000);
});
});
// 触发事件,让监听函数执行
asyncSeriesHook.promise("hannie", 18).then(ret => {
console.log(ret);
});
>
1 hannie 18 2020-08-03T06:42:05.558Z
2 hannie 18 2020-08-03T06:42:07.565Z
3 hannie 18 2020-08-03T06:42:10.567Z
time: 6016.385ms
3