preface
The official Webpack documentation describes the Plugin API as follows:
Tapable, a small library, is a core tool for WebPack, but can be used elsewhere to provide a similar plug-in interface. Many objects in Webpack extend from the Tapable class. This class exposes the TAP, tapAsync, and tapPromise methods, which you can use to inject custom build steps that will be triggered at different times throughout the compilation process.
The body of the
Tapable exposed hooks can be divided into synchronous and asynchronous types, and these two types can be divided into parallel and serial modes in execution. Details are as follows:
The introduction of each Hook is as follows:
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 listener functions returns a value of noundefined , all remaining logic is skipped |
3 | SyncWaterfallHook | Synchronous serial | The return value of the previous listener can be passed to the next listener |
4 | SyncLoopHook | Synchronous cycle | When fired, the listener is repeated if it returns true, and exits the loop if it returns undefined |
5 | AsyncParallelHook | Asynchronous concurrent | Does not care about the return value of the listening function |
6 | AsyncParallelBailHook | Asynchronous concurrent | As long as the return value of the listener function is notundefined , ignores subsequent listener function execution, jumps directly to callAsync and other callback functions that trigger the function binding, and then executes the bound callback function |
7 | AsyncSeriesHook | Asynchronous serial port | Does not relate to arguments to callback() |
8 | AsyncSeriesBailHook | Asynchronous serial port | The argument to callback() is notundefined Callback functions 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 |
example
class Car {
constructor() { this.hooks = { accelerate: new SyncHook(["newSpeed"])}; }// Declare the hook
// The hook call
setSpeed(newSpeed) { this.hooks.accelerate.call(newSpeed); }}// Declare an instance
const myCar = new Car();
// Register event 1
myCar.hooks.accelerate.tap("LoggerPlugin".newSpeed= > console.log(`[LoggerPlugin]Accelrating to ${newSpeed}`));
// Register event 2
myCar.hooks.accelerate.tap(
{ name: "LoggerPlugin2".before: "LoggerPlugin" },
newSpeed= > console.log(`[LoggerPlugin1]Accelrating to ${newSpeed}`));// Register event 3
myCar.hooks.accelerate.tap({
name: "LoggerPlugin3".fn: newSpeed= > console.log(`[LoggerPlugin3]Accelrating to ${newSpeed}`)}); myCar.hooks.accelerate.intercept({call: newSpeed= > console.log(`The newSpeed is: ${newSpeed}`),
tap: tapInfo= > console.log(`${tapInfo.name}tap was trigged! `),
register: tapInfo= > {
console.log(`${tapInfo.name} is doing its jos`.JSON.stringify(tapInfo));
returntapInfo; }}); myCar.setSpeed(60);
Copy the code
The result is as follows:
LoggerPlugin2 is doing its jos {"type":"sync"."name":"LoggerPlugin2"."before":"LoggerPlugin"}
LoggerPlugin is doing its jos {"type":"sync"."name":"LoggerPlugin"}
LoggerPlugin3 is doing its jos {"type":"sync"."name":"LoggerPlugin3"}
The newSpeed is: 60
LoggerPlugin2 tap was trigged!
[LoggerPlugin1]Accelrating to 60
LoggerPlugin tap was trigged!
[LoggerPlugin]Accelrating to 60
LoggerPlugin3 tap was trigged!
[LoggerPlugin3]Accelrating to 60
Copy the code
Specifically, the use of Tapable Hook can be roughly divided into three steps.
- Var sync = new SyncHook([‘a’, ‘b’])
- Register several events sync.tap(‘name’, fn)
- Call the event registered in step 2 through sync.call(a, b) (_createCall generates call execution logic only when a call is called)
The generated Call method is shown at the end of this article.
The source code interpretation
The core code is distributed in two files: Hook. Js and HookcodeFactory.js. The former mainly implements the logic for registering events to provide a base class from which other hooks can inherit, while the latter provides the logic for generating the call, callAsync, and callPromise method bodies to generate the logic for performing the registration method create.
We use the SyncHook used in the above example to read the source code. Other Hook implementations are pretty much the same.
SyncHook
The core code is as follows
class SyncHookCodeFactory extends HookCodeFactory {
// Use it in HookCodeFactory contentWithInterceptors to generate the execution logic
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) = >onError(err), onDone, rethrowIfPossible }); }}const factory = new SyncHookCodeFactory();
// Generate the method body that executes the logic
const COMPILE = function(options) {
Setup, see HookCodeFactory for the create method
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
// Use Hook to initialize a base Hook
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
Copy the code
A few key points:
new Hook()
Used to initialize an underlying Hook instanceCOMPILE
Encapsulate the generationcall
Performs the logic of the code in the callcall
Will be called (see belowCALL_DELEGATE
)content
Method is used during executionCOMPILE
Some of the methodscustomLogic.
Hook.js
Core code:
const CALL_DELEGATE = function(. args) {
this.call = this._createCall("sync");
return this.call(... args); };class Hook {
constructor(args = [], name = undefined) {
// omit code...
this.taps = [];
this.call = CALL_DELEGATE;
}
Compile logic is not implemented here. Instead, compile is assigned in each Hook class to generate the execution code in HookCodeFactory
// Refer to COMPILE in AsyncHook above
compile(options) {
throw new Error("Abstract: should be overridden");
}
_createCall(type) {
return this.compile({ // omit code...
});
}
_tap(type, options, fn) {
// omit code...
options = Object.assign({ type, fn }, options);
Register in the _runRegisterInterceptors interceptor can customize options and return the new configuration
options = this._runRegisterInterceptors(options);
this._insert(options);
}
// Register events
tap(options, fn) {
this._tap("sync", options, fn);
}
// Add a new declaration method to the TAPS event queue and adjust the execution order of the registration methods,
_insert(item) {
this._resetCompilation();
let before;
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue; }}if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item; }}Copy the code
Key points:
tap
Method for registering normal events (tapAsync, tapPromise for registering asynchronous events)- Core method
_tap
Will be called_insert
Add events to the registered event queue and adjust the order in which they are executed (if passed in while registering the event)stage
.before
Parameter).
In _insert, it is difficult to adjust the execution order of events, you can see the comments added in the author’s reading
HookCodeFactory.js
The core is the create method. The specific logic is to concatenate string through new Function and generate the method body of the pass parameter and call method. You can learn the code to download to the local, do it yourself.
myCar.hooks.accelerate.call
newSpeed => {
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(newSpeed);
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
var _fn0 = _x[0];
_fn0(newSpeed);
var _tap1 = _taps[1];
_interceptors[0].tap(_tap1);
var _fn1 = _x[1];
_fn1(newSpeed);
var _tap2 = _taps[2];
_interceptors[0].tap(_tap2);
var _fn2 = _x[2];
_fn2(newSpeed);
};
Copy the code
Want to say
Although the core code will be registered and implemented to do a separation, to achieve the decoupling of each Hook, but increased the difficulty of understanding, in addition to the specific implementation of create method feel there is room for optimization, the current splicing way undoubtedly increases the cost of memory, except the big man…
Improper place, also hope to point out!!
- Webpack Chinese document
- Tapable Github
- Webpack4.0 source code analysis Tapable
- Here’s what you need to know about Tapable