Webpack is the most popular module packaging tool in modern front-end development, which can be loaded and packaged with a simple configuration. So how does it make it easy to build code by configuring a few plug-ins?

Webpack configuration

const path = require('path');
module.exports = {
  entry: "./app/entry".// string | object | array
  // Webpack entry
  output: {  // Define options for how WebPack outputs
    path: path.resolve(__dirname, "dist"), // string
    // The destination path for all output files
    filename: "[chunkhash].js".// string
    // The entry chunk file naming template
    publicPath: "/assets/".// string
    // The output directory of the build file
    /* Other advanced configurations */
  },
  module: {  // Module related configuration
    rules: [ // Configure the loaders module to parse the rules
      {
        test: /\.jsx? $/.// RegExp | string
        include: [ // As with test, options must be matched
          path.resolve(__dirname, "app")].exclude: [ // Must not match options (precedence over test and include)
          path.resolve(__dirname, "app/demo-files")].loader: "babel-loader".// Module context parsing
        options: { // Loader options
          presets: ["es2015"]}}},resolve: { // Parse module options
    modules: [ // The search directory for the module
      "node_modules",
      path.resolve(__dirname, "app")].extensions: [".js".".json".".jsx".".css"].// The extension of the file used
    alias: { // List of module aliases
      "module": "new-module"}},devtool: "source-map".// enum
  // Add metadata to browser developer tools to enhance debugging
  plugins: [ // Add-on plugin list
    // ...],}Copy the code

As we can see from the above, there are several core concepts that need to be understood in webPack configuration: Entry, Output, Loaders, Plugins, and Chunk

  • Entry: Specifies the Entry module from which WebPack starts building and calculates which modules or libraries it directly or indirectly depends on
  • Output: Tell WebPack how to name the Output file and the Output directory
  • Loaders: Since WebPack can only handle javascript, we need to process some non-JS files into webPack capable modules, such as sass files
  • Plugins:LoadersProcessing files of various types into modules that WebPack can handle,pluginsHas a very strong ability. Plug-ins can range from packaging optimizations and compression to redefining variables in the environment. But it’s also the most complicated one. For example, the JS file compression optimizationUglifyJsPluginThe plug-in
  • Chunk: A product of coding split, we can package some code into a single Chunk, such as some common modules, to make better use of the cache. Or load some function modules on demand to optimize the loading time. In webpack3 and before, we usedCommonsChunkPluginSome common code is divided into a chunk, to achieve a separate load. In webpack4CommonsChunkPluginAbandoned, usedSplitChunksPlugin

Webpack,

If you have read this, you may have a general understanding of WebPack. How does Webpack work? As we all know, WebPack is a highly complex and abstract collection of plug-ins, and understanding how WebPack works is a great help in locating build errors and writing plug-ins to handle build tasks on a daily basis.

Have to say tapable

Webpack is essentially a mechanism of event flow, and its workflow is to connect various plug-ins in series. The core of implementing all these is Tapable. The core Compiler responsible for Compilation and the Compilation responsible for creating bundles in WebPack are both instances of Tapable. Prior to Tapable1.0, which was used by WebPack3 and before, Tapable provided

  • plugin(name:string, handler:function)Register the plug-in into the Tapable object
  • The apply (... pluginInstances: (AnyPlugin|function)[])Invoke the plug-in definition to register event listeners in the Tapable instance registry
  • ApplyPlugins * (name: string,...).Multiple strategies carefully control the triggering of events, includingapplyPluginsAsync,applyPluginsParallelAnd other methods to achieve the control of event triggering, to achieve

(1) continuous order multiple events (2) parallel execution executed asynchronously (3) (4) one by one to implement the plug-in, after the previous output is a plug-in input flow of the waterfall suspended execution sequence (5) allows the plug-in, namely a plug-in return an undefined value, namely the exits We can see, Tapable, like EventEmitter in NodeJS, provides the ability to register events on and emit emit. It’s important to understand this: let’s write a plug-in

function CustomPlugin() {}
CustomPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', pluginFunction);
}
Copy the code

This will be implemented in time during the WebPack lifecycle

this.apply*("emit",options)
Copy the code

Of course, all the Tapable mentioned above are pre-1.0. If you want to learn more about Tapable and event flow, you can see what Tapable 1.0 is like. In version 1.0, there is a big change. Instead of registering events with plugins, event calls are triggered by applyPlugins*. What is Tapable in 1.0?

This exposes a number of hooks that you can use to create hook functions for your plug-in

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");
Copy the code

Let’s see how it works.

class Order {
    constructor() {
        this.hooks = { //hooks
            goods: new SyncHook(['goodsId'.'number']),
            consumer: new AsyncParallelHook(['userId'.'orderId'])
        }
    }

    queryGoods(goodsId, number) {
        this.hooks.goods.call(goodsId, number);
    }

    consumerInfoPromise(userId, orderId) {
        this.hooks.consumer.promise(userId, orderId).then((a)= > {
            //TODO
        })
    }

    consumerInfoAsync(userId, orderId) {
        this.hooks.consumer.callAsync(userId, orderId, (err, data) => {
            //TODO}}})Copy the code

The constructor for all hooks accepts an optional string array

const hook = new SyncHook(["arg1"."arg2"."arg3"]);
Copy the code
Call the tap method to register a consument
order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {
    return fetchGoods(goodsId, number);
})
// Add one more
order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {
    logger(goodsId, number);
})

/ / call
order.queryGoods('10000000'.1)
Copy the code

For a SyncHook, we add the consumer with a tap, and trigger the hook execution sequence with a call.

For a non-Sync * hook, that is, an Async * hook, there are other ways to register consumers and calls

// Register a Sync hook
order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {
   logger(userId, orderId);
})

order.hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {
    LoginCheck(userId, callback);
})

order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {
    return Promise.resolve();
})

/ / call
/ / return Promise
order.consumerInfoPromise('user007'.'1024');

// The callback function
order.consumerInfoAsync('user007'.'1024')
Copy the code

From the chestnuts above, you probably have an idea of how Tapable works, how it works

  • Number of plug-in registrations
  • Type of plug-in registration (Sync, Async, Promise)
  • Method of invocation (sync, async, Promise)
  • Number of arguments for instance hooks
  • Is it usedinterception

Tapable,

Sync*

  • Plug-ins registered under this hook are executed sequentially.
  • Can only usetapRegistration, not availabletapPromiseandtapAsyncregistered
// All hooks inherit from hooks
class Sync* extends Hook { 
	tapAsync() { // Sync* hooks do not support tapAsync
		throw new Error("tapAsync is not supported on a Sync*");
	}
	tapPromise() {// Sync* hooks do not support tapPromise
		throw new Error("tapPromise is not supported on a Sync*");
	}
	compile(options) { // Compile the code to execute the Plugin according to certain policies
		factory.setup(this, options);
		returnfactory.create(options); }}Copy the code

For Async* type hooks

  • supporttap,tapPromise,tapAsyncregistered
class AsyncParallelHook extends Hook {
	constructor(args) {
		super(args);
		this.call = this._call = undefined;
	}

	compile(options) {
		factory.setup(this, options);
		returnfactory.create(options); }}Copy the code
class Hook {
	constructor(args) {
		if(!Array.isArray(args)) args = [];
		this._args = args; // The string array of the hook instance
		this.taps = []; / / consumer
		this.interceptors = []; // interceptors
		this.call = this._call =  // Call the hook in sync mode
		this._createCompileDelegate("call"."sync");
		this.promise = 
		this._promise = // Make a promise
		this._createCompileDelegate("promise"."promise");
		this.callAsync = 
		this._callAsync = // Call with type async
		this._createCompileDelegate("callAsync"."async");
		this._x = undefined; // 
	}

	_createCall(type) {
		return this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});
	}

	_createCompileDelegate(name, type) {
		const lazyCompileHook = (. args) = > {
			this[name] = this._createCall(type);
			return this[name](... args); };return lazyCompileHook;
	}
	// Call the tap type register
	tap(options, fn) {
		// ...
		options = Object.assign({ type: "sync".fn: fn }, options);
		// ...
		this._insert(options);  // Add to this.taps
	}
	// Register an async hook
	tapAsync(options, fn) {
		// ...
		options = Object.assign({ type: "async".fn: fn }, options);
		// ...
		this._insert(options); // Add to this.taps} Register promise type hooks tapPromise(options, fn) {// ...
		options = Object.assign({ type: "promise".fn: fn }, options);
		// ...
		this._insert(options); // Add to this.taps}}Copy the code

Tap, tapSync, tapPromise are called each time to register different types of plug-in hooks, and they are called by way of call, callAsync, and Promise. The compile method is called to quickly compile a method to execute these plug-ins in order to follow a certain execution policy.

const factory = new Sync*CodeFactory();
class Sync* extends Hook { 
	// ...
	compile(options) { // Compile the code to execute the Plugin according to certain policies
		factory.setup(this, options);
		returnfactory.create(options); }}class Sync*CodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) = >onError(err), onDone, rethrowIfPossible }); }}Copy the code

