Introduction to the

When looking at the source code of L7, we saw that we used the core module tapable of Webpack, which is the key link to connect each plugin of Webpack. Simply speaking, if you do not understand Tapable, you must not understand the source code of Webpack, and the call mechanism of each plugin will not be clear.

The overview

Tapable is simply known as EventEmitter in Webpack, which is used to publish and subscribe messages. It exposes a number of hooks that are provided for custom plugin mounts:Tapable is divided into synchronous and asynchronous hooks. It should be noted that asynchronous hooks are divided into parallel and serial, while synchronous hooks are naturally serial. Let’s take a look at the registration and trigger methods:

Synchronous Hook Asynchronous Hook
registered tap TapAsync, tapPromise, tap
The trigger call CallAsync, promise

It is best to initialize a Hook with new in the Class constructor: it takes an array of arguments, and the triggering method takes the same number of arguments, depending on the arguments passed.

class Kunkun{
  constructor() {
    this.hooks = {
      sing: new SyncHook(["song"]),
      dance: new SyncWaterfallHook(['danceName']),
      rap: new SyncBailHook(),
      basketball: AsyncSeriesHook(["shot"]),}; }}const kunkun = new Kunkun();

// Register method
/ / a parameter
kunkun.hooks.sing.tap("GoSing".song= > {console.log('Sang a song${song}`)});
/ / no parameters
kunkun.hooks.dance.tap("GoDance".danceName= > {console.log('Jumped one${danceName}`)});
kunkun.hooks.rap.tap("GoRap".() = > {console.log('Here comes a Rap')});
// Register asynchronous functions
kunkun.hooks.basketball.tapPromise("GoBasketball".(shot, callback) = > {
  return new Promise(resolve= > {
    setTimeout(() = > {
      resolve('Voted for one${shot}Ball `);
    },1000)})});// Execute synchronization Hook
kunkun.hooks.sing.call('You're so beautiful, chicken.');
kunkun.hooks.dance.call(waltz);
kunkun.hooks.rap.call();
// Execute asynchronous Hook
myCar.hooks.basketball.promise(2).then((res) = > {
	console.log(res)
})

Copy the code

Hook detailed scenario

SyncHook

After an event is emitted, all event handlers are executed in the order in which the event was registered.

SyncBailHook

If an event handler executes without a null return value, the remaining unexecuted event handlers are skipped, as in the above example:

kunkun.hooks.rap.tap("GoRap1".() = > {console.log('Here comes a Rap1')});
kunkun.hooks.rap.tap("GoRap2".() = > {
  console.log('Here comes a Rap2');
  return 0;
});
kunkun.hooks.rap.tap("GoRap3".() = > {console.log('Here comes Rap3')});

kunkun.hooks.rap.call();

// Here comes Rap1
// Here comes Rap2
Copy the code

SyncWaterfallHook

The return value of the previous event handler is passed as an argument to the next event handler. Only the arguments in the first binding function are the actual arguments when fired, and all the arguments in the other binding methods are the results of the previous one:

kunkun.hooks.dance.tap("Dance1".danceName= > {
  return 'Jumped one${danceName}1 `
});
kunkun.hooks.dance.tap("Dance2".(data) = > {
	return 'Jumped one${danceName}2 `
});
kunkun.hooks.dance.tap("Dance3".(data) = > {
  return 'Jumped one${danceName}3 `
});
kunkun.hooks.dance.call(waltz);

// Danced a waltz 123
Copy the code

SyncLoopHook

Event handlers return true to indicate that the loop executes the current event handler, and undefined to indicate the end of the loop. Unlike SyncBailHook, SyncBailHook only decides whether to proceed with subsequent event handlers. SyncLoopHook loops through each event handler until it returns undefined, and then moves on to other event handlers.

let syncLoopHook = new SyncLoopHook(["name"."age"]);
let temp = 0;
// Register events
syncLoopHook.tap("1".name= > {
    console.log("1", name, temp);
    return temp++ < 2 ? true : undefined;
});
syncLoopHook.tap("2".name= > {
    console.log("2", name, temp);
    return temp++ < 5 ? true : undefined;
});
syncLoopHook.tap("3".name= > console.log("3", name));

syncLoopHook.call("maosong");

// 1 maosong 0
// 1 maosong 1
// 1 maosong 2
// 2 maosong 3
// 2 maosong 4
// 2 maosong 5
// 3 maosong
Copy the code

AsyncParallelHook

All registered methods are synchronized in parallel, as promise.all ()

AsyncSeriesHook

All registered methods are executed serially, that is, after one method is executed, the next method is executed after the result is obtained

AsyncParallelBailHook

Same idea as SyncBailHook, but with asynchrony

AsyncSeriesBailHook

Same idea as SyncBailHook, but with asynchrony

AsyncSeriesWaterfallHook

AsyncSeriesHook + SyncWaterfallHook details to view both hooks

The difference between asynchronous registration and trigger

tapAsync / callAsync

In AsyncParallelHook, the done() method is provided at registration time to indicate the end of asynchron, and the callback can be passed in when triggered to trigger a callback after all the registered methods have finished running

const { AsyncParallelHook } = require("tapable");
let asyncParallelHook = new AsyncParallelHook(["name"]);
asyncParallelHook.tapAsync("1".(name, done) = > {
  settimeout(() = > {
    console.log("1", name);
    done();
  }, 1000);
});
asyncParallelHook.tapAsync("2".(name, done) = > {
  settimeout(() = > {
    console.log("2", name);
    done();
  }, 1000);
});
// Trigger the event to make the listener function execute
asyncParallelHook.callAsync("maosong".() = > {
    console.log("over");
});

// 1 maosong
// 2 maosong
// over

Copy the code

Provide the next() method in AsyncSeriesHook to access the next registered monitor method, using the same method as above.

The difference between done() and next()?

The done method only performs callAsync callbacks to check if the condition is met. If one of the event handlers does not call done, callAsync callbacks will not be called, but all event handlers will execute.

The next execution mechanism is more like next() in the Generator. If next is not called in the callback of the registered event, it will be “stuck” in the position of the event handler that did not call Next when the event is triggered. In other words, the execution of subsequent event handlers will not continue until next is called. Next is called in the last event handler to determine whether callAsync’s callback is called.

tapPromise /promise

For a concrete example, look at the original Kunkun example, which returns a Promise instance