Webpack is essentially an event stream mechanism. Tapable is the core library that enables Webpack to concatenate plug-ins. The Compiler responsible for compiling and the Compilation responsible for creating bundles are instances of the Tapable constructor

This article will simulate the tapable library “hook” event processing mechanism, understand tapable, can learn the principle of Webpack do a foundation.

In Webpack, what these “hooks” really do is connect plug-ins read from configuration files to plug-ins and loaders to loaders, executing in “parallel” or “serial.”

Handwriting these methods are not only familiar with tapable underlying principles, but also cause their own synchronous and asynchronous implementations of serial and parallel respectively.

Tapable core library class

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
 } = require("tapable");
Copy the code

Sync type hook

SyncHook: serial synchronization

// Serial synchronization execution
class SyncHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName is the name of the plugin, just for comment purposes
	tap(taskName,task){
		this.tasks.push(task)
	}
	call(. args){
		// An exception can also be thrown if the parameters are insufficient
		if (args.length < this.instanseArgs.length) throw new Error("Insufficient parameters");

		// The parameters passed in are exactly the same as those specified in the array passed in to create the instance
		args = args.slice(0.this.instanseArgs.length);

		// Execute the event handlers in turn
		this.tasks.forEach(task= > task(...args));
	}

}


// Create an instance
let syncHook = new SyncHook(["name"."age".'t']);
// let syncHook = new SyncHook(["name", "age", 't']); // Insufficient parameters will be displayed
// Register events
syncHook.tap("tast1".(name, age) = > console.log("1", name, age));
syncHook.tap("tast2".(name, age) = > console.log("2", name, age));
syncHook.tap("tast3".(name, age) = > console.log("3", name, age));

// Trigger the event to make the listener function execute
syncHook.call("hannie".18);

>
1 hannie 18
2 hannie 18
3 hannie 18
Copy the code

SyncBailHook: Serial synchronous execution, if the event handler executes with a return value that is not null (i.e., undefined), the remaining unexecuted event handlers are skipped

/** * SyncBailHook is also executed in serial synchronization. If the event handler is executed with a return value that is not null (i.e., undefined), the remaining unexecuted event handler */ is skipped
// Serial synchronization execution
class SyncBailHook {
  constructor(instanseArgs) {
    this.instanseArgs = instanseArgs;
    this.tasks = [];
  }
  // taskName is the name of the plugin, just for comment purposes
  tap(taskName, task) {
    this.tasks.push(task)
  }
  call(. args) {
    // An exception can also be thrown if the parameters are insufficient
    if (args.length < this.instanseArgs.length) throw new Error("Insufficient parameters");

    // The parameters passed in are exactly the same as those specified in the array passed in to create the instance
    args = args.slice(0.this.instanseArgs.length);

    // Execute the event handlers in turn
    let ret = undefined;
    for (let i = 0; i < this.tasks.length; i++) {
      ret = this.tasks[i](args)
      if (ret) {
        break; }}// forEach cannot use break
    // this.tasks.forEach(function(task) { 
    // ret = task(args)
    // if(! ret){
    // break;
    / /}

    // });