Hookcodefactor #create is called in the compile method to compile and generate the execution code.


class HookCodeFactory {
	constructor(config) {
		this.config = config;
		this.options = undefined;
	}

	create(options) {
		this.init(options);
		switch(this.options.type) {
			case "sync":  // Compile to generate sync and return the result directly
				return new Function(this.args(), 
				"\"use strict\"; \n" + this.header() + this.content({
					// ...
					onResult: result= > `return ${result}; \n`.// ...
				}));
			case "async": // Async, async, async, async, async, async
				return new Function(this.args({
					after: "_callback"
				}), "\"use strict\"; \n" + this.header() + this.content({
					// ...
					onResult: result= > `_callback(null, ${result}); \n`.onDone: (a)= > "_callback(); \n"
				}));
			case "promise": // Return the promise type and put the result in resolve
				// ...
				code += "return new Promise((_resolve, _reject) => {\n";
				code += "var _sync = true; \n";
				code += this.header();
				code += this.content({
					// ...
					onResult: result= > `_resolve(${result}); \n`.onDone: (a)= > "_resolve(); \n"
				});
			    // ...
				return new Function(this.args(), code); }}// callTap simply executes some plug-in and returns the result
	callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		let code = "";
		let hasTapCached = false;
		// ...
		code += `var _fn${tapIndex} = The ${this.getTapFn(tapIndex)}; \n`;
		const tap = this.options.taps[tapIndex];
		switch(tap.type) {
			case "sync":
				// ...
				if(onResult) {
					code += `var _result${tapIndex} = _fn${tapIndex}(The ${this.args({
						before: tap.context ? "_context" : undefined
					})}); \n`;
				} else {
					code += `_fn${tapIndex}(The ${this.args({
						before: tap.context ? "_context" : undefined
					})}); \n`;
				}
				
				if(onResult) { // The result is passed through
					code += onResult(`_result${tapIndex}`);
				}
				if(onDone) { // Notifies the plug-in that it is ready to execute the next plug-in
					code += onDone();
				}
				break;
			case "async": // Execute asynchronously. After the plug-in is finished, the result will be transparently transmitted by executing callback
				let cbCode = "";
				if(onResult)
					cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
				else
					cbCode += `_err${tapIndex} => {\n`;
				cbCode += `if(_err${tapIndex}) {\n`;
				cbCode += onError(`_err${tapIndex}`);
				cbCode += "} else {\n";
				if(onResult) {
					cbCode += onResult(`_result${tapIndex}`);
				}
				
				cbCode += "}\n";
				cbCode += "}";
				code += `_fn${tapIndex}(The ${this.args({
					before: tap.context ? "_context" : undefined,
					after: cbCode //CbCode pass-through})}); \n`;
				break;
			case "promise": // _fn${tapIndex} is the tapIndex plugin. It must be a Promise plugin
				code += `var _hasResult${tapIndex}= false; \n`;
				code += `_fn${tapIndex}(The ${this.args({
					before: tap.context ? "_context" : undefined
				})}).then(_result${tapIndex} => {\n`;
				code += `_hasResult${tapIndex}= true; \n`;
				if(onResult) {
					code += onResult(`_result${tapIndex}`);
				}
			// ...
				break;
		}
		return code;
	}
	// Execute the plugin in the order in which the plugin is registered
	callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
		// ...
		const firstAsync = this.options.taps.findIndex(t= >t.type ! = ="sync");
		const next = i= > {
			// ...
			const done = (a)= > next(i + 1);
			// ...
			return this.callTap(i, {
				// ...
				onResult: onResult && ((result) = > {
					return onResult(i, result, done, doneBreak);
				}),
				// ...
			});
		};
		return next(0);
	}

	callTapsLooping({ onError, onDone, rethrowIfPossible }) {
		
		const syncOnly = this.options.taps.every(t= > t.type === "sync");
		let code = "";
		if(! syncOnly) { code +="var _looper = () => {\n";
			code += "var _loopAsync = false; \n";
		}
		code += "var _loop; \n";
		code += "do {\n";
		code += "_loop = false; \n";
		// ...
		code += this.callTapsSeries({
			// ...
			onResult: (i, result, next, doneBreak) = > { // Once a plugin returns undefined, a plugin is invoked. If undefined, the next plugin is invoked
				let code = "";
				code += `if(${result}! == undefined) {\n`;
				code += "_loop = true; \n";
				if(! syncOnly) code +="if(_loopAsync) _looper(); \n";
				code += doneBreak(true);
				code += `} else {\n`;
				code += next();
				code += `}\n`;
				return code;
			},
			// ...
		})
		code += "} while(_loop); \n";
		// ...
		return code;
	}
	// Call plug-in execution in parallel
	callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) = > run() }) {
		// ...
		// Iterate through the registry for all plug-ins and invoke them
		for(let i = 0; i < this.options.taps.length; i++) {
			// ...
			code += "if(_counter <= 0) break; \n";
			code += onTap(i, () => this.callTap(i, {
				// ...
				onResult: onResult && ((result) = > {
					let code = "";
					code += "if(_counter > 0) {\n";
					code += onResult(i, result, done, doneBreak);
					code += "}\n";
					return code;
				}),
				// ...
			}), done, doneBreak);
		}
		// ...
		returncode; }}Copy the code

