Webpack source code analysis

// Use the Webpack version

"html-webpack-plugin": "^ 4.5.0." "."webpack": "^ 4.44.2"."webpack-cli": "^" 3.3.12
Copy the code

Locate the WebPack entry

We create a run.js file, import webpack and our configuration file webpack.config.js, and call webpack as a function with the parameters in the configuration option webpack.config.js:

// run.js

const config = require('./webpack.config')
const webpack = require('webpack')

const compiler = webpack(config)

compiler.run((err, stats) = > {
  console.log(err)
  console.log(stats.toJson())
})
Copy the code

After the execution of the webpack function, a compiler will be generated, and the run method of the compiler will be invoked to compile the code. We can run the run.js using Node and find that the project can compile and package normally. This shows that compiler is the core module of Webpack.

Let’s look at the package.json identifier for the entry file in Webpack:

"main": "lib/webpack.js"."web": "lib/webpack.web.js"."bin": "./bin/webpack.js".Copy the code

What does lib/webpack.js do

First let’s look at what the webpack.js file in webpack’s lib directory does:

// webpack lib/webpack.js.// There are a bunch of file imports

/ * * *@param {WebpackOptions} options options object
 * @param {function(Error=, Stats=): void=} callback callback
 * @returns {Compiler | MultiCompiler} the compiler object
 */
const webpack = (options, callback) = > {
  // Verify that options comply with rules, that is, verify that the user configuration file does not have any improper
  const webpackOptionsValidationErrors = validateSchema(
    webpackOptionsSchema,
    options
  );
  // If there is a problem with the user profile, an exception message is thrown
  if (webpackOptionsValidationErrors.length) {
    throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
  }
  // Declare a compiler
  let compiler;
  // In the case of multi-configuration packaging, multiple compiler are generated and the compilers are handed over to MultiCompiler
  if (Array.isArray(options)) {
    compiler = new MultiCompiler(
      Array.from(options).map((options) = > webpack(options))
    );
  } else if (typeof options === "object") {
    // Otherwise, the default webPack configuration is merged into the user configuration to enhance compilation capabilities
    options = new WebpackOptionsDefaulter().process(options);

    // Generate compiler instances
    compiler = new Compiler(options.context);
    
    // Mount configuration options to the Compiler instance, carrying options throughout the packaging process
    compiler.options = options;
    
    // Load the node I/O file read and write plug-in, respectively
    // inputFileSystem and outputFileSystem process file I/O.
    // watchFileSystem listens for file changes, while intermediateFileSystem handles all file system operations that are not considered input or output.
    // Such as writing records, caching, or parsing output
    new NodeEnvironmentPlugin({
      infrastructureLogging: options.infrastructureLogging,
    }).apply(compiler);
    
    // Mount the user plug-in
    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); }}}// Execute the event bound to the environment
    compiler.hooks.environment.call();
    
    // Execute the event bound to afterEnvironment
    compiler.hooks.afterEnvironment.call();
    
    // Load the built-in webpack plug-in
    compiler.options = new WebpackOptionsApply().process(options, compiler);
  } else {
    throw new Error("Invalid argument: options");
  }
  // Compile the callback
  if (callback) {
    if (typeofcallback ! = ="function") {
      throw new Error("Invalid argument: callback");
    }
    if (
      options.watch === true| | -Array.isArray(options) && options.some((o) = > o.watch))
    ) {
      const watchOptions = Array.isArray(options)
        ? options.map((o) = > o.watchOptions || {})
        : options.watchOptions || {};
      return compiler.watch(watchOptions, callback);
    }
    compiler.run(callback);
  }
  return compiler;
};

exports = module.exports = webpack; .// The following is to mount a bunch of native plug-ins to webpack to facilitate the 'webpack.' call
Copy the code

