Analysis of webpack source code (1) — Analysis of webpack startup process

Time:2021-5-11

The article first appeared on my bloghttps://github.com/mcuking/bl…

In this paper, we use webpack source code to analyze its internal workflow, and the version to be analyzed is 4.41.5.

First of all, we need to confirm the execution entry file of webpack_ The bin field of package.json of webpack in modules (as shown below). We can know that the entry file is webpack.js under bin file, that isnode_modules\webpack\bin\webpack.js

"bin": {
  "webpack": "./bin/webpack.js"
},

Analyze the webpack entry file: webpack.js

There are 171 lines in the file, which are mainly divided into six parts

  1. Normal execution return
process.exitCode = 0;
  1. Defines a method runcommand to run a command
const runCommand = (command, args) => {...};
  1. This paper defines a method isinstalled to determine whether a package is installed
const isInstalled = packageName => {
    try {
        require.resolve(packageName);

        return true;
    } catch (err) {
        return false;
    }
};
  1. Two available CLIS for webpack are defined: webpack CLI and webpack command. The installed property is calculated by calling the isinstalled method above.
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."
    }
];
  1. Then calculate the installed cli
const installedClis = CLIs.filter(cli => cli.installed);
  1. It is then processed according to the number of CLIS installed
if (installedClis.length === 0)  {...}
else if(installedClis.length === 1) {...}
else {...}

If none of them are installed, you will be prompted whether you want to install webpack cli. If you agree, it will be automatically installed for you; If one of them is installed, it will be used directly; If two are installed, you will be prompted to remove one of the CLIS.

Through the above analysis, we can confirm that webpack will eventually find the NPM package webpack cli or webpack command and execute the CLI.

So let’s take a look at what webpack cli has done.

Analysis of webpack cli operation mechanism

The current version of webpack cli is 3.3.10. Through the bin field of package.json in the webpack cli package, we can confirm that the entry execution file of webpack cli isnode_modules\webpack-cli\bin\cli.js

"bin": {
    "webpack-cli": "./bin/cli.js"
},

Then we continue to analyze what cli.js has done. Through a general review, we can confirm that the business logic of webpack cli is not complex. The main file is cli.js, and the rest are utils or config files. The code of cli.js is only 366 lines. Now let’s analyze exactly what has been done in cli.js.

First of all, the user input parameters are classified at the beginning, and it is judged that some of the parameters are in non_ COMPILATION_ In args, call the method exported by default from prompt command file in utils, and pass the command and input parameters into the method.

const NON_COMPILATION_CMD = process.argv.find(arg => {
    if (arg === "serve") {
        global.process.argv = global.process.argv.filter(a => a !== "serve");
        process.argv = global.process.argv;
    }
    return NON_COMPILATION_ARGS.find(a => a === arg);
});

if (NON_COMPILATION_CMD) {
    return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
}

It can be understood that some commands of webpack cli do not need to be compiled, that is, to initialize a compiler object. So let’s look at the commands that don’t need to be compiled, and what they do.

const NON_COMPILATION_ARGS = [
  "Init", // create a webpack configuration file
  "Migrate", // migrate the web pack version
  "Serve", // run webpack serve
  "Generate loader" // generate webpack loader code
  "Generate plugin" // generate webpack plugin code
  "Info" // returns some information related to the local environment
];

And then we’ll see./utils/prompt-commandThe relevant code is as follows:

module.exports = function promptForInstallation(packages, ...args) {
    try {
        const path = require("path");
        const fs = require("fs");
        pathForCmd = path.resolve(process.cwd(), "node_modules", "@webpack-cli", packages);
        if (!fs.existsSync(pathForCmd)) {
            const globalModules = require("global-modules");
            pathForCmd = globalModules + "/@webpack-cli/" + packages;
            require.resolve(pathForCmd);
        } else {
            require.resolve(pathForCmd);
        }
        packageIsInstalled = true;
    } catch (err) {
        packageIsInstalled = false;
    }
    if (!packageIsInstalled) {...}
}

After the promptforinstallation method receives the command, it will determine whether the package corresponding to the command has been installed, for example, whether the init package exists in the @ webpack cli folder. If it does not exist, it will prompt to install and run after installation. If it exists, it will run directly.

Next, let’s go back to the mainstream process and take a look at the logic behind cli. JS.

const yargs = require("yargs").usage(`webpack-cli ${require("../package.json").version}

require("./config/config-yargs")(yargs);

yargs.parse(process.argv.slice(2), (err, argv, output) => {...})

Default export method of. / config / config yargs file

module.exports = function(yargs) {
    yargs
        .help("help")
        .alias("help", "h")
        .version()
        .alias("version", "v")
        .options({
            config: {
                type: "string",
                describe: "Path to the config file",
                group: CONFIG_GROUP,
                defaultDescription: "webpack.config.js or webpackfile.js",
                requiresArg: true
            },
            ...
        );
}

You can see that webpack cli is usedyargsTool to build an interactive command-line tool, which can provide commands and grouping parameters, and can dynamically generate help information. The function of the method in the config yargs file is to configure yargs.

Next, let’s go back to cli. JS and see what’s done in the yargs. Parse method.

yargs.parse(process.argv.slice(2), (err, argv, output) => {
    ...
    try {
        options = require("./utils/convert-argv")(argv);
    } catch(e) {
        ...
    }
    ...
})

We can see that next is the call./utils/convert-argvFile, and passed in input parameters. Then look./utils/convert-argvWhat does this method do?

module.exports = function(...args) {
    if (argv.config) {} else {
        const defaultConfigFileNames = ["webpack.config", "webpackfile"].join("|");
        const webpackConfigFileRegExp = `(${defaultConfigFileNames})(${extensions.join("|")})`;
        const pathToWebpackConfig = findup(webpackConfigFileRegExp);
        ...
    }
}

Due to the large amount of code, I just excerpt some key code here. It is not difficult to find that this method is mainly used to generate configuration items of webpack, such as output, etc., while the source mainly includes command line input and files such as webpack.config.js.

Back to cli.js, when the configuration options are generated, what do you do next?

yargs.parse(process.argv.slice(2), (err, argv, output) => {
    ...
    function processOptions(options) {
        let compiler;
        try {
            compiler = webpack(options);
        } catch (err) {
            ...
        }

        if (firstOptions.watch || options.watch) {
            const watchOptions =
                firstOptions.watchOptions || options.watchOptions || firstOptions.watch || options.watch || {};
            ...
            compiler.watch(watchOptions, compilerCallback);
            ...
        } else {
            compiler.run((err, stats) => {
                if (compiler.close) {
                    compiler.close(err2 => {
                        compilerCallback(err || err2, stats);
                    });
                } else {
                    compilerCallback(err, stats);
                }
            });
        }
    }
    processOptions(options)
})

As we can see, the processoptions method is called in the last stage, in which a compiler instance object of webpack is obtained (we will introduce the compiler of webpack later), and whether the current watch mode is determined. If yes, the watch method on the compiler instance is called to run, otherwise, the run method is called to run, That’s the beginning of the real packaging build process.

In summary, the main functions of webpack cli are as follows:

  1. Yargs is introduced to provide an interactive command line tool
  2. The configuration file and command line parameters are converted to generate configuration option parameters;
  3. Then instantiate the webpack object according to the configuration, and execute the build process.

In the next article, we will continue to analyze the construction mechanism in webpack.

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]