The content method is called in HookCodeFactory#create, which calls different methods to perform the compilation to produce the final code according to the hook’s execution policy.

  • SyncHook calls' callTapsSeries' to compile the function that eventually executes the plug-in. What 'callTapsSeries' does is to iterate through the list of plug-ins in the order they were registered.Copy the code
class SyncHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) = >onError(err), onDone, rethrowIfPossible }); }}Copy the code
  • SyncBailHook when once a return value result is notundefinedThe plug-in in the execution list is finished
 class SyncBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) = > `if(${result}! == undefined) {\n${onResult(result)}; \n} else {\n${next()}}\n`.// ...}); }}Copy the code
  • The execution result of the previous plug-in in the SyncWaterfallHook is taken as the input parameter of the next plug-in
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			// ...
			onResult: (i, result, next) = > {
				let code = "";
				code += `if(${result}! == undefined) {\n`;
				code += `The ${this._args[0]} = ${result}; \n`;
				code += `}\n`;
				code += next();
				return code;
			},
			onDone: (a)= > onResult(this._args[0])}); }}Copy the code
  • AsyncParallelHook callcallTapsParallelParallel execution plug-in
class AsyncParallelHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
		return this.callTapsParallel({
			onError: (i, err, done, doneBreak) = > onError(err) + doneBreak(true), onDone }); }}Copy the code

