1 TapableIntroduction
webpackEssentially a mechanism for event flow, its workflow is to connect the various plugins together, and the core to achieve this isTapable,webpackThe most core of the compilationCompilerAnd responsible for creating bundlesCompilationAllTapableAn example. This article mainly introduces the hook function in Tapable.
The tapable package exposes a number of hook classes that can be used to create hook functions for plugins, including the following:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy code
The constructor of all hook classes receives an optional argument, which is an array of string arguments, as follows:
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
Copy code
Let's take a closer look at the use of hooks and the principles of some hook classes.
2 hooks overview
Commonly used hooks mainly include the following types, which are divided into synchronous and asynchronous, and asynchronously divided into concurrent execution and serial execution, as shown in the following figure:
| Serial number | Hook name | Implementation modalities | Use points |
|---|---|---|---|
| 1 | SyncHook | Synchronous serial | Don't care about the return value of the listener function |
| 2 | SyncBailHook | Synchronous serial | As long as there is a function in the listener function, the return value is notnull, skip all remaining logic |
| 3 | SyncWaterfallHook | Synchronous serial | The return value of the previous listener function can be passed to the next listener function. |
| 4 | SyncLoopHook | Synchronous loop | When the listener function is triggered, if the listener returnstrueThis listener function will be executed repeatedly if it returnsundefined Then exit the loop |
| 5 | AsyncParallelHook | Asynchronous concurrency | Don't care about the return value of the listener function |
| 6 | AsyncParallelBailHook | Asynchronous concurrency | As long as the return value of the listener function is notnull, will ignore the subsequent listener function execution, jump directly tocallAsyncThe callback function that triggers the function binding, and then executes the bound callback function. |
| 7 | AsyncSeriesHook | Asynchronous serial | Irrelevantcallback()Parameter |
| 8 | AsyncSeriesBailHook | Asynchronous serial | callback()The parameter is notnullWill execute directlycallAsyncCallback function that triggers function binding |
| 9 | AsyncSeriesWaterfallHook | Asynchronous serial | In the last listener functioncallback(err, data)The second argument, which can be used as the argument to the next listener function |
3 hooks
Sync* hook
Synchronous serial
(1) SyncHook
Don't care about the return value of the listener function
- usage
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //All constructors receive an optional argument, which is an array of strings.
// subscribe
queue.tap('1', function (name, name2) {// The first argument to tap is the function that identifies the subscription.
console.log(name, name2, 1);
return '1'
});
queue.tap('2', function (name) {
console.log(name, 2);
});
queue.tap('3', function (name) {
console.log(name, 3);
});
// release
queue.call('webpack', 'webpack-cli');// The function that triggers the subscription when it is published. Also pass in the parameters.
// Results of the:
/*
Webpack undefined 1 // The parameters passed in need to be consistent with the new instance, otherwise the parameters of the multipass are not obtained.
webpack 2
webpack 3
*/
Copy code
- principle
class SyncHook_MY{
constructor(){
this.hooks = [];
}
// subscribe
tap(name, fn){
this.hooks.push(fn);
}
// release
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
Copy code
(2) SyncBailHook
As long as there is a function in the listener function, the return value is notnull, skip all remaining logic
- usage
const {
SyncBailHook
} = require("tapable");
let queue = new SyncBailHook(['name']);
queue.tap('1', function (name) {
console.log(name, 1);
});
queue.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue.tap('3', function (name) {
console.log(name, 3);
});
queue.call('webpack');
// Results of the:
/*
webpack 1
webpack 2
*/
Copy code
- principle
class SyncBailHook_MY {
constructor() {
this.hooks = [];
}
// subscribe
tap(name, fn) {
this.hooks.push(fn);
}
// release
call() {
for (let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
Copy code
(3) SyncWaterfallHook
The return value of the previous listener function can be passed to the next listener function.
- usage
const {
SyncWaterfallHook
} = require("tapable");
let queue = new SyncWaterfallHook(['name']);
// The return value of the previous function can be passed to the next function
queue.tap('1', function (name) {
console.log(name, 1);
return 1;
});
queue.tap('2', function (data) {
console.log(data, 2);
return 2;
});
queue.tap('3', function (data) {
console.log(data, 3);
});
queue.call('webpack');
// Results of the:
/*
webpack 1
1 2
2 3
*/
Copy code
- principle
class SyncWaterfallHook_MY{
constructor(){
this.hooks = [];
}
// subscribe
tap(name, fn){
this.hooks.push(fn);
}
// release
call(){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...arguments): hook(result);
}
}
}
Copy code
(4) SyncLoopHook
When the listener function is triggered, if the listener returnstrueThis listener function will be executed repeatedly if it returnsundefined Then exit the loop
- usage
const {
SyncLoopHook
} = require("tapable");
let queue = new SyncLoopHook(['name']);
let count = 3;
queue.tap('1', function (name) {
console.log('count: ', count--);
if (count > 0) {
return true;
}
return;
});
queue.call('webpack');
// Results of the:
/*
count: 3
count: 2
count: 1
*/
Copy code
- principle
class SyncLoopHook_MY {
constructor() {
this.hook = null;
}
// subscribe
tap(name, fn) {
this.hook = fn;
}
// release
call() {
let result;
do {
result = this.hook(...arguments);
} while (result)
}
}
Copy code
Async* hook
Asynchronous parallelism
(1) AsyncParallelHook
Don't care about the return value of the listener function.
There are three registration/release modes, as follows:
| Asynchronous subscription | Call method |
|---|---|
| tap | callAsync |
| tapAsync | callAsync |
| tapPromise | promise |
- usage - tap
const {
AsyncParallelHook
} = require("tapable");
let queue1 = new AsyncParallelHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// Results of the
/*
webpack 1
webpack 2
webpack 3
cost: 4.520ms
*/
Copy code
- usage - tapAsync
let queue2 = new AsyncParallelHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// Results of the
/*
webpack 1
webpack 2
webpack 3
over
time: 3004.411ms
*/
Copy code
- usage - promise
let queue3 = new AsyncParallelHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
resolve();
}, 2000);
});
});
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
/*
webpack 1
webpack 2
webpack 3
over
cost3: 3007.925ms
*/
Copy code
(2) AsyncParallelBailHook
As long as the return value of the listener function is notnull, will ignore the subsequent listener function execution, jump directly tocallAsyncThe callback function that triggers the function binding, and then executes the bound callback function.
- usage - tap
let queue1 = new AsyncParallelBailHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
console.log(name, 1);
});
queue1.tap('2', function (name) {
console.log(name, 2);
return 'wrong'
});
queue1.tap('3', function (name) {
console.log(name, 3);
});
queue1.callAsync('webpack', err => {
console.timeEnd('cost');
});
// Results of the:
/*
webpack 1
webpack 2
cost: 4.975ms
*/
Copy code
- usage - tapAsync
let queue2 = new AsyncParallelBailHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
return 'wrong';// The last callback will not be called.
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', () => {
console.log('over');
console.timeEnd('cost1');
});
// Results of the:
/*
webpack 1
webpack 2
webpack 3
*/
Copy code
- usage - promise
let queue3 = new AsyncParallelBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 1);
resolve();
}, 1000);
});
});
queue3.tapPromise('2', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 2);
reject('wrong');// The argument of reject() is a non-null argument, the last callback will not be called again.
}, 2000);
});
});
queue3.tapPromise('3', function (name, cb) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log(name, 3);
resolve();
}, 3000);
});
});
queue3.promise('webpack')
.then(() => {
console.log('over');
console.timeEnd('cost3');
}, () => {
console.log('error');
console.timeEnd('cost3');
});
// Results of the:
/*
webpack 1
webpack 2
error
cost3: 2009.970ms
webpack 3
*/
Copy code
Asynchronous serial
(1) AsyncSeriesHook
Irrelevantcallback()Parameter
- usage - tap
const {
AsyncSeriesHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('zfpx', err => {
console.log(err);
console.timeEnd('cost1');
});
// Results of the
/*
1
2
3
undefined
cost1: 3.933ms
*/
Copy code
- usage - tapAsync
let queue2 = new AsyncSeriesHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, cb) {
setTimeout(() => {
console.log(name, 1);
cb();
}, 1000);
});
queue2.tapAsync('2', function (name, cb) {
setTimeout(() => {
console.log(name, 2);
cb();
}, 2000);
});
queue2.tapAsync('3', function (name, cb) {
setTimeout(() => {
console.log(name, 3);
cb();
}, 3000);
});
queue2.callAsync('webpack', (err) => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// Results of the
/*
webpack 1
webpack 2
webpack 3
undefined
over
cost2: 6019.621ms
*/
Copy code
- usage - promise
let queue3 = new AsyncSeriesHook(['name']);
console.time('cost3');
queue3.tapPromise('1',function(name){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 1);
resolve();
},1000)
});
});
queue3.tapPromise('2',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 2);
resolve();
},2000)
});
});
queue3.tapPromise('3',function(name,callback){
return new Promise(function(resolve){
setTimeout(function(){
console.log(name, 3);
resolve();
},3000)
});
});
queue3.promise('webapck').then(err=>{
console.log(err);
console.timeEnd('cost3');
});
// Results of the
/*
webapck 1
webapck 2
webapck 3
undefined
cost3: 6021.817ms
*/
Copy code
- principle
class AsyncSeriesHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
var slef = this;
var args = Array.from(arguments);
let done = args.pop();
let idx = 0;
function next(err) {
// If the next parameter has a value, jump directly to the callback function that performs callAsync
if (err) return done(err);
let fn = slef.hooks[idx++];
fn ? fn(...args, next) : done();
}
next();
}
}
Copy code
(2) AsyncSeriesBailHook
callback()The parameter is notnullWill execute directlycallAsyncCallback function that triggers function binding
- usage - tap
const {
AsyncSeriesBailHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesBailHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(1);
return "Wrong";
});
queue1.tap('2', function (name) {
console.log(2);
});
queue1.tap('3', function (name) {
console.log(3);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.timeEnd('cost1');
});
// Results of the:
/*
1
null
cost1: 3.979ms
*/
Copy code
- usage - tapAsync
let queue2 = new AsyncSeriesBailHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log(name, 1);
callback();
}, 1000)
});
queue2.tapAsync('2', function (name, callback) {
setTimeout(function () {
console.log(name, 2);
callback('wrong');
}, 2000)
});
queue2.tapAsync('3', function (name, callback) {
setTimeout(function () {
console.log(name, 3);
callback();
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// Results of the
/*
webpack 1
webpack 2
wrong
over
cost2: 3014.616ms
*/
Copy code
- usage - promise
let queue3 = new AsyncSeriesBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 1);
resolve();
}, 1000)
});
});
queue3.tapPromise('2', function (name, callback) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name, 2);
reject();
}, 2000)
});
});
queue3.tapPromise('3', function (name, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(name, 3);
resolve();
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.log('over');
console.timeEnd('cost3');
}, err => {
console.log(err);
console.log('error');
console.timeEnd('cost3');
});
// Results of the:
/*
webpack 1
webpack 2
undefined
error
cost3: 3017.608ms
*/
Copy code
(3) AsyncSeriesWaterfallHook
In the last listener functioncallback(err, data)The second argument, which can be used as the argument to the next listener function
- usage - tap
const {
AsyncSeriesWaterfallHook
} = require("tapable");
// tap
let queue1 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
console.log(name, 1);
return 'lily'
});
queue1.tap('2', function (data) {
console.log(2, data);
return 'Tom';
});
queue1.tap('3', function (data) {
console.log(3, data);
});
queue1.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost1');
});
// Results of the:
/*
webpack 1
2 'lily'
3 'Tom'
null
over
cost1: 5.525ms
*/
Copy code
- usage - tapAsync
let queue2 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
setTimeout(function () {
console.log('1: ', name);
callback(null, 2);
}, 1000)
});
queue2.tapAsync('2', function (data, callback) {
setTimeout(function () {
console.log('2: ', data);
callback(null, 3);
}, 2000)
});
queue2.tapAsync('3', function (data, callback) {
setTimeout(function () {
console.log('3: ', data);
callback(null, 3);
}, 3000)
});
queue2.callAsync('webpack', err => {
console.log(err);
console.log('over');
console.timeEnd('cost2');
});
// Results of the:
/*
1: webpack
2: 2
3: 3
null
over
cost2: 6016.889ms
*/
Copy code
- usage - promise
let queue3 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log('1:', name);
resolve('1');
}, 1000)
});
});
queue3.tapPromise('2', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('2:', data);
resolve('2');
}, 2000)
});
});
queue3.tapPromise('3', function (data, callback) {
return new Promise(function (resolve) {
setTimeout(function () {
console.log('3:', data);
resolve('over');
}, 3000)
});
});
queue3.promise('webpack').then(err => {
console.log(err);
console.timeEnd('cost3');
}, err => {
console.log(err);
console.timeEnd('cost3');
});
// Results of the:
/*
1: webpack
2: 1
3: 2
over
cost3: 6016.703ms
*/
Copy code
- principle
class AsyncSeriesWaterfallHook_MY {
constructor() {
this.hooks = [];
}
tapAsync(name, fn) {
this.hooks.push(fn);
}
callAsync() {
let self = this;
var args = Array.from(arguments);
let done = args.pop();
console.log(args);
let idx = 0;
let result = null;
function next(err, data) {
if (idx >= self.hooks.length) return done();
if (err) {
return done(err);
}
let fn = self.hooks[idx++];
if (idx == 1) {
fn(...args, next);
} else {
fn(data, next);
}
}
next();
}
}
Copy code