preface

Webpack is a rich topic. Here’s an example.

  • webpackloaderpluginWhat’s the difference?
  • writtenwebpackloaderorplugin?
  • webpackWhat is the compilation process?

And then you can start!!!!! A lot of. Both of you are right, but the interview time is limited, plus the Internet has a lot of answers. So, questions like these are not likely to be at the top of an interviewer’s list.

So, have you ever encountered or seen a problem like this?

Q:webpackIn the compilation processhookWhat are the nodes?

I mentioned the webPack compilation process in a previous article, and the text is already very detailed. For details, click on how the advanced/advanced front end answers JavaScript interview questions (ii).

The beginning of a webpack

Let’s start with webpack.config.js

const path = require('path')

module.exports = {
  devtool: 'none'.mode: 'development'.context: process.cwd(),
  entry: './src/index.js'.output: {
    filename: 'index.js'.path: path.resolve('dist')},module: {rules: [{test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader".options: {
            presets: [["@babel/preset-env",
                {
                  useBuiltIns: "usage"}]."@babel/preset-react"]}}}]},plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'}})]Copy the code

Think about it, why can you write webpack.config.js in your project to do everything?

  • There areentry(entry)
  • There areloader
  • There areplugin
  • There areoutput

Module. exports finally produced a {}. So who is the commonJS {} used here?

We look atnode_modules/webpack-cli/bin/cli.jsInside part of the source code


/ / cli. Js... Part of the
const webpack = require("webpack"); 

let lastHash = null;
let compiler;
    try {
	compiler = webpack(options); / / 1
	} catch (err) {
            if (err.name === "WebpackOptionsValidationError") {
		if (argv.color) console.error(`\u001b[1m\u001b[31m${err.message}\u001b[39m\u001b[22m`);
                else console.error(err.message);
                // eslint-disable-next-line no-process-exit
                process.exit(1);
                }

                throw err;
        }
// ...
// ...
// ...

if (firstOptions.watch || options.watch) { / / watch model
    const watchOptions = 
    irstOptions.watchOptions || options.watchOptions || 
    firstOptions.watch || options.watch || {};
    if (watchOptions.stdin) {
            process.stdin.on("end".function(_) {
                    process.exit(); // eslint-disable-line
            });
            process.stdin.resume();
    }
    compiler.watch(watchOptions, compilerCallback);
    if(outputOptions.infoVerbosity ! = ="none") console.error("\nwebpack is watching the files... \n");
} else {
    // run method,
  compiler.run((err, stats) = > { / / 2
		if (compiler.close) {
                    ompiler.close(err2= > {
			compilerCallback(err || err2, stats);
                    });
		} else{ compilerCallback(err, stats); }}); }// ...
Copy the code

Look at 1,2.

  1. compiler = webpack(options);
  2. compiler.run

Module. exports exports {}, options (1). Options = webpack.config.js module. Exports + webpack default built-in configuration. The key point I want to talk about is not here, if you have time, please learn more about it.

Moving on, there’s webpack(options), so look at node_modules/lib/webpack.js

/ * * *@param {WebpackOptions} options options object
 * @param {function(Error=, Stats=): void=} callback callback
 * @returns {Compiler | MultiCompiler} the compiler object
 */
const webpack = (options, callback) = > {
	// code ...
	if (Array.isArray(options)) {
		Module.exports = []; // exports = module. Exports = []
                // code ...
	} else if (typeof options === "object") {
		options = new WebpackOptionsDefaulter().process(options); // Get the default built-in configuration information for WebPack

		compiler = new Compiler(options.context);
		compiler.options = options;
                
                // NodeEnvironmentPlugin gives compiler files the ability to read and write
		new NodeEnvironmentPlugin({
			infrastructureLogging: options.infrastructureLogging
		}).apply(compiler);
		if (options.plugins && Array.isArray(options.plugins)) {
			for (const plugin of options.plugins) {
				if (typeof plugin === "function") {
					plugin.call(compiler, compiler);
				} else {
					plugin.apply(compiler);
				}
			}
		}
                
		compiler.hooks.environment.call(); // the first hook encountered
		compiler.hooks.afterEnvironment.call(); // meet the second hook
                // The following line of code is important and will be highlighted later
		compiler.options = new WebpackOptionsApply().process(options, compiler);
	} else {
		throw new Error("Invalid argument: options");
	}
	if (callback) {
            // code ...
	}
	return compiler;
};
Copy the code