Lib /webpack.js does one main thing: Compiler instance compiler is generated, which is an instance object of class Compiler, while Compiler is a subclass of Tapable. Therefore, the core of Compiler is to execute a compilation and packaging task mounted on hooks, which can be synchronous or asynchronous. Eventually a packaged collection of files will be output, and the key to the input and output is the NodeEnvironmentPlugin. Plugins mounted in this process are also tasks to be executed on the hook. When the hook is triggered, these plug-in tasks will participate in the compilation and packaging process, enhancing the compilation and packaging effect.

What does bin/webpack.js do

Let’s take a look at what bin/webpack.js does:

// webpack bin/webpack.js

#!/usr/bin/env node

// @ts-ignore
// Process exit ID. 0 indicates normal exit and 1 indicates abnormal exit
process.exitCode = 0;

/ * * *@param {string} command process to run
 * @param {string[]} args commandline arguments
 * @returns {Promise<void>} promise* /
// Execute command line tasks, mainly used to guide users to install the Webpack command line tool
const runCommand = (command, args) = > {
  const cp = require("child_process");
  return new Promise((resolve, reject) = > {
    const executedCommand = cp.spawn(command, args, {
      stdio: "inherit".shell: true}); executedCommand.on("error".(error) = > {
      reject(error);
    });

    executedCommand.on("exit".(code) = > {
      if (code === 0) {
        resolve();
      } else{ reject(); }}); }); };/ * * *@param {string} packageName name of the package
 * @returns {boolean} is the package installed? * /
// Determine whether module dependencies are installed by obtaining the path of module dependencies
const isInstalled = (packageName) = > {
  try {
    require.resolve(packageName);

    return true;
  } catch (err) {
    return false; }};/ * * *@typedef {Object} CliOption
 * @property {string} name display name
 * @property {string} package npm package name
 * @property {string} binName name of the executable file
 * @property {string} alias shortcut for choice
 * @property {boolean} installed currently installed?
 * @property {boolean} recommended is recommended
 * @property {string} url homepage
 * @property {string} description description
 */

/ * *@type {CliOption[]} * /
// Webpack command line tools collection
const CLIs = [
  {
    name: "webpack-cli".package: "webpack-cli".binName: "webpack-cli".alias: "cli".installed: isInstalled("webpack-cli"),
    recommended: true.url: "https://github.com/webpack/webpack-cli".description: "The original webpack full-featured CLI."}, {name: "webpack-command".package: "webpack-command".binName: "webpack-command".alias: "command".installed: isInstalled("webpack-command"),
    recommended: false.url: "https://github.com/webpack-contrib/webpack-command".description: "A lightweight, opinionated webpack CLI.",},];// Find the installed command line tool, usually webpack-CLI