    // Execute the event handlers in turn, stopping down if the return value is not null
    // let i = 0, ret;
    // do {
    // ret = this.tasks[i++](... args);
    // } while (! ret);}}// Create an instance
let syncBailHook = new SyncBailHook(["name"."age"]);

// Register events
syncBailHook.tap("1".(name, age) = > console.log("1", name, age));

syncBailHook.tap("2".(name, age) = > {
  console.log("2", name, age);
  return "2";
});
// Skip execution
syncBailHook.tap("3".(name, age) = > console.log("3", name, age));

// Trigger the event to make the listener function execute
syncBailHook.call("hannie".18);

> 
 1 hannie 18
 2 hannie 18

Copy the code

SyncLoopHook: Serial loop execution

/** * The SyncBailHook function returns true to continue the loop, i.e. the loop executes the current event handler, and undefined to end the loop. SyncLoopHook loops through each event handler until it returns undefined, and then moves on to other event handlers. * /

class SyncLoopHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(. args) {
        // The parameters passed in are exactly the same as those specified in the array passed in to create the instance
        args = args.slice(0.this.args.length);

        // Execute the event handlers in turn, continuing with the current event handler if true is returned
        // Continue down to the other event handlers until undefined is returned
        this.tasks.forEach(task= > {
            let ret;
            do{ ret = task(... args); }while (ret === true| |! (ret ===undefined)); }); }}// Create an instance
let syncLoopHook = new SyncLoopHook(["name"."age"]);

// Define auxiliary variables
let total1 = 0;
let total2 = 0;

// Register events
syncLoopHook.tap("1".(name, age) = > {
    console.log("1", name, age, total1);
    return total1++ < 2 ? true : undefined;
});

syncLoopHook.tap("2".(name, age) = > {
    console.log("2", name, age, total2);
    return total2++ < 2 ? true : undefined;
});

syncLoopHook.tap("3".(name, age) = > console.log("3", name, age));

// Trigger the event to make the listener function execute
syncLoopHook.call("hannie".18);

> 
1 hannie 18 0
1 hannie 18 1
1 hannie 18 2
2 hannie 18 0
2 hannie 18 1
2 hannie 18 2
3 hannie 18

Copy the code

SyncWaterfallHook: Serial synchronization execution

/** * SyncWaterfallHook is executed sequentially, and the return value of the last event handler is passed as an argument to the next event handler, and so on. Because of this, only the arguments of the first event handler can be passed by call. Call returns the return value of the last event handler */
// Serial synchronization execution
class SyncWaterfallHook {
	constructor(instanseArgs) {
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
	// taskName is the name of the plugin, just for comment purposes
	tap(taskName, task) {
		this.tasks.push(task)
	}
	call(. args) {
		// An exception can also be thrown if the parameters are insufficient
		if (args.length < this.instanseArgs.length) throw new Error("Insufficient parameters");

		// The parameters passed in are exactly the same as those specified in the array passed in to create the instance
		args = args.slice(0.this.instanseArgs.length);

		let [first, ...others] = this.tasks;
		others.reduce((res, task) = > {
			returntask(res) }, first(... args)) } }// Create an instance
let syncWaterfallHook = new SyncWaterfallHook(["name"."age"]);

// Register events
syncWaterfallHook.tap("1".(name, age) = > {
	console.log("1", name, age);
	return "1";
});

syncWaterfallHook.tap("2".data= > {
	console.log("2", data);
	return "2";
});

syncWaterfallHook.tap("3".data= > {
	console.log("3", data);
	return "3"
});

// Trigger the event to make the listener function execute
let ret = syncWaterfallHook.call("hannie".18);
console.log("call", ret);

>
1 hannie 18
2 1
3 2
call undefined

Copy the code

Async type of hook

AsyncParallelHook (setTimeout) : Parallel and asynchronous execution

/** * parallel asynchronous execution */
class AsyncParallelHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName is the name of the plugin, just for comment purposes
	tapAsync(taskName,task){
		this.tasks.push(task)
	}
	callAsync(. args){
    let finalCallback = args.pop();

		// An exception can also be thrown if the parameters are insufficient
		if (args.length < this.instanseArgs.length) throw new Error("Insufficient parameters");

		// The parameters passed in are exactly the same as those specified in the array passed in to create the instance
		args = args.slice(0.this.instanseArgs.length); //["name", "age"]
  
    // let i = 0;
    // let done = () => {
    // if (++i === this.tasks.length) {
    // finalCallback();
    / /}
    // };
// Task is a function that contains the done callback
    this.tasks.forEach((task,index) = >{
      // task(... args,done)task(... args,() = >{
        if(index===this.tasks.length){ finalCallback(); }})// if(index===this.tasks.length){
      // finalCallback();
      //} // it works here, too, just to execute after the loop}}})// Create an instance
let asyncParallelHook = new AsyncParallelHook(["name"."age"]);

// Register events
console.time("time");
asyncParallelHook.tapAsync("1".(name, age, done) = > {
    setTimeout(() = > {
        console.log("1", name, age, new Date());
        done();
    }, 1000);
});

asyncParallelHook.tapAsync("2".(name, age, done) = > {
    setTimeout(() = > {
        console.log("2", name, age, new Date());
        done();
    }, 2000);
});

asyncParallelHook.tapAsync("3".(name, age, done) = > {
    setTimeout(() = > {
        console.log("3", name, age, new Date());
        done();
        console.timeEnd("time");
    }, 3000);
});

// Trigger the event to make the listener function execute
asyncParallelHook.callAsync("hannie".18.() = > {
    console.log("complete");
});


>
1 hannie 18 2020-08-03T06:13:14.007Z
2 hannie 18 2020-08-03T06:13:15.001Z
3 hannie 18 2020-08-03T06:13:16.004Z
time: 3005.195ms
Copy the code

AsyncParallelHook (Promise) : Parallel asynchronous execution

/** * parallel asynchronous execution */

class AsyncParallelHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName is the name of the plugin, just for comment purposes
  tapPromise(taskName,task){
		this.tasks.push(task)
	}
	promise(. args){

		// The parameters passed in are exactly the same as those specified in the array passed in to create the instance
		args = args.slice(0.this.instanseArgs.length); //["name", "age"]
  
  
    return Promise.all(this.tasks.map((task) = >{
      returntask(... args) }))// return Promise.all(this.tasks.map(task => task(... args)));}}// Create an instance
let asyncParallelHook = new AsyncParallelHook(["name"."age"]);

// Register events
console.time("time");
asyncParallelHook.tapPromise("1".(name, age) = > {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncParallelHook.tapPromise("2".(name, age) = > {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3".(name, age) = > {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// Trigger the event to make the listener function execute
asyncParallelHook.promise("hannie".18).then(ret= > {
    console.log(ret);
});

>
1 hannie 18 2020-08-03T06:22:47.198Z
2 hannie 18 2020-08-03T06:22:48.198Z
3 hannie 18 2020-08-03T06:22:49.197Z
time: 3004.218ms
[ '1'.'2'.'3' ]
Copy the code

AsyncSeriesHook (setTimeout) : Serial asynchronous execution

/** * serial asynchronous execution * step by step execution */

class AsyncSeriesHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
// taskName is the name of the plugin, just for comment purposes
    tapAsync(taskName,task){
        this.tasks.push(task)
    }
    callAsync(. args){
        let finalCallback = args.pop();
        // The parameters passed in are exactly the same as those specified in the array passed in to create the instance
        args = args.slice(0.this.instanseArgs.length); //["name", "age"]

        / / recursive next
        let i = 0;
        let next = () = > {
            let task = this.tasks[i++]; task ? task(... args, next) : finalCallback(); } next()// this.tasks.forEach((task,index)=>{
    // task(... args, next)
    // })}}// Create an instance
let asyncSeriesHook = new AsyncSeriesHook(["name"."age"]);

// Register events
console.time("time");
asyncSeriesHook.tapAsync("1".(name, age, next) = > {
    setTimeout(() = > {
        console.log("1", name, age, new Date());
        next();
    }, 1000);
});

asyncSeriesHook.tapAsync("2".(name, age, next) = > {
    setTimeout(() = > {
        console.log("2", name, age, new Date());
        next();
    }, 2000);
});

asyncSeriesHook.tapAsync("3".(name, age, next) = > {
    setTimeout(() = > {
        console.log("3", name, age, new Date());
        next();
        console.timeEnd("time");
    }, 3000);
});

// Trigger the event to make the listener function execute
asyncSeriesHook.callAsync("hannie".18.() = > {
    console.log("complete");
});

> 
1 hannie 18 2020-08-03T06:32:51.094Z
2 hannie 18 2020-08-03T06:32:53.103Z
3 hannie 18 2020-08-03T06:32:56.106Z
complete
time: 6015.353ms
Copy the code

AsyncSeriesHook (Promise) : serial asynchronous execution

/** ** serial asynchronous execution */

class AsyncSeriesHook{
	constructor(instanseArgs){
		this.instanseArgs = instanseArgs;
		this.tasks = [];
	}
    // taskName is the name of the plugin, just for comment purposes
    tapPromise(taskName,task){
		this.tasks.push(task)
	}
	promise(. args){
		// The parameters passed in are exactly the same as those specified in the array passed in to create the instance
		args = args.slice(0.this.instanseArgs.length); //["name", "age"]
        let [first, ...others] = this.tasks;
        return others.reduce((promise, task) = > {    
        return promise.then(() = >task(... args)); }, first(... args)); }}// Create an instance
let asyncSeriesHook = new AsyncSeriesHook(["name"."age"]);

// Register events
console.time("time");
asyncSeriesHook.tapPromise("1".(name, age) = > {
    console.log( name, age);
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncSeriesHook.tapPromise("2".(name, age) = > {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncSeriesHook.tapPromise("3".(name, age) = > {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// Trigger the event to make the listener function execute
asyncSeriesHook.promise("hannie".18).then(ret= > {
    console.log(ret);
});

> 
1 hannie 18 2020-08-03T06:42:05.558Z
2 hannie 18 2020-08-03T06:42:07.565Z
3 hannie 18 2020-08-03T06:42:10.567Z
time: 6016.385ms
3
Copy the code

www.jianshu.com/p/273e1c990…