写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址

Webpack本质上是一种基于事件流的编程范例,其实就是一系列的插件运行。

Webpack主要使用Compiler和Compilation两个类来控制Webpack的整个生命周期。他们都继承了Tapabel并且通过Tapabel来注册了生命周期中的每一个流程需要触发的事件。

首先声明一点:Webpack中最核心的两个对象:负责编译的Compiler和负责构建bundles的Compilation都继承自Tapable。

class Compilation extends Tapable {}
1
class Compiler extends Tapable {}
1

tapable介绍

Webpack本质上是一种事件流的机制,它的工作流程就是:将各个插件串联起来,而实现这一切的核心就是Tapable。

Tapable是一个类似于nodejs的EventEmitter的库,核心原理也依赖于发布-订阅模式。主要是控制钩子函数的发布与订阅,控制着webpack的插件系统。Tapable库对外暴露了很多Hook(钩子)类,为插件提供挂载的钩子函数。

const {
	SyncHook, // 同步钩子
	SyncBailHook, // 同步熔断钩子(熔断的作用:遇到return就会返回,停止向下执行)
	SyncWaterfallHook, // 同步流水钩子(流水的作用:前一个插件的执行结果作为参数传给下一个插件)
	SyncLoopHook, // 同步循环钩子
	AsyncParallelHook, // 异步并发钩子
	AsyncParallelBailHook, // 异步并发熔断钩子
	AsyncSeriesHook, // 异步串行钩子
	AsyncSeriesBailHook, // 异步串行熔断钩子
	AsyncSeriesWaterfallHook // 异步串行流水钩子
 } = require('tapable');
1
2
3
4
5
6
7
8
9
10
11

tapable暴露出来的都是类方法,new一个类方法就可以获得我们需要的钩子。 class接受数组参数options,非必传。类方法会根据传参,接受同样数量的参数。

const hook = new SyncHook(['args', 'arg2', 'arg3']);
1

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
1

来看个🌰:

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(); // 启动钩子
1
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);
1
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(); // 启动钩子
1
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);
1
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(); // 启动钩子
1
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学的不错喔
*/
1
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(); // 启动钩子
1
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
*/
1
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
*/
1
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
*/
1
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
*/
1
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
*/
1
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
*/
1
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(); // 启动钩子
1
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');
});
1
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
停止学习
*/
1
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: 停止学习了
*/
1
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();
1
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');
// });
1
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

参考文档

  1. webpack4.0源码分析之Tapable
  2. webpack插件机制之Tapable
  3. webpack系列之二Tapable
  4. Webpack4.0 source code analysis of Tapable
  5. Webpack插件机制之Tapable-源码解析

评 论: