Recently I suddenly thought of a problem, in webpack we add plugin in the form of array, so how to ensure the execution order of these plug-ins, such as a plug-in dependency needs to wait for another plug-in to change, such as a plug-in and another plug-in can be parallel, so from the source to explore the results.
What is the plugins
Plug-ins, as the name suggests, are described in the official WebPack documentation
Plugins are the backbone of webpack. webpack itself is built on the same plugin system that you use in your webpack configuration! They also serve the purpose of doing anything else that a loader cannot do.
Plug-ins are the backbone of WebPack, and WebPack itself is actually built based on the same plugin system provided for your configuration, and they can be used to do things that Loader can’t.
Such as file optimization, resource management, variable injection and so on.
So how does WebPack implement plug-ins?
This is where the event mechanism comes in
What is the event mechanism
Take a chestnut
When you know that a newsstand is bringing in a magazine you really want to read this month, but neither you nor your boss knows when it will actually be coming, you have two options:
- Run to the newsstand every day
- Leave a phone number for your boss to call you when it arrives
The first is polling, which is a waste of your time
The second is the event mechanism
Here’s a code to describe it
class EventEmiter {
constructor() {
this.taps = []; // Who needs to be notified when the goods arrive
}
tap(name, callback) {
this.taps.push({
name, // The subscription name
callback // What that person is going to do})}call() {
this.taps.forEach(tap= > {
console.log('notice'+ tap.name); tap.callback && tap.callback(); }); }}const bossNotify = new EventEmiter();
bossNotify.tap('Joe' , () = > {
setTimeout(() = > {
console.log('Buy it in 1 second'); // Not so fast actually
}, 1000);
});
bossNotify.tap('bill'.() = > {
setTimeout(() = > {
console.log('Buy it in 2 seconds'); // Not so fast actually
}, 2000);
});
/*** One day, the magazine arrived */
bossNotify.call(); // Comrades, the magazines you asked for have arrived
Copy the code
This allows us to implement a simple event mechanism, but it doesn’t seem to meet our plug-in execution requirements. In the WebPack build process, some plug-ins need to be synchronous, some need to be asynchronously serial, and some need to be asynchronously parallel. In fact, you can see from the source that WebPack uses the Tapable library.
tapable
Tapable’s official documentation shows that it mainly provides the following hooks
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
These hooks can be divided into synchronous and asynchronous hooks, and asynchronous hooks can be divided into serial and concurrent hooks
Take a look at the meanings of these nouns in a chart
The name of the | Hook in | role |
---|---|---|
Hook | tap .tapAsync .tapPromise |
Hook the base class |
SyncHook | tap |
Synchronous hooks |
SyncBailHook | tap |
Synchronized hooks are not executed as long as the executing handler returns a value |
SyncLoopHook | tap |
Synchronous hooks that loop through the handler as long as it returns a value |
SyncWaterfallHook | tap |
Synchronization hook, the return value of the previous handler is used as the input value of the next handler |
AsyncParallelBailHook | tap .tapAsync .tapPromise |
An asynchronous hook that the handler fires in parallel, but has to do with the logic inside the handler to call the callback function |
AsyncParallelHook | tap .tapAsync .tapPromise |
Asynchronous hooks that handlers fire in parallel |
AsyncSeriesBailHook | tap .tapAsync .tapPromise |
An asynchronous hook is triggered serially by the handler, but has to do with the logic inside the handler to call the callback function |
AsyncSeriesHook | tap .tapAsync .tapPromise |
Asynchronous hook, handler serial trigger |
AsyncSeriesLoopHook | tap .tapAsync .tapPromise |
Asynchronous hooks that trigger handler loop calls |
AsyncSeriesWaterfallHook | tap .tapAsync .tapPromise |
An asynchronous hook that allows the previous handler to pass values to the next handler based on an internal callback function |
In addition to these hooks, you can see the following files in the source code
The name of the | role |
---|---|
HookCodeFactory | Compile the factory class that generates the executable FN |
HookMap | Map structure, store multiple Hook instances |
MultiHook | Combine multiple Hook instances |
Tapable | Forward compatible with older versions, instances must have hooks attributes |
hook
Virtually all hooks inherit from this base class
Let’s take a look at some of its important methods
constructor(args) { // constructor
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
this._x = undefined;
}
tap(options, fn){}tapAsync(options, fn){}tapPromise(options, fn){}Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call"."sync"),
configurable: true.writable: true
},
_promise: {
value: createCompileDelegate("promise"."promise"),
configurable: true.writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync"."async"),
configurable: true.writable: true}});Copy the code
Regardless of the implementation, these methods are the core of hook. Tap registers the handler and call triggers events to notify the handler. The following subclasses will analyze the specific operation.
SyncHook
The synchronous hook is the simplest hook, as shown below
const {
SyncHook
} = require('tapable');
const hook = new SyncHook(['arg1'.'arg2'.'arg3']);
hook.tap('hook1'.(arg1, arg2, arg3) = > {
console.log(arg1, arg2, arg3);
}); // Register events
hook.call(1.2.3); / / call
/ / 123
Copy the code
It looks like the chestnut who bought the magazine at the newsstand.
To see how the source code is implemented?
// SyncHook.js
class SyncHook extends Hook {
tapAsync() {
throw new Error("tapAsync is not supported on a SyncHook");
}
tapPromise() {
throw new Error("tapPromise is not supported on a SyncHook");
}
compile(options) {
factory.setup(this, options);
return factory.create(options); // Return the object created by the factory by passing arguments to the instance of the factory}}module.exports = SyncHook;
Copy the code
SyncHook does not overload the constructor, nor does it have a tap method. It uses hook methods. Now you can look at the hook class again
constructor(args) { // constructor
if (!Array.isArray(args)) args = []; // The argument passed in
this._args = args;
this.taps = []; // Saves an array of listening events
this.call = this._call;
}
tap(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeofoptions ! = ="object" || options === null)
throw new Error(
"Invalid arguments to tap(options: Object, fn: function)"
);
options = Object.assign({ type: "sync".fn: fn }, options); // A {name: 'XXX ', fn: callback} object is generated
if (typeofoptions.name ! = ="string" || options.name === "")
throw new Error("Missing name for tap");
options = this._runRegisterInterceptors(options); // This hook will not be used for the time being
this._insert(options); // Insert arguments
}
Copy the code
As you can see here, the first argument to tap() can be an object or a string, if the string is just the name of the processor, if the object has the following properties
interface Tap {
name: string, // The handler name
type: string, // The type of handler
before: string | array, // Insert before the specified handler
fn: Function.// Handler executes the function
stage: number, // The execution order of the handler
context: boolean // Internally shared objects
}
Copy the code
Before and stage affect the order in which the handler is executed. Before means that the handler is placed before the specified handler, and stage is placed before the specified handler. These two attributes affect the _insert function called in tap
_insert(item) {
this._resetCompilation(); // The call function is reassigned to prevent tampering
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
If there is a before or xstage, it will be handled when the tap is inserted. Here, the handlers are successfully registered in this. Taps, completing the event listening. Then when the corresponding Hook is executed, that is, when the call method is called, we go back to the Hook and look,
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call"."sync"),
configurable: true.writable: true
},
_promise: {
value: createCompileDelegate("promise"."promise"),
configurable: true.writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync"."async"),
configurable: true.writable: true
}
Copy the code
As you can see, the function returned by createCompileDelegate(“call”, “sync”) is invoked
function createCompileDelegate(name, type) {
return function lazyCompileHook(. args) {
this[name] = this._createCall(type);
return this[name](... args);// In fact, it is equivalent to
// this.call = this._creteCall(type)
// return this.call(... args)
};
}
Copy the code
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
Copy the code
_createCall will actually call this.pile, which is implemented by subclass SyncHook, going back to the SyncHook method you just saw
compile(options) {
factory.setup(this, options);
return factory.create(options); // Return the object created by the factory by passing arguments to the instance of the factory
}
Copy the code
Taps, parameters, and type (‘sync’) are passed in from the taps module
fn = new Function(
this.args(),
'"use strict"; \n' +
this.header() +
this.content({
onError: err= > `throw ${err}; \n`.onResult: result= > `return ${result}; \n`.resultReturns: true.onDone: () = > "".rethrowIfPossible: true}));Copy the code
The content here is actually in SyncHook
header() {
let code = "";
if (this.needContext()) {
code += "var _context = {}; \n";
} else {
code += "var _context; \n";
}
code += "var _x = this._x; \n";
if (this.options.interceptors.length > 0) {
code += "var _taps = this.taps; \n";
code += "var _interceptors = this.interceptors; \n";
}
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
code += `The ${this.getInterceptor(i)}.call(The ${this.args({
before: interceptor.context ? "_context" : undefined
})}); \n`; }}return code;
}
Copy the code
In the create function, a new function is generated by concatenating the corresponding string of passing arguments, something like this
function anonymous(arg1, arg2, arg3
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(arg1, arg2, arg3);
}
Copy the code
/*** corresponds to our initial hook. Tap ('hook1', (arg1, arg2, arg3) => {console.log(arg1, arg2, arg3); }); * * /
Copy the code
setup(instance, options) {
instance._x = options.taps.map(t= > t.fn);
}
Copy the code
This. _x is an array that holds the TAPS method we registered. The core of the code above is to walk through the TAPS method we registered and execute it.
What is the relationship between the classes
And that’s exactly what the other hooks do, except tap might become tapAsync, and tapPromise Call would program callAsync, callPromise
What kind of running functions do the other hooks end up generating
The rest of the Hook
SyncBailHook
function anonymous(/ * ` ` * /) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0();
if(_result0 ! = =undefined) { // if undefined returns directly, if not, go to the next function
return _result0;
} else {
var _fn1 = _x[1];
var _result1 = _fn1();
if(_result1 ! = =undefined) {
return _result1;
} else{}}}Copy the code
SyncWaterfallHook
function anonymous(arg1) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(arg1);
if(_result0 ! = =undefined) {
arg1 = _result0; // Save the result here for the next function
}
var _fn1 = _x[1];
var _result1 = _fn1(arg1);
if(_result1 ! = =undefined) {
arg1 = _result1;
}
return arg1;
}
Copy the code
AsyncSeriesHook
function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(_err0= > {
if (_err0) {
_callback(_err0);
} else {
var _fn1 = _x[1];
_fn1(_err1= > {
if (_err1) {
_callback(_err1);
} else {
_callback(); // execute serially, finally returning _callback}}); }}); }Copy the code
AsyncParallelHook
function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x;
do {
var _counter = 2; // The number of registered events
var _done = () = > {
_callback();
};
if (_counter <= 0) break;
var _fn0 = _x[0];
_fn0(_err0= > {
// This function is the next function
// The time when this function will be called is uncertain. It is possible that the next few registration functions will have been executed
if (_err0) {
// If all registration functions have not been executed, terminate
if (_counter > 0) {
_callback(_err0);
_counter = 0; }}else {
// Check the value of _counter, if 0, end
// Check whether the function has been completed because the actual call time is not determined.
if (--_counter === 0) { _done() }; }});Before performing the next registration callback, check to see if _counter has been reset, etc. If the reset indicates that something returns to err, terminate.
if (_counter <= 0) break;
var _fn1 = _x[1];
_fn1(_err1= > {
if (_err1) {
if (_counter > 0) {
_callback(_err1);
_counter = 0; }}else {
if (--_counter === 0) _done(); }}); }while (false);
}
Copy the code
Here events are executed concurrently
Webpack event flow
So how do you apply Tapable in WebPack?
compiler
Compiler object is the compiler object of Webpack, which is generated when Webpack is initialized for the first time and receives the corresponding parameters synthesized by the user’s custom configuration. We can get the main environment information of Webpack through compiler object
compilation
Inherited from compiler, the Compilation object represents a single build and build resource. When running the WebPack development environment middleware, each time a file change is detected, a new compilation is created, resulting in a new set of compilation resources. A compiled object represents the current module resources, compile-generated resources, changing files, and state information that is being traced. The compiled object also provides a number of keypoint callbacks that plug-ins can choose to use when doing custom processing.
hooks
In fact, both inherit from Tapable
/ / the compiler hooks
this.hooks = {
/ * *@type {SyncBailHook<Compilation>} * /
shouldEmit: new SyncBailHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Stats>} * /
done: new AsyncSeriesHook(["stats"]),
/ * *@type {AsyncSeriesHook<>} * /
additionalPass: new AsyncSeriesHook([]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
beforeRun: new AsyncSeriesHook(["compiler"]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
run: new AsyncSeriesHook(["compiler"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
emit: new AsyncSeriesHook(["compilation"]),
/ * *@type {AsyncSeriesHook<string, Buffer>} * /
assetEmitted: new AsyncSeriesHook(["file"."content"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
afterEmit: new AsyncSeriesHook(["compilation"]),
/ * *@type {SyncHook<Compilation, CompilationParams>} * /
thisCompilation: new SyncHook(["compilation"."params"]),
/ * *@type {SyncHook<Compilation, CompilationParams>} * /
compilation: new SyncHook(["compilation"."params"]),
/ * *@type {SyncHook<NormalModuleFactory>} * /
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/ * *@type {SyncHook<ContextModuleFactory>} * /
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/ * *@type {AsyncSeriesHook<CompilationParams>} * /
beforeCompile: new AsyncSeriesHook(["params"]),
/ * *@type {SyncHook<CompilationParams>} * /
compile: new SyncHook(["params"]),
/ * *@type {AsyncParallelHook<Compilation>} * /
make: new AsyncParallelHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Compilation>} * /
afterCompile: new AsyncSeriesHook(["compilation"]),
/ * *@type {AsyncSeriesHook<Compiler>} * /
watchRun: new AsyncSeriesHook(["compiler"]),
/ * *@type {SyncHook<Error>} * /
failed: new SyncHook(["error"]),
/ * *@type {SyncHook<string, string>} * /
invalid: new SyncHook(["filename"."changeTime"]),
/ * *@type {SyncHook} * /
watchClose: new SyncHook([]),
/ * *@type {SyncBailHook<string, string, any[]>} * /
infrastructureLog: new SyncBailHook(["origin"."type"."args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/ * *@type {SyncHook} * /
environment: new SyncHook([]),
/ * *@type {SyncHook} * /
afterEnvironment: new SyncHook([]),
/ * *@type {SyncHook<Compiler>} * /
afterPlugins: new SyncHook(["compiler"]),
/ * *@type {SyncHook<Compiler>} * /
afterResolvers: new SyncHook(["compiler"]),
/ * *@type {SyncBailHook<string, Entry>} * /
entryOption: new SyncBailHook(["context"."entry"])};Copy the code
/ / compilation of hooks
this.hooks = {
/ * *@type {SyncHook<Module>} * /
buildModule: new SyncHook(["module"]),
/ * *@type {SyncHook<Module>} * /
rebuildModule: new SyncHook(["module"]),
/ * *@type {SyncHook<Module, Error>} * /
failedModule: new SyncHook(["module"."error"]),
/ * *@type {SyncHook<Module>} * /
succeedModule: new SyncHook(["module"]),
/ * *@type {SyncHook<Dependency, string>} * /
addEntry: new SyncHook(["entry"."name"]),
/ * *@type {SyncHook<Dependency, string, Error>} * /
failedEntry: new SyncHook(["entry"."name"."error"]),
/ * *@type {SyncHook<Dependency, string, Module>} * /
succeedEntry: new SyncHook(["entry"."name"."module"]),
/ * *@type {SyncWaterfallHook<DependencyReference, Dependency, Module>} * /
dependencyReference: new SyncWaterfallHook([
"dependencyReference"."dependency"."module"
]),
/ * *@type {AsyncSeriesHook<Module[]>} * /
finishModules: new AsyncSeriesHook(["modules"]),
/ * *@type {SyncHook<Module>} * /
finishRebuildingModule: new SyncHook(["module"]),
/ * *@type {SyncHook} * /
unseal: new SyncHook([]),
/ * *@type {SyncHook} * /
seal: new SyncHook([]),
/ * *@type {SyncHook} * /
beforeChunks: new SyncHook([]),
/ * *@type {SyncHook<Chunk[]>} * /
afterChunks: new SyncHook(["chunks"]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeDependenciesBasic: new SyncBailHook(["modules"]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeDependencies: new SyncBailHook(["modules"]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
/ * *@type {SyncBailHook<Module[]>} * /
afterOptimizeDependencies: new SyncHook(["modules"]),
/ * *@type {SyncHook} * /
optimize: new SyncHook([]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeModulesBasic: new SyncBailHook(["modules"]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeModules: new SyncBailHook(["modules"]),
/ * *@type {SyncBailHook<Module[]>} * /
optimizeModulesAdvanced: new SyncBailHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
afterOptimizeModules: new SyncHook(["modules"]),
/ * *@type {SyncBailHook<Chunk[], ChunkGroup[]>} * /
optimizeChunksBasic: new SyncBailHook(["chunks"."chunkGroups"]),
/ * *@type {SyncBailHook<Chunk[], ChunkGroup[]>} * /
optimizeChunks: new SyncBailHook(["chunks"."chunkGroups"]),
/ * *@type {SyncBailHook<Chunk[], ChunkGroup[]>} * /
optimizeChunksAdvanced: new SyncBailHook(["chunks"."chunkGroups"]),
/ * *@type {SyncHook<Chunk[], ChunkGroup[]>} * /
afterOptimizeChunks: new SyncHook(["chunks"."chunkGroups"]),
/ * *@type {AsyncSeriesHook<Chunk[], Module[]>} * /
optimizeTree: new AsyncSeriesHook(["chunks"."modules"]),
/ * *@type {SyncHook<Chunk[], Module[]>} * /
afterOptimizeTree: new SyncHook(["chunks"."modules"]),
/ * *@type {SyncBailHook<Chunk[], Module[]>} * /
optimizeChunkModulesBasic: new SyncBailHook(["chunks"."modules"]),
/ * *@type {SyncBailHook<Chunk[], Module[]>} * /
optimizeChunkModules: new SyncBailHook(["chunks"."modules"]),
/ * *@type {SyncBailHook<Chunk[], Module[]>} * /
optimizeChunkModulesAdvanced: new SyncBailHook(["chunks"."modules"]),
/ * *@type {SyncHook<Chunk[], Module[]>} * /
afterOptimizeChunkModules: new SyncHook(["chunks"."modules"]),
/ * *@type {SyncBailHook} * /
shouldRecord: new SyncBailHook([]),
/ * *@type {SyncHook<Module[], any>} * /
reviveModules: new SyncHook(["modules"."records"]),
/ * *@type {SyncHook<Module[]>} * /
optimizeModuleOrder: new SyncHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
advancedOptimizeModuleOrder: new SyncHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
beforeModuleIds: new SyncHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
moduleIds: new SyncHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
optimizeModuleIds: new SyncHook(["modules"]),
/ * *@type {SyncHook<Module[]>} * /
afterOptimizeModuleIds: new SyncHook(["modules"]),
/ * *@type {SyncHook<Chunk[], any>} * /
reviveChunks: new SyncHook(["chunks"."records"]),
/ * *@type {SyncHook<Chunk[]>} * /
optimizeChunkOrder: new SyncHook(["chunks"]),
/ * *@type {SyncHook<Chunk[]>} * /
beforeChunkIds: new SyncHook(["chunks"]),
/ * *@type {SyncHook<Chunk[]>} * /
optimizeChunkIds: new SyncHook(["chunks"]),
/ * *@type {SyncHook<Chunk[]>} * /
afterOptimizeChunkIds: new SyncHook(["chunks"]),
/ * *@type {SyncHook<Module[], any>} * /
recordModules: new SyncHook(["modules"."records"]),
/ * *@type {SyncHook<Chunk[], any>} * /
recordChunks: new SyncHook(["chunks"."records"]),
/ * *@type {SyncHook} * /
beforeHash: new SyncHook([]),
/ * *@type {SyncHook<Chunk>} * /
contentHash: new SyncHook(["chunk"]),
/ * *@type {SyncHook} * /
afterHash: new SyncHook([]),
/ * *@type {SyncHook<any>} * /
recordHash: new SyncHook(["records"]),
/ * *@type {SyncHook<Compilation, any>} * /
record: new SyncHook(["compilation"."records"]),
/ * *@type {SyncHook} * /
beforeModuleAssets: new SyncHook([]),
/ * *@type {SyncBailHook} * /
shouldGenerateChunkAssets: new SyncBailHook([]),
/ * *@type {SyncHook} * /
beforeChunkAssets: new SyncHook([]),
/ * *@type {SyncHook<Chunk[]>} * /
additionalChunkAssets: new SyncHook(["chunks"]),
/ * *@type {AsyncSeriesHook} * /
additionalAssets: new AsyncSeriesHook([]),
/ * *@type {AsyncSeriesHook<Chunk[]>} * /
optimizeChunkAssets: new AsyncSeriesHook(["chunks"]),
/ * *@type {SyncHook<Chunk[]>} * /
afterOptimizeChunkAssets: new SyncHook(["chunks"]),
/ * *@type {AsyncSeriesHook<CompilationAssets>} * /
optimizeAssets: new AsyncSeriesHook(["assets"]),
/ * *@type {SyncHook<CompilationAssets>} * /
afterOptimizeAssets: new SyncHook(["assets"]),
/ * *@type {SyncBailHook} * /
needAdditionalSeal: new SyncBailHook([]),
/ * *@type {AsyncSeriesHook} * /
afterSeal: new AsyncSeriesHook([]),
/ * *@type {SyncHook<Chunk, Hash>} * /
chunkHash: new SyncHook(["chunk"."chunkHash"]),
/ * *@type {SyncHook<Module, string>} * /
moduleAsset: new SyncHook(["module"."filename"]),
/ * *@type {SyncHook<Chunk, string>} * /
chunkAsset: new SyncHook(["chunk"."filename"]),
/ * *@type {SyncWaterfallHook<string, TODO>} * /
assetPath: new SyncWaterfallHook(["filename"."data"]), // TODO MainTemplate
/ * *@type {SyncBailHook} * /
needAdditionalPass: new SyncBailHook([]),
/ * *@type {SyncHook<Compiler, string, number>} * /
childCompiler: new SyncHook([
"childCompiler"."compilerName"."compilerIndex"
]),
/ * *@type {SyncBailHook<string, LogEntry>} * /
log: new SyncBailHook(["origin"."logEntry"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/ * *@type {SyncHook<object, Module>} * /
normalModuleLoader: new SyncHook(["loaderContext"."module"]),
/ * *@type {SyncBailHook<Chunk[]>} * /
optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]),
/ * *@type {SyncBailHook<Chunk[]>} * /
optimizeExtractedChunks: new SyncBailHook(["chunks"]),
/ * *@type {SyncBailHook<Chunk[]>} * /
optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]),
/ * *@type {SyncHook<Chunk[]>} * /
afterOptimizeExtractedChunks: new SyncHook(["chunks"])};Copy the code
Both initialize many hooks of different types in their constructors. During initialization of build parameters, plugins are iterated over to execute the apply method in plugin. In the Apply method, however, listening is usually done, for example
apply(compiler) {
compiler.hooks.compilation.tap(
"DllEntryPlugin".(compilation, { normalModuleFactory }) = > {
const dllModuleFactory = newDllModuleFactory(); compilation.dependencyFactories.set( DllEntryDependency, dllModuleFactory ); compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); }); compiler.hooks.make.tapAsync("DllEntryPlugin".(compilation, callback) = > {
compilation.addEntry(
this.context,
new DllEntryDependency(
this.entries.map((e, idx) = > {
const dep = new SingleEntryDependency(e);
dep.loc = {
name: this.name,
index: idx
};
return dep;
}),
this.name
),
this.name,
callback
);
});
Copy the code
The initialized hooks are called at different stages of the build process, so different hooks trigger different callback mechanisms. This ensures that plugins are executed in order