/ / {
// name: "webpack-cli",
// package: "webpack-cli",
// binName: "webpack-cli",
// alias: "cli",
// installed: isInstalled("webpack-cli"),
// recommended: true,
// url: "https://github.com/webpack/webpack-cli",
// description: "The original webpack full-featured CLI."
// },
const installedClis = CLIs.filter((cli) = > cli.installed);

if (installedClis.length === 0) {
  // If you do not have any webpack command-line tools installed, some questions will be asked from the command line
  // to guide the user to install the WebPack command line tool
  const path = require("path");
  const fs = require("fs");
  const readLine = require("readline");

  let notify =
    "One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:"; .// Omit some code (just ask some information to guide the user to install the Webpack command-line tool dependency)

} else if (installedClis.length === 1) {
  const path = require("path");
  InstalledClis [0]. Package = 'webpack-cli'
  // this is equivalent to const pkgPath = require.resolve(' webpack-cli/package.json ');
  // This is the absolute path to the webpack command line tool package.json
  const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
  // eslint-disable-next-line node/no-missing-require
  // Load package.json to obtain package information
  const pkg = require(pkgPath);
  // eslint-disable-next-line node/no-missing-require
  // installedClis[0]. BinName = 'webpack-cli'
  // path.dirname(pkgPath) is the absolute path of webpack-cli
  // "bin": {
  // "webpack-cli": "./bin/cli.js"
  // },
  // Load webpack-cli/bin/cli.js
  require(path.resolve(
    path.dirname(pkgPath),
    pkg.bin[installedClis[0].binName]
  ));
} else {
  // Installing multiple command-line tools can cause conflicts, so prompt the user to uninstall one and keep only one
  console.warn(
    `You have installed ${installedClis
      .map((item) => item.name)
      .join(
        " and "
      )} together. To work with the "webpack" command you need only one CLI package, please remove one of them or use them directly via their binary.`
  );

  // @ts-ignore
  process.exitCode = 1;
}
Copy the code

Webpack-cli or webpack-command is the command line tool we have installed. We usually use webpack-cli, after finding it, directly through the require function to execute.

By reading webpack-cli/bin/cli.js, we can see that processOptions(options) is finally called; To execute the following code:

const config = require('./webpack.config')
const webpack = require('webpack')

const compiler = webpack(config)

compiler.run((err, stats) = > {
  console.log(err)
  console.log(stats.toJson())
})
Copy the code

Webpack-cli /bin/cli.js is used by processOptions.

This is the run.js we wrote at the beginning, that is, when the command line runs webpack, bin/webpack.js will determine whether the user has installed command line tools such as webpack-cli, if not, it will prompt the user to install it, and then install options through command line parameters. Finally, we give the options to our run.js code to execute and package.

Packaging main process analysis

1. Configure the extension

Extend user – defined configurations with webPack default configurations to enhance compilation capabilities.

// node_modules/webpack/lib/webpack.js

function webpack(){... options =newWebpackOptionsDefaulter().process(options); . }Copy the code

The WebpackOptionsDefaulter class does not have its own member methods. It inherits the parent class from OptionsDefaulter. It has only one constructor.

// node_modules/webpack/lib/WebpackOptionsDefaulter.js

class WebpackOptionsDefaulter extends OptionsDefaulter {
  constructor() {
    super(a);this.set("entry"."./src");

    this.set("devtool"."make".(options) = >
      options.mode === "development" ? "eval" : false
    );
    this.set("cache"."make".(options) = > options.mode === "development");

    this.set("context", process.cwd());
    this.set("target"."web");

    this.set("module"."call".value= > Object.assign({}, value)); . }}Copy the code
// node_modules/webpack/lib/OptionsDefaulter.js

/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */
"use strict";

/** * Retrieve the user-defined configuration *@param {object} Obj User-defined configuration *@param {string} Path Configuration option (path parameter: output.filename) *@returns {any} - if {@param path} requests element from array, then `undefined` will be returned
 */
const getProperty = (obj, path) = > {
  let name = path.split(".");
  for (let i = 0; i < name.length - 1; i++) {
    obj = obj[name[i]];
    // If the value configured by the user is not an object, invert it to true, or an array, and stop execution
    if (typeofobj ! = ="object"| |! obj ||Array.isArray(obj)) return;
  }
  return obj[name.pop()];
};

/**
 * Sets the value at path of object. Stops execution, if {@param path} requests element from array to be set
 * @param {object} obj object to query
 * @param {string} path query path
 * @param {any} value value to be set
 * @returns {void}* /
const setProperty = (obj, path, value) = > {
  let name = path.split(".");
  for (let i = 0; i < name.length - 1; i++) {
    // If the value of the configuration item is not an object, or is not undefined, stop execution
    if (typeofobj[name[i]] ! = ="object"&& obj[name[i]] ! = =undefined) return;
    // If the value of the configuration item is an array, stop execution
    if (Array.isArray(obj[name[i]])) return;
    // If the value is undefined, declare it as an object
    if(! obj[name[i]]) obj[name[i]] = {};// Set the configuration item to obj
    obj = obj[name[i]];
  }
  // Set values for configuration options
  obj[name.pop()] = value;
};

/ * * *@typedef {'call' | 'make' | 'append'} Configuration mode */
/ * * *@typedef {(options: object) => any} Create the configuration function */
/ * * *@typedef {(value: any, options: object) => any} Compute the configuration function */
/ * * *@typedef {any[]} AppendConfigValues* /

class OptionsDefaulter {
  constructor() {
    /** * stores the default option Settings or functions used to calculate them (the default configuration can be either a setting or a function that requires a function call to calculate the Settings) */
    this.defaults = {};
    / * * * store configuration options {' call '|' make '|' append '} *@type {{[key: string]: ConfigType}} * /
    this.config = {};
  }

  /**
   * Enhancing {@param options} with default values
   * @param {object} options provided options
   * @returns {object} - enhanced options
   * @throws {Error} - will throw error, if configuration value is other then `undefined` or {@link ConfigType}
   */
  process(options) {
    options = Object.assign({}, options);
    for (let name in this.defaults) {
      switch (this.config[name]) {
        /** * If the configuration item does not need to be evaluated by a function, and the user has not set a value for it, set it */ using the value in default
        case undefined:
          if (getProperty(options, name) === undefined) {
            setProperty(options, name, this.defaults[name]);
          }
          break;
        /** * call takes out the user configuration, returns a value or merges an object, and sets it back */
        case "call":
          setProperty(
            options,
            name,
            this.defaults[name].call(this, getProperty(options, name), options)
          );
          break;
        /** * make depends on the user's configuration options, evaluates according to the options, * executes the WebpackOptionsDefaulter function set by the set method * This function depends on the user's configuration options. The configuration value */ is calculated based on some configurations in options
        case "make":
          // If the user configures this option, the user's configuration is used, otherwise the default configuration is used
          if (getProperty(options, name) === undefined) {
            setProperty(options, name, this.defaults[name].call(this, options));
          }
          break;
        /** * Insert the default configuration to the original configuration (basically useless) */
        case "append": {
          let oldValue = getProperty(options, name);
          if (!Array.isArray(oldValue)) { oldValue = []; } oldValue.push(... this.defaults[name]); setProperty(options, name, oldValue);break;
        }
        default:
          throw new Error(
            "OptionsDefaulter cannot process " + this.config[name] ); }}return options;
  }

  /** * set the default value *@param {string} Name option path (not a single name, name path, similar to output.filename) *@param {ConfigType | any} Config If a third argument is provided, then it must be evaluated using config (the third argument is usually a function, and the second argument determines how it is evaluated) *@param {MakeConfigHandler | CallConfigHandler | AppendConfigValues} [def] Default configuration *@returns {void}* /
  set(name, config, def) {
    // Defaults stores the values of configuration options, or gets a function
    // config stores the value of configuration options
    if(def ! = =undefined) {
      this.defaults[name] = def;
      this.config[name] = config;
    } else {
      this.defaults[name] = config;
      delete this.config[name]; }}}module.exports = OptionsDefaulter;
Copy the code

The constructor of WebpackOptionsDefaulter stores the values of configuration options or computed values in its defaults by calling the set method, stores the computed values in config, call takes out the user configuration and after some judgment, returns this value, Or after object merge; Make relies on the user’s custom configuration to calculate the value and then set it to the configuration item.

2. Instantiate Compiler

The Compiler module is the backbone engine of WebPack and creates a Compilation instance through all the options passed through the CLI or Node API. It extends from the Tapable class for registering and calling plug-ins. Most user-facing plug-ins are first registered with Compiler.

// node_modules/webpack/lib/webpack.js

function webpack(){... compiler =newCompiler(options.context); . }Copy the code

Create an instance of Compiler. The parameter is the current execution environment (process.cwd()) in which webpack is currently executed, usually the root (absolute) path of the project. Class Compiler is a subclass of class Tapable. Compiler’s constructor first executes the Tapable constructor of the super() class:

// Tapable.js

function Tapable() {
  this._pluginCompat = new SyncBailHook(["options"]);

  // The plug-in name is capitalized
  this._pluginCompat.tap(
    {
      name: "Tapable camelCase".stage: 100,},(options) = > {
      options.names.add(
        options.name.replace(/[- ]([a-z])/g.(str, ch) = >ch.toUpperCase()) ); });// Mount the plug-in to hooks
  this._pluginCompat.tap(
    {
      name: "Tapable this.hooks".stage: 200,},(options) = > {
      let hook;
      for (const name of options.names) {
        hook = this.hooks[name];
        if(hook ! = =undefined) {
          break; }}if(hook ! = =undefined) {
        const tapOpt = {
          name: options.fn.name || "unnamed compat plugin".stage: options.stage || 0};if (options.async) hook.tapAsync(tapOpt, options.fn);
        else hook.tap(tapOpt, options.fn);
        return true; }}); }Copy the code

Tapable defines a member _pluginCompat and instantiates a synchronous fuse hook assigned to _pluginCompat. This hook is used to handle plug-in compatibility and registers two hook functions to capitalize and mount plug-ins. The following plugin and apply are mounted on the Tapable prototype. Plugin prompts you to mount the plug-in using _pluginCompat, and apply prompts you to Tapable. Apply is deprecated, and apply is invoked directly on the plug-in.

After the Tapable constructor is executed, Compiler then mounts lifecycle functions on its own hooks, which are instances of Tapabe hook classes:

// node_modules/webpack/lib/Compiler.js

this.hooks = {
  // All the files that need to be exported have been generated, asking the plug-in which files need to be exported and which files do not
  shouldEmit: new SyncBailHook(["compilation"]),

  // Complete a complete compilation and output process successfully
  done: new AsyncSeriesHook(["stats"]),

  // This hook allows you to do one more build
  additionalPass: new AsyncSeriesHook([]),

  // Hooks started before run are called before a build is executed, and immediately after the compiler.run method is executed
  beforeRun: new AsyncSeriesHook(["compiler"]),

  // Start a new compilation
  run: new AsyncSeriesHook(["compiler"]),

  // Once you have determined which files to output, execute file output. You can obtain and modify the output here
  emit: new AsyncSeriesHook(["compilation"]),

  // Execute when asset is output. This hook can access information about the output asset, such as its output path and byte content
  assetEmitted: new AsyncSeriesHook(["file"."content"]),

  // The hook to execute after output
  afterEmit: new AsyncSeriesHook(["compilation"]),

  Called when the compilation is initialized and before the compilation event is triggered
  thisCompilation: new SyncHook(["compilation"."params"]),

  // When Webpack is running in development mode, a new Compilation is created whenever a file change is detected. A Compilation object contains the current module resources, compile-build resources, changing files, and so on. The Compilation object also provides many event callbacks for plug-ins to extend
  // compilation is executed after creation
  compilation: new SyncHook(["compilation"."params"]),

  // Compiler generates various modules using the NormalModuleFactory module. Starting at the entry point, the module breaks down each request, parses the file contents for further requests, and then crawls the entire file by breaking down all requests and parsing new files. In the final phase, each dependency becomes an instance of the module
  normalModuleFactory: new SyncHook(["normalModuleFactory"]),

  // Compiler uses the ContextModuleFactory module to generate dependencies from WebPack's unique require.context API. It parses the requested directory, generates requests for each file, and filters based on the regExp passed in. The final matched dependency will be passed to NormalModuleFactory
  contextModuleFactory: new SyncHook(["contextModulefactory"]),

  // Executes after the Compilation parameter is created. This hook can be used to add/modify the Compilation parameter
  beforeCompile: new AsyncSeriesHook(["params"]),

  // beforeCompile immediately after, but before a new compilation is created
  // This event tells the plug-in that a new compilation is about to start, with the compiler object attached to the plug-in
  compile: new SyncHook(["params"]),

  // compilation is executed before the end of compilation. A new compilation is created, which reads files from Entry, compiles files according to the file type and Loader configuration, and then finds out the files that the file depends on, and recursively compiles and parses the files
  make: new AsyncParallelHook(["compilation"]),

  // The Compilation is completed and executed after the Compilation and sealing
  afterCompile: new AsyncSeriesHook(["compilation"]),

  // In listening mode, after a new compilation is triggered, but before the compilation actually starts
  // It is similar to run, except that it starts compilation in listening mode. In this event, you can see which files have changed causing a new compilation to restart
  watchRun: new AsyncSeriesHook(["compiler"]),

  // called when the compilation fails
  // If you encounter an exception during the compilation and output process that causes Webpack to exit, you will go directly to this step. The plug-in can obtain the specific cause of the error from this event
  failed: new SyncHook(["error"]),

  // executes when a compilation under observation is invalid
  invalid: new SyncHook(["filename"."changeTime"]),

  // executes when a compilation under observation is stopped
  watchClose: new SyncHook([]),

  InfrastructureLogging allows infrastructure logs to be used when the infrastructureLogging option is enabled in the configuration
  infrastructureLog: new SyncBailHook(["origin"."type"."args"]),

  // called when the compiler is preparing the environment, right after the plug-in is initialized in the configuration file
  // Start applying node.js-style file systems to compiler objects to facilitate subsequent file finding and reading
  environment: new SyncHook([]),

  // When the compiler environment is set, it is called directly after the environment hook
  afterEnvironment: new SyncHook([]),

  // called after the initialization of the internal plug-in collection is set up
  // Invoke the apply method of all built-in and configured plug-ins to enable the plug-in
  afterPlugins: new SyncHook(["compiler"]),

  // triggered when resolver is set
  // Initialize the resolver according to the configuration. The resolver is responsible for finding the file in the specified path in the file system
  afterResolvers: new SyncHook(["compiler"]),

  // Called after entry in the Webpack option has been processed
  // Read the configured Entrys and instantiate a corresponding EntryPlugin for each Entry to prepare for recursive parsing of the Entry
  entryOption: new SyncBailHook(["context"."entry"]),};Copy the code

After all the periodic functions are instantiated, _pluginCompat is used to register a Compailer hook that is compatible with the old Webpack plugin. Plugins in tapable/lib/ tapable. Js are called using the following methods in tapable:

// Tapable.js

Tapable.addCompatLayer = function addCompatLayer(instance) {
  Tapable.call(instance);
  instance.plugin = Tapable.prototype.plugin;
  instance.apply = Tapable.prototype.apply;
};

Tapable.prototype.plugin = util.deprecate(function plugin(name, fn) {
  if (Array.isArray(name)) {
    name.forEach(function (name) {
      this.plugin(name, fn);
    }, this);
    return;
  }
  const result = this._pluginCompat.call({
    name: name,
    fn: fn,
    names: new Set([name]),
  });
  if(! result) {throw new Error(
      `Plugin could not be registered at '${name}'. Hook was not found.\n` +
        "BREAKING CHANGE: There need to exist a hook at 'this.hooks'. " +
        "To create a compatibility layer for this hook, hook into 'this._pluginCompat'."); }},"Tapable.plugin is deprecated. Use new API on `.hooks` instead");

Tapable.prototype.apply = util.deprecate(function apply() {
  for (var i = 0; i < arguments.length; i++) {
    arguments[i].apply(this); }},"Tapable.apply is deprecated. Call apply on the plugin directly instead");
Copy the code

This basically does two things: register plugin-compatible hooks _pluginCompat and mount the webpack.plugin registered hooks onto Compiler.hooks; Compiler.hooks mount a stack of Compiler lifecycle hooks.

Compiler mounts several main methods: run, compile, readRecords, etc., which will be described later when run is executed.

3. Mount configuration options and plugins to compiler

// node_modules/webpack/lib/webpack.js

function webpack() {... compiler.options = options;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); }}}... }Copy the code

Mount the extended options to the instance Compiler, and then the plug-in. If the plug-in is a function, this and parameters are the instance Compiler. If the plug-in is an object, the apply method is called to mount the plug-in to the instance Compiler.

4. Add file read, write, and listen capabilities

// node_modules/webpack/lib/webpack.js

function webpack() {...new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging, }).apply(compiler); . }Copy the code

NodeEnvironmentPlugin is a plug-in that extends the Node runtime environment to add file read/write and file listening capabilities to the instance Compiler:

// node_modules/webpack/lib/node/NodeEnvironmentPlugin.js

apply(compiler) {
  // Log
  compiler.infrastructureLogger = createConsoleLogger(
    Object.assign(
      {
        level: "info".debug: false.console: nodeConsole,
      },
      this.options.infrastructureLogging
    )
  );
  // Add file read capability
  compiler.inputFileSystem = new CachedInputFileSystem(
    new NodeJsInputFileSystem(),
    60000
  );

  const inputFileSystem = compiler.inputFileSystem;

  // Add file write capability
  compiler.outputFileSystem = new NodeOutputFileSystem();

  // Add the ability to listen for files (recompile files if they change while reading files)
  compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem);
  // Reset the inputFileSystem before run
  compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin".(compiler) = > {
    if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
  });
}
Copy the code

5. Mount a bunch of built-in and external plug-ins

// node_modules/webpack/lib/webpack.js

function webpack() {... compiler.options =newWebpackOptionsApply().process(options, compiler); . }Copy the code

The key is the handling of entry:

// node_modules/webpack/lib/WebpackOptionsApply.js

/ * * *@param {WebpackOptions} options options object
 * @param {Compiler} compiler compiler object
 * @returns {WebpackOptions} options object
 */
process(options, compiler){...newEntryOptionPlugin().apply(compiler); compiler.hooks.entryOption.call(options.context, options.entry); .return options;
}
Copy the code

Let’s look at what the EntryOptionPlugin does:

// node_modules/webpack/lib/EntryOptionPlugin.js

"use strict";
// Dependency import, there are three main entry file loading methods: single entry, multiple entry, dynamic entry

const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");

/** * Check whether there is a single entry or multiple entries *@param {string} context context path
 * @param {EntryItem} item entry array or single path
 * @param {string} name entry key name
 * @returns {SingleEntryPlugin | MultiEntryPlugin} returns either a single or multi entry plugin
 */
const itemToPlugin = (context, item, name) = > {
  // Array is multi-entry, string is single entry
  if (Array.isArray(item)) {
    return new MultiEntryPlugin(context, item, name);
  }
  return new SingleEntryPlugin(context, item, name);
};

module.exports = class EntryOptionPlugin {
  / * * *@param {Compiler} compiler the compiler instance one is tapping into
   * @returns {void}* /
  apply(compiler) {
    compiler.hooks.entryOption.tap("EntryOptionPlugin".(context, entry) = > {
      // If it is a string or array, the single entry and multiple entries are checked, and the corresponding plug-in instance is generated
      if (typeof entry === "string" || Array.isArray(entry)) {
        itemToPlugin(context, entry, "main").apply(compiler);
      } else if (typeof entry === "object") {
        // If it is an object, the plugin instance will be generated
        for (const name of Object.keys(entry)) { itemToPlugin(context, entry[name], name).apply(compiler); }}else if (typeof entry === "function") {
        // If it is a function, it may be a dynamic import, which generates dynamically imported plug-in instances
        new DynamicEntryPlugin(context, entry).apply(compiler);
      }
      return true; }); }};Copy the code

Calling the Apply method of an EntryOptionPlugin instance registers an EntryOptionPlugin function on the entryOption hook, which is then executed to generate different plug-in instances for different entry files.

In conclusion, we can see that the main work flow of Webpack is: Add file read/write and listening capabilities of compiler. Mount built-in plug-ins and external plug-ins. Finally return an instance of compiler. Calling the Compiler’s run method starts compiling the packaging.