The first hook I met

  • compiler.hooks.environment// the first hook encountered
  • compiler.hooks.afterEnvironment// meet the second hook

What are these hooks for, you ask?

We’ll follow the source code directly to the location of the two hooks: node_modules/lib/ compiler.js

class Compiler extends Tapable {
	constructor(context) {
		super(a);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"])};// code ...
             }
              // code ...
}
Copy the code

The following hooks are weirdly located here. Move them for webpack 5. These hooks are a bit weird and webpack5 will remove them. Because Webpack has some hooks reserved, the purpose is to fire them at different stage times. These two hooks, however, are really just stage reminders and don’t do anything special.

Continue with the compiler.run method.

// code...
run(callback) {
		if (this.running) return callback(new ConcurrentCompilationError());

		const finalCallback = (err, stats) = > {
			this.running = false;

			if (err) {
				this.hooks.failed.call(err);
			}

			if(callback ! = =undefined) return callback(err, stats);
		};

		const startTime = Date.now();

		this.running = true;

		const onCompiled = (err, compilation) = > {
			if (err) return finalCallback(err);

			if (this.hooks.shouldEmit.call(compilation) === false) {
				const stats = new Stats(compilation);
				stats.startTime = startTime;
				stats.endTime = Date.now();
				this.hooks.done.callAsync(stats, err= > {
					if (err) return finalCallback(err);
					return finalCallback(null, stats);
				});
				return;
			}

			this.emitAssets(compilation, err= > {
				if (err) return finalCallback(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 finalCallback(err);

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

				this.emitRecords(err= > {
					if (err) return finalCallback(err);

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

		this.hooks.beforeRun.callAsync(this.err= > {
			if (err) return finalCallback(err);

			this.hooks.run.callAsync(this.err= > {
				if (err) return finalCallback(err);

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

					this.compile(onCompiled);
				});
			});
		});
	}
Copy the code

Resolution:

  1. We define a finalCallback that fires the this.links. failed hook inside the method. The method is not called at this point.

  2. OnCompiled defines an onCompiled that is ready to be called later in the compile method. This means the logic to occur after a module has been compiled.

    • this.hooks.shouldEmitHooks are used to determine the compilation of the current module (i.ecompilation(Have you finished it? If done, execute finalCallback directly.
    • Next, executethis.emitAssetsMethods (which will eventually be handled herechunkWrites to the specified file and prints todist
    • The triggercompilation.hooks.needAdditionalPassHook, which means that there are additional conditions that need to be met, otherwise return undefined, and the program terminates.
      • willcompilation.needAdditionalPassSet totrue
      • Assigns some values from the compilation object to STATS
      • Trigger the this.hooks. Done hook to execute finalCallback.
  3. BeforeRun hook, or asynchronously if there are no exceptions.

  4. Call the this.readRecords method (to read the file) and pass what you read to this.pile. So let’s go ahead and look at the compile method


  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(err= > {
          if (err) return callback(err);

          compilation.seal(err= > {
            if (err) return callback(err);

            this.hooks.afterCompile.callAsync(compilation, err= > {
              if (err) return callback(err);

              return callback(null, compilation);
            });
          });
        });
      });
    });
  }
  
  // code ...
  
  newCompilation(params) {
    const compilation = this.createCompilation();
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
  }
Copy the code
  1. The compile method
    • By calling thethis.newCompilationParamsMethod to get the initialization parameters to instantiate the compilation.
    • The triggerthis.hooks.beforeCompileHook, or return if an exception occurs.
    • Immediate triggerthis.hooks.compilehook
    • callthis.newCompilationmethods
      • Instantiate object:compilation
      • The triggerthis.hooks.thisCompilationhook
      • The triggerthis.hooks.compilationhook
      • Get the instantiated object:compilation
    • The triggerthis.hooks.makeHook, or return if an exception occurs.
    • callcompilation.finishMethod, and return a callback if an exception occurs.
    • callcompilation.sealMethod (to process chunk) and return a callback if an exception occurs.
    • The triggerthis.hooks.afterCompileHook, which represents completion of compilation.
    • The final callback for the return isthis.onCompiled. Performthis.onCompiledAnd passed incompilation. Back to theStep 2

What about the hooks for the compilation?

Q: Where did you go? What happened to the important hooks in the Compilation object?

A: When you trigger the this.hooks. Make hook, the compilation. AddEntry method is called.

How do I listen for the make hook?

A: You may not have forgotten the beginning of the code…

compiler.options = new WebpackOptionsApply().process(options, compiler); // Please search globally for this line of code
Copy the code

WebpackOptionsApply is what?node_modules/lib/WebpackOptionsApply.js

// Abbreviate the code, because there are too many 😂

//WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin")


class WebpackOptionsApply {
  process(options, compiler) {
    new EntryOptionPlugin().apply(compiler)

    compiler.hooks.entryOption.call(options.context, options.entry)
  }
}

module.exports = WebpackOptionsApply
Copy the code

Obviously, depend onEntryOptionPlugin.js

// EntryOptionPlugin.js
const SingleEntryPlugin = require("./SingleEntryPlugin")

const itemToPlugin = function (context, item, name) {
  return new SingleEntryPlugin(context, item, name)
}

class EntryOptionPlugin {
  apply(compiler) {
    compiler.hooks.entryOption.tap('EntryOptionPlugin'.(context, entry) = > {
      itemToPlugin(context, entry, "main").apply(compiler)
    })
  }
}

module.exports = EntryOptionPlugin
Copy the code

Obviously, depend onSingleEntryPlugin.js

// SingleEntryPlugin.js

class SingleEntryPlugin {
  constructor(context, entry, name) {
    this.context = context
    this.entry = entry
    this.name = name
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
        "SingleEntryPlugin".(compilation, { normalModuleFactory }) = >{ compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); }); compiler.hooks.make.tapAsync("SingleEntryPlugin".(compilation, callback) = > {
            const { entry, name, context } = this;

            constdep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); }); }}module.exports = SingleEntryPlugin