Webpack process essay

The webPack process tutorial in this article is based on WebPack 4.

Webpack entry file

From the package.json file of the Webpack project, we find the entry to execute the function. If we introduce Webpack into the function, the entry will be lib/webpack.js, whereas if we execute it in the shell, We’ll get to./bin/webpack.js. Let’s start with lib/webpack.js.

{"name": "webpack", "version": "4.1.1",... "main": "lib/webpack.js", "web": "lib/webpack.web.js", "bin": "./bin/webpack.js", ... }Copy the code

Webpack entrance

const webpack = (options, callback) = > {
    // ...
    // Verify that options are correct
    // Preprocess options
    options = new WebpackOptionsDefaulter().process(options); // The default webPack4 configuration
	compiler = new Compiler(options.context); The Compiler / / instance
	// ...
    // If options.watch === true && callback then the watch thread is enabled
	compiler.watch(watchOptions, callback);
	compiler.run(callback);
	return compiler;
};
Copy the code

The webPack entry file actually instantiates the Compiler and calls the run method to enable compilation. The compilation of WebPack is performed in the following sequence of hook calls.

  • Before-run clears the cache
  • Run Registers the cache data hook
  • before-compile
  • Compile starts compiling
  • Make analyzes dependency and indirect-dependency modules from the entry and creates module objects
  • Build-module Module construction
  • Seal Encapsulates the build result and cannot be changed
  • After-compile completes the build, and the data is cached
  • Emit output to the dist directory

