写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址。
Webpack本质上是一种基于事件流的编程范例,其实就是一系列的插件运行。
Webpack主要使用Compiler和Compilation两个类来控制Webpack的整个生命周期。他们都继承了Tapabel并且通过Tapabel来注册了生命周期中的每一个流程需要触发的事件。
首先声明一点:Webpack中最核心的两个对象:负责编译的Compiler和负责构建bundles的Compilation都继承自Tapable。
class Compilation extends Tapable {}
class Compiler extends Tapable {}
tapable介绍
Webpack本质上是一种事件流的机制,它的工作流程就是:将各个插件串联起来,而实现这一切的核心就是Tapable。
Tapable是一个类似于nodejs的EventEmitter的库,核心原理也依赖于发布-订阅模式。主要是控制钩子函数的发布与订阅,控制着webpack的插件系统。Tapable库对外暴露了很多Hook(钩子)类,为插件提供挂载的钩子函数。
const {
SyncHook, // 同步钩子
SyncBailHook, // 同步熔断钩子(熔断的作用:遇到return就会返回,停止向下执行)
SyncWaterfallHook, // 同步流水钩子(流水的作用:前一个插件的执行结果作为参数传给下一个插件)
SyncLoopHook, // 同步循环钩子
AsyncParallelHook, // 异步并发钩子
AsyncParallelBailHook, // 异步并发熔断钩子
AsyncSeriesHook, // 异步串行钩子
AsyncSeriesBailHook, // 异步串行熔断钩子
AsyncSeriesWaterfallHook // 异步串行流水钩子
} = require('tapable');
2
3
4
5
6
7
8
9
10
11
tapable暴露出来的都是类方法,new一个类方法就可以获得我们需要的钩子。 class接受数组参数options,非必传。类方法会根据传参,接受同样数量的参数。
const hook = new SyncHook(['args', 'arg2', 'arg3']);
hooks概览
常用的钩子主要包含以下几种,分为同步和异步,异步又分为并发执行和串行执行,如下图:
钩子名称/执行方式 | 使用说明 |
---|---|
SyncHook/同步串行 | 不关心监听函数的返回值 |
SyncBailHook/同步串行 | 只要监听函数中有一个函数的返回值不为undefined,则跳过剩下所有的逻辑 |
SyncWaterfallHook/同步串行 | 上一个监听函数的返回值可以传给下一个监听函数 |
SyncLoopHook/同步循环 | 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环 |
AsyncParallelHook/异步并行 | 不关心监听函数的返回值 |
AsyncParallelBailHook/异步并行 | 只要监听函数的返回值不为undefined,就会忽略后面的监听函数执行,直接跳到callAsync函数绑定的回调函数,然后执行这个回调函数 |
AsyncSeriesHook/异步串行 | 不关系callback()的参数 |
AsyncSeriesBailHook/异步串行 | callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数 |
AsyncSeriesWaterfallHook/异步串行 | 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数 |
hooks类型总结
type | function |
---|---|
Hook | 所有钩子的后缀 |
Waterfall | 同步方法,但是它会传值给下一个函数 |
Bail | 熔断:当函数有任何返回值时,就会在当前执行函数停止 |
Loop | 监听函数返回true时则这个监听函数会循环执行,如果返回undefined则表示退出循环 |
Sync | 同步方法 |
AsyncSeries | 异步串行钩子 |
AsyncParallel | 异步并行执行钩子 |
钩子的绑定与执行
Tabable提供了同步和异步绑定钩子的方法,并且它们都有绑定事件和执行事件对应的方法。
Async* | Sync* |
---|---|
绑定:tapAsync/tapPromise/tap | 绑定:tap |
执行:callAsync/promise | 执行:call |
需要注意:同步钩子只能用tap绑定,但是异步钩子既可以使用tap绑定,也可以使用tapAsync/tapPromise绑定。
同步钩子
SyncHook
SyncHook不关心监听函数的返回值。
npm i tabable -D
来看个🌰:
const {SyncHook} = require('tapable'); // 解构同步钩子
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new SyncHook(['name', 'age'])
}
}
start() {
// 利用钩子的call方法调用监听函数
this.hooks.arch.call('tom', 12);
}
tap() {
// 利用钩子的tap方法注册监听函数
this.hooks.arch.tap('node', (name, age) => {
console.log('node', `${name}-${age}`); // node tom-12
});
this.hooks.arch.tap('vue', name => {
console.log('vue', name); // vue tom
});
}
}
const l = new Lesson();
// console.log(l.hooks.arch);
l.tap(); // 这里注册了两个监听函数
// console.log(l.hooks.arch.taps);
l.start(); // 启动钩子
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SyncHook钩子实现
class SyncHook {
constructor() {
this.tasks = [];
}
// 订阅
tap(name, task) {
this.tasks.push(task);
}
// 发布
call(...args) {
this.tasks.forEach(task => task(...args));
}
}
const hook = new SyncHook(['name', 'age']);
hook.tap('webpack', (name, age) => {
console.log('webpack', `${name}--${age}`);
});
hook.tap('node', (name, age) => {
console.log('node', `${name}--${age}`);
});
hook.call('tom', 12);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SyncBailHook
SyncBailHook为钩子加个保险,当return返回不是undefine时就会停止。
来看个🌰:
const {SyncBailHook} = require('tapable'); // 解构同步钩子
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new SyncBailHook(['name', 'age'])
}
}
start() {
// 利用钩子的call方法调用监听函数
this.hooks.arch.call('tom', 12);
}
tap() {
// 利用钩子的tap方法注册监听函数
this.hooks.arch.tap('node', (name, age) => {
console.log('node', `${name}-${age}`); // node tom-12
// return '有点累,停止学习'; // 会停止
// return undefined; // 不会停止
});
this.hooks.arch.tap('vue', name => {
console.log('vue', name); // vue tom
// 没有return语句,默认是return undefined;
});
}
}
const l = new Lesson();
console.log(l.hooks.arch);
l.tap(); // 注册监听函数
console.log(l.hooks.arch.taps);
l.start(); // 启动钩子
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SyncBailHook钩子实现:
class SyncBailHook {
constructor() {
this.tasks = [];
}
// 订阅
tap(name, task) {
this.tasks.push(task);
}
// 发布
// 这里用函数剩余运算符接收若干个参数,将所有参数存入数组args中
call(...args) {
let res; // 当前回调函数的返回值
let index = 0; // 当前要执行的回调函数索引值,默认从第一个开始
// 至少要执行一个回调
// 当前回调返回undefined且还有回调未执行的话再继续执行
do {
// 回调函数执行的时候,需要利用数组的展开运算符,将数组中的参数分别传递给订阅函数
res = this.tasks[index++](...args);
} while (res === undefined && this.tasks.length > index);
}
}
const hook = new SyncBailHook(['name', 'age']);
// 这里虽然注册了两个回调,但是node回调不会执行
hook.tap('webpack', (name, age) => {
console.log('webpack', `${name}--${age}`);
return '停止学习';
});
hook.tap('node', (name, age) => {
console.log('node', `${name}--${age}`);
});
hook.call('tom', 12);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SyncWaterfallHook
SyncWaterfallHook钩子监听上一个函数的返回值并将该返回值传给下一个监听函数。
来看个🌰:
const {SyncWaterfallHook} = require('tapable'); // 解构同步钩子
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子 waterfall 瀑布
arch: new SyncWaterfallHook(['name', 'age'])
}
}
start() {
// 利用钩子的call方法调用监听函数
this.hooks.arch.call('tom', 12);
}
tap() {
// 利用钩子的tap方法注册监听函数
this.hooks.arch.tap('node', (name, age) => {
console.log('node', `${name}-${age}`); // node tom-12
return 'node学的不错'; // return返回值是传递给下一个监听函数的数据
});
this.hooks.arch.tap('vue', data => {
console.log('vue', data); // vue node学的不错
});
}
}
const l = new Lesson();
l.tap(); // 注册监听函数
l.start(); // 启动钩子
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
SyncWaterfallHook钩子实现
class SyncWaterfallHook { // 同步钩子-瀑布
constructor() {
this.tasks = [];
}
// 订阅
tap(name, task) {
this.tasks.push(task);
}
// 发布
// 这里用函数剩余运算符接收若干个参数,将所有参数存入数组args中
call(...args) {
// 解构获取第一个task和其余剩余的task
const [first, ...others] = this.tasks;
const res = first(...args); // 获取第一个tash回调函数的返回值
// reduce迭代
// pre是前一个task的返回结果,cur是当前task
others.reduce((pre, cur) => {
return cur(pre);
}, res);
}
}
const hook = new SyncWaterfallHook(['name', 'age']);
hook.tap('webpack', (name, age) => {
console.log('webpack', `${name}--${age}`);
return 'webpack学的不错喔';
});
hook.tap('node', data => {
console.log('node', data);
return 'node学的不错喔';
});
hook.tap('vue', data => {
console.log('vue', data);
});
hook.call('tom', 12);
/**
执行结果:
webpack tom--12
node webpack学的不错喔
vue node学的不错喔
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
SyncLoopHook
SyncLoopHook钩子当监听函数被触发的时候,如果该监听函数返回true时,这个监听函数会多次执行,如果返回undefined,则表示退出循环。
来看个🌰:
const {SyncLoopHook} = require('tapable'); // 解构同步钩子
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
// 订阅钩子
arch: new SyncLoopHook(['name', 'age'])
}
}
start() {
// 利用钩子的call方法调用监听函数
this.hooks.arch.call('tom', 12);
}
tap() {
// 利用钩子的tap方法注册监听函数
this.hooks.arch.tap('node', (name, age) => {
console.log('node', `${name}-${age}`); // node tom-12
return ++this.index === 3 ? undefined : '继续学';
});
this.hooks.arch.tap('vue', name => {
console.log('vue', name);
});
}
}
const l = new Lesson();
console.log(l.hooks.arch);
l.tap(); // 注册监听函数
console.log(l.hooks.arch.taps);
l.start(); // 启动钩子
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
SyncLoopHook钩子实现
class SyncLoopHook { // 同步钩子-瀑布
constructor() {
this.tasks = [];
}
// 订阅
tap(name, task) {
this.tasks.push(task);
}
// 发布
// 这里用函数剩余运算符接收若干个参数,将所有参数存入数组args中
call(...args) {
this.tasks.forEach(task => {
let res; // res为当前监听函数的返回值
// 当该返回值不为undefined时继续执行
do {
res = task(...args);
} while (res !== undefined);
});
}
}
const hook = new SyncLoopHook(['name', 'age']);
let total = 0;
hook.tap('webpack', (name, age) => {
console.log('webpack', `${name}--${age}`);
return ++total === 3 ? undefined : '继续学';
});
hook.tap('node', name => {
console.log('node', name);
});
hook.tap('vue', name => {
console.log('vue', name);
});
hook.call('tom', 12);
/**
执行结果:
webpack tom--12
webpack tom--12
webpack tom--12
node tom
vue tom
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
异步钩子
异步的钩子分为串行
和并行
两种,并行
需要等待所有并发的异步事件执行完成后再执行回调。
Tapable库中有三种注册/发布模式:
订阅模式 | 调用方法 |
---|---|
同步订阅tap | call(同步调用) |
异步订阅tapAsync(callback) | callAsync(异步调用) |
异步订阅tapPromise | promise |
AsyncParallelHook(异步并行)
AsyncParallelHook
是异步并行的钩子:不关心监听函数的返回值。
来看个🌰:异步tapAsync注册。
const {AsyncParallelHook} = require('tapable'); // 解构异步钩子
// 异步tapAsync注册
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncParallelHook(['name'])
}
}
start() {
this.hooks.arch.callAsync('tom', () => {
console.log('end');
});
}
tap() {
// tapAsync异步订阅
this.hooks.arch.tapAsync('webpack', (name, callback) => {
setTimeout(() => {
console.log('webpack', name);
callback();
}, 1000);
});
this.hooks.arch.tapAsync('node', (name, callback) => {
setTimeout(() => {
console.log('node', name);
callback();
}, 1000);
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
执行结果:
webpack tom
node tom
end
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
来看个🌰:异步tapPromise注册。
const {AsyncParallelHook} = require('tapable'); // 解构异步钩子
// 异步tapPromise注册
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncParallelHook(['name'])
}
}
start() {
this.hooks.arch.promise('tom').then(() => {
console.log('end');
})
}
tap() {
// tapPromise异步订阅
this.hooks.arch.tapPromise('webpack', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('webpack', name);
resolve();
}, 1000);
})
});
this.hooks.arch.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
});
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
执行结果:
webpack tom
node tom
end
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
AsyncParallelHook钩子实现
class AsyncParallelHook { // 钩子是异步的
constructor() {
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
// 获取到最终的执行函数
const finalCallback = args.pop();
let index = 0;
// 类似于promise.all的实现
const done = () => {
index++;
if (index === this.tasks.length) {
finalCallback();
}
};
this.tasks.forEach(task => {
// pop方法会修改原数组,所以这里的args已经把最后一个参数删掉
// 每个task都会执行done方法,用来判断是否所有task都执行完毕
task(...args, done);
})
}
tapPromise(name, task) {
this.tasks.push(task);
}
promise(...args) {
const tasks = this.tasks.map(task => {
return task(...args);
});
return Promise.all(tasks);
}
}
const hook = new AsyncParallelHook(['name']);
// hook.tapAsync('react', (name, callback) => {
// setTimeout(() => {
// console.log('react', name);
// callback()
// }, 1000)
// });
// hook.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name);
// callback()
// }, 1000)
// });
// hook.tapAsync('webpack', (name, callback) => {
// setTimeout(() => {
// console.log('webpack', name);
// callback();
// }, 1000);
// });
// hook.callAsync('tom', () => {
// console.log('end');
// });
hook.tapPromise('react', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 1000);
});
});
hook.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
});
});
hook.promise('tom').then(() => {
console.log('end');
});
/**
执行结果
react tom
node tom
webpack tom
end
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
AsyncParallelBailHook
AsyncParallelBailHook
是一个带保险的异步回调钩子,只要监听函数的返回值不为undefined
,就会忽略后面的监听函数执行,直接跳到callAsync
等触发函数绑定的回调函数,然后执行这个被绑定的回调函数。使用和原理与SyncBailHook
相似。
来看个🌰:tapAsync异步订阅。
const {AsyncParallelBailHook} = require('tapable'); // 解构异步钩子
// 异步tapAsync注册
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncParallelBailHook(['name'])
}
}
start() {
this.hooks.arch.callAsync('tom', () => {
console.log('end');
});
}
tap() {
// tapAsync异步订阅
this.hooks.arch.tapAsync('webpack', (name, callback) => {
setTimeout(() => {
console.log('webpack', name);
// return '停止向下执行';// 后面的回调就不会调用了
callback();
}, 1000);
});
this.hooks.arch.tapAsync('node', (name, callback) => {
setTimeout(() => {
console.log('node', name);
callback();
}, 1000);
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
执行结果:
webpack tom
node tom
end
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
来看个🌰:tapPromise异步订阅。
const {AsyncParallelBailHook} = require('tapable'); // 解构异步钩子
// 异步tapPromise注册
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncParallelBailHook(['name'])
}
}
start() {
this.hooks.arch.promise('tom').then(() => {
console.log('end');
});
}
tap() {
// tapPromise异步订阅
this.hooks.arch.tapPromise('webpack', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('webpack', name);
reject('wrong');// reject()的参数是一个不为null的参数时,后面的回调就不会再调用了
// resolve();
}, 1000);
});
});
this.hooks.arch.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
});
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
执行结果:
webpack tom
node tom
end
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
AsyncSeriesHook(异步串行)
AsyncSeriesHook
钩子是异步串行(
one by one)。
来看个🌰:
const {AsyncSeriesHook} = require('tapable'); // 解构异步钩子
class Lesson {
constructor() {
this.index = 0;
this.hooks = {
// 订阅钩子
arch: new AsyncSeriesHook(['name'])
}
}
start() {
// 发布
// this.hooks.arch.callAsync('tom', () => {
// console.log('end');
// })
// 另一种发布
this.hooks.arch.promise('tom').then(() => {
console.log('end');
}
)
}
tap() { // 注册监听函数,订阅
// this.hooks.arch.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name)
// callback()
// }, 1000)
// })
// this.hooks.arch.tapAsync('react', (name, callback) => {
// setTimeout(() => {
// console.log('react', name)
// callback()
// }, 1000)
// })
// 另一种订阅
this.hooks.arch.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
})
})
this.hooks.arch.tapPromise('react', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 1000);
})
})
}
}
let l = new Lesson();
l.tap(); // 注册两个函数
l.start(); // 启动钩子
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
AsyncSeriesHook
钩子实现:
class AsyncSeriesHook {
constructor() {
this.tasks = [];
}
// tapAsync(name, task) {
// this.tasks.push(task);
// }
// callAsync(...args) {
// const finalCallback = args.pop();
// let index = 0;
// const next = () => {
// if (this.tasks.length === index) return finalCallback();
// const task = this.tasks[index++];
// task(...args, next);
// }
// next();
// }
tapPromise(name, task) {
this.tasks.push(task);
}
promise(...args) {
// 将promise串联起来
const [first, ...other] = this.tasks;
return other.reduce((p, n) => { // 类似redux源码
return p.then(() => n(...args));
}, first(...args));
}
}
const hook = new AsyncSeriesHook(['name']);
// hook.tapAsync('react', (name, callback) => {
// setTimeout(() => {
// console.log('react', name);
// callback();
// }, 1000);
// });
// hook.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name);
// callback();
// }, 1000);
// });
// hook.tapAsync('webpack', (name, callback) => {
// setTimeout(() => {
// console.log('webpack', name);
// callback();
// }, 1000);
// });
hook.tapPromise('react', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 1000);
})
});
hook.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
})
});
// hook.callAsync('tom', () => {
// console.log('end');
// });
hook.promise('tom').then(() => {
console.log('end');
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
AsyncSeriesBailHook
来看个🌰:tapAsync异步订阅
const {AsyncSeriesBailHook} = require('tapable');
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncSeriesBailHook(['name'])
}
}
start() {
this.hooks.arch.callAsync('tom', data => {
console.log(data);
});
}
tap() {
// tapAsync异步订阅
this.hooks.arch.tapAsync('webpack', (name, callback) => {
setTimeout(() => {
console.log('webpack', name);
callback();
}, 1000);
});
this.hooks.arch.tapAsync('node', (name, callback) => {
setTimeout(() => {
console.log('node', name);
callback('停止学习'); // 后面的回调将不会执行
}, 1000);
});
this.hooks.arch.tapAsync('vue', (name, callback) => {
setTimeout(() => {
console.log('vue', name);
callback();
}, 1000);
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
webpack tom
node tom
停止学习
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
来看个🌰:tapPromise异步订阅
const {AsyncSeriesBailHook} = require('tapable');
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncSeriesBailHook(['name'])
}
}
start() {
this.hooks.arch.promise('tom').then(data => {
console.log(data);
}, error => {
console.log('error:', error); // 停止学习了
});
}
tap() {
// tapPromise异步订阅
this.hooks.arch.tapPromise('webpack', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('webpack', name);
resolve();
}, 1000);
})
});
this.hooks.arch.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
reject('停止学习了');
}, 1000);
})
});
this.hooks.arch.tapPromise('vue', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('vue', name);
resolve();
}, 1000);
})
});
}
}
let l = new Lesson();
l.tap(); // 注册两个监听函数
l.start(); // 启动钩子
/**
webpack tom
node tom
error: 停止学习了
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
AsyncSeriesWaterfallHook
上一个监听函数的中的callback(err, data)
的第二个参数,可以作为下一个监听函数的参数。
来看个🌰:
const {AsyncSeriesWaterfallHook} = require('tapable'); // 解构异步钩子
class Lesson {
constructor() {
this.hooks = {
// 订阅钩子
arch: new AsyncSeriesWaterfallHook(['name'])
};
}
start() {
// this.hooks.arch.callAsync('tom', () => {
// console.log('end');
// });
this.hooks.arch.promise('tom').then(() => {
console.log('end');
});
}
tap() { // 注册监听函数,订阅
// this.hooks.arch.tapAsync('node', (name, callback) => {
// setTimeout(() => {
// console.log('node', name);
// // callback(null, 'node学的不错喔');
// callback('aaa', 'result'); // 如果第一个参数不是null,会直接跳过后面的钩子,直接走到最终的
// }, 1000);
// });
// this.hooks.arch.tapAsync('react', (data, callback) => {
// setTimeout(() => {
// console.log('react', data);
// callback();
// }, 1000);
// });
// 另一种订阅方式
this.hooks.arch.tapPromise('node', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name);
resolve();
}, 1000);
});
});
this.hooks.arch.tapPromise('react', name => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('react', name);
resolve();
}, 1000);
});
});
}
}
const l = new Lesson();
l.tap();
l.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
AsyncSeriesWaterfallHook
钩子实现
class AsyncSeriesWaterfallHook {
constructor(args) { // args => ['name']
this.tasks = [];
}
tapAsync(name, task) {
this.tasks.push(task);
}
callAsync(...args) {
const finalCallback = args.pop();
let index = 0;
const next = (err, data) => {
let task = this.tasks[index];
// 如果task没有取到,则执行最后一个
if(!task) return finalCallback();
if (index === 0) {
// 执行第一个
task(...args, next);
} else {
task(data, next);
}
index++;
}
next(); // 先调一次
}
// tapPromise(name, task) {
// this.tasks.push(task);
// }
// promise(...args) {
// // 将promise串联起来
// const [first, ...other] = this.tasks;
// return other.reduce((p, n) => {
// return p.then(data => n(data));
// }, first(...args));
// }
}
const hook = new AsyncSeriesWaterfallHook(['name']);
hook.tapAsync('react', (name, callback) => {
setTimeout(() => {
console.log('react', name);
callback(null, 'react学的不错喔');
}, 1000)
});
hook.tapAsync('node', (data, callback) => {
setTimeout(() => {
console.log('node', data);
callback(null, 'node学的不错喔');
}, 1000)
});
hook.tapAsync('webpack', (data, callback) => {
setTimeout(() => {
console.log('webpack', data);
callback();
}, 1000)
});
// hook.tapPromise('react', name => {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('react', name);
// resolve('react学的不错喔');
// }, 1000);
// });
// })
// hook.tapPromise('node', data => {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// console.log('node', data);
// resolve();
// }, 1000);
// });
// });
hook.callAsync('tom', () => {
console.log('end');
});
// hook.promise('tom').then(() => {
// console.log('end');
// });
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
参考文档
- webpack4.0源码分析之Tapable
- webpack插件机制之Tapable
- webpack系列之二Tapable
- Webpack4.0 source code analysis of Tapable
- Webpack插件机制之Tapable-源码解析