Copy the code

Resolution:

  1. new WebpackOptionsApply().process(options, compiler)Mount all webPack built-in plug-ins (entry)
  2. Go to theWebpackOptionsApply.jsamong
    • processMethod is callednew EntryOptionPlugin().apply(compiler)
      • Go to theEntryOptionPlugin.jsamong
        • In the callitemToPlugin, and returned another oneSingleEntryPluginThe constructor of the instance object is responsible for receiving the abovecontext entry name
        • entryOptionIs a hook instance,entryOptionEntryOptionPluginThe inside of theapplyMethod is calledtap(Registered event listener)
      • Go to theSingleEntryPlugin.js
        • compilationHooks to monitor
        • makeThe hook listens.
    • triggeredcompiler.hooks.entryOptionhook

On the blackboard! To highlight! Once the make hook is triggered, it is executed in its callback methodcompilation.addEntry“, indicating that all the preparatory work has been done before the module is compiled.

  1. addEntry -> this._addModuleChain -> this.createModule
  2. Finally bycompilercallcompilation.sealmethods
    • Triggers the compilation. h-ooks. seal hook

    • Trigger compilation. Hooks. BeforeChunks hook

    • Trigger compilation. Hooks. AfterChunks hook

    • Call compilation. CreateChunkAssets method, finally to invoke this. EmitAssets method, under the output files to the package path.

The subtotal

Q: What are the hook nodes in the Webpack compilation process?

A:

  • compiler.hooks.environment
  • compiler.hooks.afterEnvironment
  • compiler.hooks.failed
  • compiler.hooks.shouldEmit
  • compilation.hooks.needAdditionalPass
  • compiler.hooks.beforeRun
  • compiler.hooks.run
  • compiler.hooks.beforeCompile
  • compiler.hooks.compile
  • compiler.hooks.thisCompilation
  • compiler.hooks.compilation
  • compiler.hooks.make
  • compiler.hooks.afterCompile
  • compiler.hooks.entryOption
  • compilation.hooks.seal
  • compilation.hooks.beforeChunks
  • compilation.hooks.afterChunks
  • compilation.createChunkAssets

(Wish you all the best)