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!