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…