Compile & build process

Webpack builds and compiles are Compilation

class Compilation extends Tapable {
	constructor(compiler) {
		super(a);this.hooks = {
			// hooks
		};
		// ...
		this.compiler = compiler;
		// ...
		// template
		this.mainTemplate = new MainTemplate(this.outputOptions);
		this.chunkTemplate = new ChunkTemplate(this.outputOptions);
		this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
			this.outputOptions
		);
		this.runtimeTemplate = new RuntimeTemplate(
			this.outputOptions,
			this.requestShortener
		);
		this.moduleTemplates = {
			javascript: new ModuleTemplate(this.runtimeTemplate),
			webassembly: new ModuleTemplate(this.runtimeTemplate)
		};

		// Build the generated resource
		this.chunks = [];
		this.chunkGroups = [];
		this.modules = [];
		this.additionalChunkAssets = [];
		this.assets = {};
		this.children = [];
		// ...
	}
	// 
	buildModule(module, optional, origin, dependencies, thisCallback) {
		// ...
		// Call the module.build method to compile the code, which is actually using acorn compilation to generate the AST
		this.hooks.buildModule.call(module);
		module.build(/**param*/);
	}
	// Add the module to the list and compile the module
	_addModuleChain(context, dependency, onModule, callback) {
		    // ...
		    // moduleFactory.create creates the module, where the loader first processes the file and then generates the module object
		    moduleFactory.create(
				{
					contextInfo: {
						issuer: "".compiler: this.compiler.name
					},
					context: context,
					dependencies: [dependency]
				},
				(err, module) = > {const addModuleResult = this.addModule(module);
					module = addModuleResult.module;
					onModule(module);
					dependency.module = module;
					
					// ...
					// Call buildModule to build the module
					this.buildModule(module.false.null.null, err => {}); }}); }// Add the entry module and start compiling & building
	addEntry(context, entry, name, callback) {
		// ...
		this._addModuleChain( // Call _addModuleChain to add the module
			context,
			entry,
			module= > {this.entries.push(module);
			},
			// ...
		);
	}

	
	seal(callback) {
		this.hooks.seal.call();

		// ...
		const chunk = this.addChunk(name);
		const entrypoint = new Entrypoint(name);
		entrypoint.setRuntimeChunk(chunk);
		entrypoint.addOrigin(null, name, preparedEntrypoint.request);
		this.namedChunkGroups.set(name, entrypoint);
		this.entrypoints.set(name, entrypoint);
		this.chunkGroups.push(entrypoint);

		GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
		GraphHelpers.connectChunkAndModule(chunk, module);

		chunk.entryModule = module;
		chunk.name = name;

		 // ...
		this.hooks.beforeHash.call();
		this.createHash();
		this.hooks.afterHash.call();
		this.hooks.beforeModuleAssets.call();
		this.createModuleAssets();
		if (this.hooks.shouldGenerateChunkAssets.call() ! = =false) {
			this.hooks.beforeChunkAssets.call();
			this.createChunkAssets();
		}
		// ...
	}


	createHash() {
		// ...
	}
	
	// Generate assets and save them to Compilation. Assets for use when writing plugins to WebPack
	createModuleAssets() {
		for (let i = 0; i < this.modules.length; i++) {
			const module = this.modules[i];
			if (module.buildInfo.assets) {
				for (const assetName of Object.keys(module.buildInfo.assets)) {
					const fileName = this.getPath(assetName);
					this.assets[fileName] = module.buildInfo.assets[assetName]; 
					this.hooks.moduleAsset.call(module, fileName);
				}
			}
		}
	}

	createChunkAssets() {
	 // ...}}Copy the code

In the Webpack Make hook, tapAsync registers a DllEntryPlugin that starts the build process by adding all entry modules to the build queue by calling the compilation.addEntry method.

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 = `The ${this.name}:${idx}`;
					return dep;
				}),
				this.name
			),
			// ...
		);
	});
