Webpack4.0 source code analysis of Tapable

1 TapableIntroduction

webpackEssentially a mechanism for event flow, its workflow is to connect the various plugins together, and the core to achieve this isTapablewebpackThe 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:

First of all, the overall feel of the use of the hook, as follows

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

Intelligent Recommendation

tapable

Webpack is a collection of plugins. The webpack implementation uses the event flow mechanism, and the tapable is the core library on which the webpack event flow mechanism depends. Tapable provides a ...

Exploring webpack source code (3) --- Tapable plugin use 2018-04-10

Last time I said that I was ready to exploreCompiler.jsBut I looked at itCompiler.jsThe most important thing inside is the one used.TapableSo, let's start with the pigeons [manually laughing and cryin...

WebPack Source Code - [1: Learn Tapable to learn more about WebPack's plug-in mechanism]

What is TAPABLE? what's the effect?     tapable Is a similarNode.js middle EventEmitter Library, but more focusedCustom event trigger and processing    tapable Create a variety of ...

Tapable listener function performs compile analysis

Tapable listener function performs compile analysis 1. header 2. create method 3. sync Subscription function re-defined The specific content of content is turned over to the subclass injects onError, ...

4 cases of webpack4.0 code debugging

4 cases Webpack.config.js configuration file...

More Recommendation

Detailed configuration package on the code division optimization.splitChunks of webpack4.0

optimizationCan achieve many functions, the most common functions are: three shaking only supports the introduction of ES Module module, the module does not delete the referenced package Split Code Co...

Webpack4.0-css file code segmentation -09

Use the mini-css-extract-plugin plugin to code split the css file optimize-css-assets-webpack-plugin for css file compression...

Webpack4.0 closes the development environment code compression UglifyJsPlugin

The most recent project used webpack 4.1.1. I didn't read the updated version of the document carefully before using it. I found that there are still big changes during the use process. One of them is...

Analysis of Tapable used in webpack-synchronization hook implementation and simulation implementation

tapable Webpack is essentially an event flow mechanism. Its workflow is to connect various plug-ins in series, and the core of all this is tapable. Tapable is somewhat similar to the events library of...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top