preface
Why do we learn tapable because…. Webpack source code are all using tapable to achieve hook mount, as a bit of a pursuit of code, how can Webpack just satisfied with it? Of course is to see the source code, write loader,plugin. Before this, if you do not know the use of tapable, the source code that is more need not read, do not understand….. So let’s talk about tapable today
1. tapable
In essence, Webpack is a mechanism of event flow. Its workflow is to connect various plug-ins in series, and the core to achieve all this is Tapable. The most core Compiler and Compilation of bundles in Webpack are both instances of Tapable
The parameters that Tapable passes when creating an instance have no effect on the program’s execution, except to help the source reader
Similarly, when tapping * is used to register a listener, the first parameter passed is just an identifier and has no effect on the program’s execution. The second argument is the callback function
2. The use of the tapable
const {
SyncHook,
SyncBailHook,
SyncWaterHook,
SyncLoopHook
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
The serial number | The name of the hook | Implement way | Use the point |
---|---|---|---|
1 | SyncHook | Synchronous serial | Does not care about the return value of the listening function |
2 | SyncBailHook | Synchronous serial | If one of the listening functions does not return null, the residual logic is skipped |
3 | SyncWaterfallHook | Synchronous serial | The return value of the previous listener is passed as an argument to the next listener |
4 | SyncLoopHook | Synchronous serial | When fired, the listener is repeated if it returns true, and exits the loop if it returns undefined |
5 | AsyncParallelHook | Asynchronous parallel | Does not care about the return value of the listening function |
6 | AsyncParallelBailHook | Asynchronous parallel | As long as the return value of the listener function is not null, the subsequent listener function execution is ignored, and the callback function that triggers the function binding, such as callAsync, is directly skipped, and the bound callback function is executed |
7 | AsyncSeriesHook | Asynchronous serial port | Do not care about arguments to callback() |
8 | AsyncSeriesBailHook | Asynchronous serial port | Callback () does not have a null argument, and calls such as callAsync that trigger the function binding are executed directly |
9 | AsyncSeriesWaterfallHook | Asynchronous serial port | The second argument to the callback(err, data) in the previous listener can be used as an argument to the next listener |
Sync* type hook
- Plug-ins registered under this hook are executed sequentially
- Only TAP can be used for registration, not tapPromise and tapAsync
3.1 SyncHook
After registering tap on an instance of SyncHook, these TAP callback functions must be executed sequentially whenever the instance calls the call method
let queue = new SyncHook(['Parameters that have no effect']);
queue.tap(1,(name,age)=>{
console.log(name,age)
})
queue.tap(2,(name,age)=>{
console.log(name,age)
})
queue.tap(3,(name,age)=>{
console.log(name,age)
})
queue.call('bearbao'.8)
// Output the result
// 'bearbao' 8
// 'bearbao' 8
// 'bearbao' 8
Copy the code
3.1.1 SyncHook implementation
class SyncHook {
constructor() {this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener) } call(... args){this.listeners.forEach(l= >l(... args)) } }Copy the code
3.2 SyncBailHook
Serial synchronous execution, if any return value is not null, skip the rest of the logic
let queue = new SyncBailHook(['name'])
queue.tap(1,name=>{
console.log(name)
})
queue.tap(1,name=>{
console.log(name)
return '1'
})
queue.tap(1,name=>{
console.log(name)
})
queue.call('bearbao')
// Only the first two callbacks are executed and the third is not executed
// bearbao
// bearbao
Copy the code
implementation
class SyncBailHook {
constructor() {this.listeners = [];
}
tap(formal,listener){
this.listeners.push(listener) } call(... args){for(let i=0; i<this.listeners.length; i++){if(this.listeners[i]()) break; }}}Copy the code
3.3 SyncWaterHook
In serial synchronization, the first registered callback receives all arguments from call, and each subsequent callback receives only one argument, the return value of the previous callback.
let queue = new SyncWaterHook(['name'.'age']);
queue.tap(1,(name,age)=>{
console.log(name,age)
return 1
})
queue.tap(2,(ret)=>{
console.log(ret)
return 2
})
queue.tap(3,(ret)=>{
console.log(ret)
return 3
})
queue.call('bearbao'.3)
// Output the result
// bearbao 3
/ / 1
/ / 2
Copy the code
SyncWaterHook implementation. The SyncWaterHook method is similar to the compose method in Redux, which passes the return value of one function to the next as an argument.
For those of you who are wondering about the call method, please refer to my previous interpretation of the compose function, which is covered in detail
Implementation and analysis of Redux advanced compose method
class SyncWaterHook{
constructor() {this.listeners = [];
}
tap(formal,listener){
this.listener.unshift(listener); } call(... args){this.listeners.reduce((a,b) = >(. args) = >a(b(... args)))(... args) } }Copy the code
3.4 SyncLoopHook
Serial synchronous execution, the listener returns true to continue the loop, return undefined to end the loop
let queue = new SyncLoopHook;
let index = 0;
queue.tap(1,_=>{
index++
if(index<3) {console.log(index);
return true
}
})
queue.call();
// Output the result
/ / 1
/ / 2
Copy the code
SyncLoopHook implementation
class SyncLoopHook{
constructor() {
this.tasks=[];
}
tap(name,task) {
this.tasks.push(task); } call(... args) {this.tasks.forEach(task= > {
let ret=true;
do{ ret = task(... args); }while(ret) }); }}Copy the code
4. Async* hooks
- Support TAP, tapPromise, tapAsync registration
- Each time, tap, tapSync, and tapPromise are called to register different types of plug-in hooks, which are called by calling Call, callAsync, and Promise. In order to execute according to certain execution strategies, the compile method is called to quickly compile a method to execute these plug-ins.
4.1 AsyncParallel
Asynchronous parallel execution
4.1.1 AsyncParallelHook
Does not care about the return value of the listening function.
There are three modes of registration/publication, as follows
Asynchronous subscription | A method is called |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
- Use it through TAP
The last of the arguments that trigger the function is the callback after the asynchronous listening callback has finished executing, and the other arguments are passed to the callback function
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tap('1'.function(name){
console.log(name,1);
});
queue.tap('2'.function(name){
console.log(name,2);
});
queue.tap('3'.function(name){
console.log(name,3);
});
queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// Execution result
/* Bearbao 1 bearbao 2 Bearbao 3 cost: 4.720ms */
Copy the code
implementation
class AsyncParallelHook {
constructor() {this.listeners = [];
}
tap(name,listener){
this.listeners.push(listener);
}
callAsync(){
this.listeners.forEach(listener= >listener(... arguments));Array.from(arguments).pop()(); }}Copy the code
- Register with tapAsync
Note that there is a special area here. How do you confirm that a callback has completed? The last argument to each listener callback is a callback function, and the current function is considered finished when the callback is executed
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapAsync('1'.function(name,callback){
setTimeout(function(){
console.log(name, 1);
callback();
},1000)}); queue.tapAsync('2'.function(name,callback){
setTimeout(function(){
console.log(name, 2);
callback();
},2000)}); queue.tapAsync('3'.function(name,callback){
setTimeout(function(){
console.log(name, 3);
callback();
},3000)}); queue.callAsync('bearbao',err=>{
console.log(err);
console.timeEnd('cost');
});
// Output the result
/*
bearbao 1
bearbao 2
bearbao 3
cost: 3000.448974609375ms
*/
Copy the code
implementation
class AsyncParallelHook {
constructor() {this.listeners = [];
}
tapAsync(name,listener){
this.listeners.push(listener); } callAsync(... arg){let callback = arg.pop();
let i = 0;
let done = (a)= >{
if(++i==this.listeners.length){
callback()
}
}
this.listeners.forEach(listener= >listener(...arg,done));
}
}
Copy the code
- Using tapPromise
When you register a listener using tapPromise, the return value of each callback function must be an instance of a Promise
let queue = new AsyncParallelHook(['name']);
console.time('cost');
queue.tapPromise('1'.function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(1);
resolve();
},1000)}); }); queue.tapPromise('2'.function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(2);
resolve();
},2000)}); }); queue.tapPromise('3'.function(name){
return new Promise(function(resolve,reject){
setTimeout(function(){
console.log(3);
resolve();
},3000)}); }); queue.promise('bearbao').then((a)= >{
console.timeEnd('cost');
})
// Perform demerit recording
/* 1 2 3 cost: 3000.448974609375ms */
Copy the code
implementation
class AsyncParallelHook {
constructor() {this.listeners = [];
}
tapPromise(name,listener){
this.listeners.push(listener); } promise(... arg){let i = 0;
return Promise.all(this.listeners.map(l= >l(arg)))
}
}
Copy the code
5. I feel so sleepy
Accidentally to 1 o ‘clock again, in order to achieve longevity achievements, today I write here, the following several methods, in two days to update
conclusion
If you think it is ok and can bring some help to you on the way of coding, please give a thumbs-up and encouragement, thank you!