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

Attached with the Webpack build process