likes
comments
collection
share

从 npm run build 学习 webpack(上)

作者站长头像
站长
· 阅读数 2

在一个 webpack 构建的项目中,npm run build作为前端常用的打包命令,可以在 package.jsonscripts 中看到是在执行 webpack 命令。

webpack 命令是如何被 npm 识别并执行的?

通过 npm init 命令初始化一个项目时,会生成一个 package.json 文件。在文件中提供一个 bin 字段,它是命令名到本地文件的映射。以便在 scripts 中以命令名调用,可以链接到可用的位置。

接着在 webpackpackage.json 中,可以看到 webpack 命令对应的可执行文件 bin/webpack.js

从 npm run build 学习 webpack(上)

打开文件可以看出定义了三个工具函数,还有一个常量 cli 对象。然后执行判断

从 npm run build 学习 webpack(上)

判断中最后都执行了 runCli 函数。只不过在检测到 cli 没安装时候,会做一些校验,提示安装等逻辑(先忽略)。最后都会执行 runCli 函数。

从 npm run build 学习 webpack(上)

runCli 函数通过 require.resolve 获取 webpack-cli 依赖的 package.json 文件绝对路径。然后导入整个 package.json 文件,赋值给 pkg。然后判断是否是 ESModule,是就使用 import 导入,否则使用 require。pkg.bin 就是 webpack-clipackage.jsonbin 字段。然后通过 cli.binName 也就是 webpack-cli,来获取 webpack-cli 的可执行文件。****

const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};

const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`);
	const pkg = require(pkgPath);

	if (pkg.type === "module" || /.mjs/i.test(pkg.bin[cli.binName])) {
		// eslint-disable-next-line n/no-unsupported-features/es-syntax
		import(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])).catch(
			error => {
				console.error(error);
				process.exitCode = 1;
			}
		);
	} else {
		require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
	}
};

webpack-clipackage.json 中看到,可执行文件为 cli.js

从 npm run build 学习 webpack(上)

cli.js 中,运行命令时没有设置自定义字段 WEBPACK_CLI_SKIP_IMPORT_LOCAL 就是 undefined,然后 importLocal 如果加载本地 cli 会返回 false,最后就执行了 runCli

const importLocal = require("import-local");
const runCLI = require("../lib/bootstrap");

if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) {
  // Prefer the local installation of `webpack-cli`
  if (importLocal(__filename)) {
    return;
  }
}

process.title = "webpack";

runCLI(process.argv);

../lib/bootstraprunCLI 函数,实例化 WebapckCLI,执行 cli.run 方法

Object.defineProperty(exports, "__esModule", { value: true });
// eslint-disable-next-line @typescript-eslint/no-var-requires
const WebpackCLI = require("./webpack-cli");
const runCLI = async (args) => {
    // Create a new instance of the CLI object
    const cli = new WebpackCLI();
    try {
        await cli.run(args);
    }
    catch (error) {
        cli.logger.error(error);
        process.exit(2);
    }
};
module.exports = runCLI;

实例化时

  1. createColors 根据中断是否支持彩色,返回一个对象,包含生成各种颜色的 ANSI 码。
  2. getLogger 返回一个对象,包含 error、warn、info、success等日志函数,日志前缀增加 [webpack-cli]
  3. this.program = program; 这里将commander的实例赋值给this.program commander是一个轻量级的,表达式丰富的命令行框架,它可以帮助你快速构建命令行应用。
  4. this.program.name("webpack"); :这个方法设置了CLI的名称为webpack。在帮助信息中,这个名称会被显示出来,告诉用户这个CLI的名称。
  5. this.program.configureOutput({...}); :这个方法用于配置命令行输出的行为。它接受一个对象作为参数,这个对象可以有writeOutwriteErroutputError等属性,用于自定义标准输出、错误输出和错误处理。
    • writeErr: this.logger.error, :这里指定了当有错误需要写入标准错误输出时,使用this.logger.error方法。this.loggerwebpack-cli自定义的日志记录器,它会对错误信息进行格式化或添加前缀等处理。
    • outputError: (str, write) => write( Error: ${this.capitalizeFirstLetter(str.replace(/^error:/, "").trim())} ), :这个函数用于自定义错误信息的输出格式。它首先使用正则表达式 /^error:/ 来移除错误字符串前面的error:前缀(如果有的话),然后调用this.capitalizeFirstLetter方法将错误信息的第一个字母大写,最后在前面加上Error: 前缀,并通过write函数输出。这个write函数实际上是commander内部处理输出的方法。
constructor() {
    this.colors = this.createColors();
    this.logger = this.getLogger();
    // Initialize program
    this.program = program;
    this.program.name("webpack");
    this.program.configureOutput({
        writeErr: this.logger.error,
        outputError: (str, write) => write(`Error: ${this.capitalizeFirstLetter(str.replace(/^error:/, "").trim())}`),
    });
}

接下来就是最重要的 run 方法:整体上就是定义一些 操作命令 常数,然后对 cli 做基础操作配置。this.program.action方法就是对命令的回调处理。

从 npm run build 学习 webpack(上)

展开this.program.action,当只执行 webpack 时,没有其他参数执行默认 build 命令,最终执行 loadCommanByName 函数

从 npm run build 学习 webpack(上)

loadCommanByName 函数判断是 build 命令,执行 makeCommand 传入 build 操作配置,以及回调函数。

从 npm run build 学习 webpack(上)

makeCommand 函数。注册 build 命令,检测并安装所需依赖 webpack,然后执行第二个参数,构建 webpack 所需 option。最后使用第三个参数作为 action,处理 build 命令。

从 npm run build 学习 webpack(上)

  • 第二个参数:加载 webpack,构建 webpack 所需的 option
this.webpack = await this.loadWebpack();
return this.getBuiltInOptions();
  • 第三个参数:通过 runWebpack 执行 webpack,处理 build
if (entries.length > 0) {
    options.entry = [...entries, ...(options.entry || [])];
}
await this.runWebpack(options, isWatchCommandUsed);

下面是整个过程的流程图

从 npm run build 学习 webpack(上)