深入webpack-cli底层一探究竟

802 阅读1分钟

入口文件

通过 package.json 文件,找到了入口文件 ./lib/index.js 和 命令注册文件 ./bin/cli.js

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

在 index.js 中,module.exports 抛出了两个方法 utils 和 CLI;

在 cli.js 中,核心代码就是 require("../lib/bootstrap"),然后执行 runCLI 方法;

在cli.js 中引入了第三方依赖库 import-local

当全局node_modules和本地node_modules中存在相同的库时,优先使用本地node_modules中的版本

runCLI 方法

  1. 通过require("./webpack-cli"),引入WebpackCLI;

  2. new WebpackCLI(),实例化CLI对象;

    • 将utils方法挂载到this.utils上;

    • 初始化 commander 的 program,并挂载到 this.program 上;

        this.logger = utils.logger;
        this.utils = utils;
    
        // Initialize program
        this.program = program;
        this.program.name("webpack");
        this.program.configureOutput({
            writeErr: this.logger.error,
            outputError: (str, write) =>
            write(`Error: ${this.utils.capitalizeFirstLetter(str.replace(/^error:/, "").trim())}`),
        });
    
  3. 执行cli的run方法;

run 方法

  1. 配置 program 的 option 选项,同时添加监听命令和选项的自定义函数
this.program.option("--color", "Enable colors on console.");
this.program.on("option:color", function () {
    ...
});
this.program.option("--no-color", "Disable colors on console.");
this.program.on("option:no-color", function () {
    ...
});

this.program.option(
    "-v, --version",
    "Output the version number of 'webpack', 'webpack-cli' and 'webpack-dev-server' and commands.",
);

this.program.option("-h, --help [verbose]", "Display help for commands and options.");
  1. 重写退出和输出
this.program.exitOverride(async (error) => {
    if (error.exitCode === 0) {
        process.exit(0);
    }

    if (error.code === "executeSubCommandAsync") {
        process.exit(2);
    }

    if (error.code === "commander.help") {
        process.exit(0);
    }
    ...
    this.logger.error("Run 'webpack --help' to see available commands and options");
    process.exit(2);
});
  1. 异步解析命令行的字符串数组,然后执行 action 事件
    this.program.parseAsync(args, parseOptions)
    this.program.action(async (options, program) => {})
  1. 通过命令行的命令名称,来动态加载commander命令,然后在 program.action 的回调中执行makeCommand方法
    loadCommandByName(commandToRun, true);
    ...
    this.makeCommand(
        isBuildCommandUsed ? buildCommandOptions : watchCommandOptions,
        async () => {
            this.webpack = await this.loadWebpack();

            return isWatchCommandUsed
                ? this.getBuiltInOptions().filter((option) => option.name !== "watch")
                : this.getBuiltInOptions();
        },
        async (entries, options) => {
            ...
            await this.runWebpack(options, isWatchCommandUsed);
        },
    )
  1. 判断命令是否已经加载,如果已经加载则返回,如果没有加载则动态创建命令
    const command = this.program.command(commandOptions.name, {
        noHelp: commandOptions.noHelp,
        hidden: commandOptions.hidden,
        isDefault: commandOptions.isDefault,
    });
    ...
    command.action(action);
  1. 在command回调中先通过 loadWebpack 方法加载了 webpack,然后执行了 runWebpack 方法
    this.runWebpack(options, isWatchCommandUsed);
  1. 在runWebpack中,创建webpack的complier
    compiler = this.webpack(
        config.options,
        callback
            ? (error, stats) => {
                ...
                callback(error, stats);
            }
            : callback,
    );

    this.createCompiler(options, callback);
  1. 进行webpack打包
    webpack(config).run((err, stats) => {})