Copy the code

Then call _addModuleChain in addEntry to start compiling. The _addModuleChain module is first generated and then built.

class NormalModuleFactory extends Tapable {
	// ...
	create(data, callback) {
		// ...
		this.hooks.beforeResolve.callAsync(
			{
				contextInfo,
				resolveOptions,
				context,
				request,
				dependencies
			},
			(err, result) => {
				if (err) return callback(err);

				// Ignored
				if(! result)return callback();
				// The factory hook will trigger the execution of the Resolver hook, and the Resolver hook will use Acorn to process JS to generate AST, and then use the Loader to load the file before acorn processing
				const factory = this.hooks.factory.call(null);

				factory(result, (err, module) = > {if (err) return callback(err);

					if (module && this.cachePredicate(module)) {
						for (const d of dependencies) {
							d.__NormalModuleFactoryCache = module;
						}
					}

					callback(null.module); }); }); }}Copy the code

After compilation is complete, the compilation is closed with the compilation.seal method to generate the resources that are stored in the compilation.assets, or the compilation.chunk, to be used when writing plug-ins to WebPack

class Compiler extends Tapable {
	constructor(context) {
		super(a);this.hooks = {
			beforeRun: new AsyncSeriesHook(["compilation"]),
			run: new AsyncSeriesHook(["compilation"]),
			emit: new AsyncSeriesHook(["compilation"]),
			afterEmit: new AsyncSeriesHook(["compilation"]),
			compilation: new SyncHook(["compilation"."params"]),
			beforeCompile: new AsyncSeriesHook(["params"]),
			compile: new SyncHook(["params"]),
			make: new AsyncParallelHook(["compilation"]),
			afterCompile: new AsyncSeriesHook(["compilation"]),
			// other hooks
		};
		// ...
	}

	run(callback) {
		const startTime = Date.now();

		const onCompiled = (err, compilation) = > {
			// ...

			this.emitAssets(compilation, err => {
				if (err) return callback(err);

				if (compilation.hooks.needAdditionalPass.call()) {
					compilation.needAdditionalPass = true;

					const stats = new Stats(compilation);
					stats.startTime = startTime;
					stats.endTime = Date.now();
					this.hooks.done.callAsync(stats, err => {
						if (err) return callback(err);

						this.hooks.additionalPass.callAsync(err= > {
							if (err) return callback(err);
							this.compile(onCompiled);
						});
					});
					return;
				}
				// ...
			});
		};

		this.hooks.beforeRun.callAsync(this, err => {
			if (err) return callback(err);
			this.hooks.run.callAsync(this, err => {
				if (err) return callback(err);

				this.readRecords(err= > {
					if (err) return callback(err);

					this.compile(onCompiled);
				});
			});
		});
	}
	// Output files to the build directory
	emitAssets(compilation, callback) {
		// ...
		this.hooks.emit.callAsync(compilation, err => {
			if (err) return callback(err);
			outputPath = compilation.getPath(this.outputPath);
			this.outputFileSystem.mkdirp(outputPath, emitFiles);
		});
	}
	
	newCompilationParams() {
		const params = {
			normalModuleFactory: this.createNormalModuleFactory(),
			contextModuleFactory: this.createContextModuleFactory(),
			compilationDependencies: new Set()
		};
		return params;
	}

	compile(callback) {
		const params = this.newCompilationParams();
		this.hooks.beforeCompile.callAsync(params, err => {
			if (err) return callback(err);
			this.hooks.compile.call(params);
			const compilation = this.newCompilation(params);

			this.hooks.make.callAsync(compilation, err => {
				if (err) return callback(err);
				compilation.finish();
				// After the make hook executes, call SEAL to generate the resource
				compilation.seal(err= > {
					if (err) return callback(err);
					this.hooks.afterCompile.callAsync(compilation, err => {
						if (err) return callback(err);
						Emit, generate the final file
						return callback(null, compilation); }); }); }); }); }}Copy the code

The final output

After the execution of SEAL, the emit hook is called to output the file to the specified path according to the path property configured in the Output of the Webpack config file.


“IVWEB Technology Weekly” shock online, pay attention to the public number: IVWEB community, weekly timing push quality articles.

  • Weekly articles collection: weekly
  • Team open source project: Feflow