webpack.js 也就是webpack 入口文件

117 阅读2分钟

webpack.js 这是webpack 最早运行的文件,会查看是否安装 webpack 套件。然后对用户进行引导。

runCommand(command, args)

作用
使用子进程运行指定命令(如 npm install),并返回一个 Promise 来处理异步执行结果。 参数

  • command:要执行的命令(如 "npm")。
  • args:命令参数数组(如 ["install", "webpack-cli"])。 返回值
    一个 Promise,在命令成功时 resolve,失败时 reject。 实现要点
  • 使用 child_process.spawn 启动命令。
  • stdio: "inherit" 让子进程输入输出显示在主终端中。
  • shell: true 允许命令在 shell 环境中运行(兼容 Windows 和 Linux)。
#!/usr/bin/env node
/**
 * @param {string} command - 要执行的命令
 * @param {string[]} args - 命令行参数
 * @returns {Promise<void>} - 返回 Promise 对象,表示命令执行结果
 */
const runCommand = (command, args) => {
	const cp = require("child_process"); // 引入子进程模块
	return new Promise((resolve, reject) => {
		// 创建子进程,执行命令
		const executedCommand = cp.spawn(command, args, {
			stdio: "inherit", // 将子进程的标准输入输出继承主进程
			shell: true // 允许在 shell 环境中执行命令
		});

		// 监听子进程错误事件
		executedCommand.on("error", error => {
			reject(error);
		});

		// 监听子进程退出事件
		executedCommand.on("exit", code => {
			if (code === 0) {
				resolve(); // 退出码为 0 表示成功
			} else {
				reject(); // 非 0 表示失败
			}
		});
	});
};

isInstalled(packageName)

作用
判断某个 npm 包是否已安装。 参数

  • packageName:包名(如 "webpack-cli")。 返回值
    true 表示已安装,false 表示未安装。 实现要点
  • 优先判断是否使用了 Yarn PnP(Plug'n'Play),若是则直接返回已安装。
  • 向上遍历目录树查找 node_modules/packageName 是否存在。
  • 同时兼容全局模块目录(module.globalPaths)。

/**
 * @param {string} packageName - 包名
 * @returns {boolean} - 检查包是否安装
 */
const isInstalled = packageName => {
	if (process.versions.pnp) {
		return true; // 支持 PnP(Yarn Plug'n'Play)的环境直接返回已安装
	}

	const path = require("path");
	const fs = require("graceful-fs"); // 兼容文件系统模块,避免某些文件系统错误

	let dir = __dirname; // 当前脚本所在目录

	// 向上遍历目录树,查找 node_modules 是否存在指定包
	do {
		try {
			if (fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()) {
				return true;
			}
		} catch (_error) {
			// 忽略错误
		}
	} while (dir !== (dir = path.dirname(dir))); // 向上遍历直到根目录

	// 兼容全局模块路径
	for (const internalPath of require("module").globalPaths) {
		try {
			if (fs.statSync(path.join(internalPath, packageName)).isDirectory()) {
				return true;
			}
		} catch (_error) {
			// 忽略错误
		}
	}

	return false;
};

runCli(cli)

作用
根据 CLI 配置加载并执行对应的入口文件。 参数

  • cli:CLI 配置对象(包含包名、bin 名、是否已安装等信息)。 实现要点
  • 使用 require.resolve 获取 CLI 包的 package.json 路径。
  • 判断该 CLI 是 ES Module 还是 CommonJS:
    • ES Module:用 import() 动态引入。
    • CommonJS:用 require() 加载执行。
/**
 * 运行 CLI
 * @param {CliOption} cli - CLI 选项对象
 */
const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`); // 查找包路径
	const pkg = require(pkgPath); // 读取 package.json

	// 判断包类型,支持 ES Module 和 CommonJS
	if (pkg.type === "module" || /\.mjs/i.test(pkg.bin[cli.binName])) {
                    import(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])).catch(err => {
                            console.error(err);
                            process.exitCode = 1;
                    });
            } else {
                    require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
            }
    };

    /**
     * @typedef {object} CliOption
     * @property {string} name - CLI 名称
     * @property {string} package - 包名
     * @property {string} binName - 可执行文件名
     * @property {boolean} installed - 是否已安装
     * @property {string} url - 包的主页
     */

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

    if (!cli.installed) {
            const path = require("path");
            const fs = require("graceful-fs");
            const readLine = require("readline");

            console.error(`CLI for webpack must be installed.\n  ${cli.name} (${cli.url})\n`);

            let packageManager;

            // 根据锁文件推断包管理工具
            if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {
                    packageManager = "yarn";
            } else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {
                    packageManager = "pnpm";
            } else {
                    packageManager = "npm";
            }

            const installOptions = [packageManager === "yarn" ? "add" : "install", "-D"];

            console.error(`We will use "${packageManager}" to install the CLI.`);
            // 通过 readLine 创建一个询问命令
            const questionInterface = readLine.createInterface({
                    input: process.stdin,
                    output: process.stderr
            });

            process.exitCode = 1; // 默认失败退出码
            // 在命令行询问是否安装 webpack
            questionInterface.question("Do you want to install 'webpack-cli' (yes/no): ", answer => {
                    // 关闭询问
                    questionInterface.close();
                    // 如果在询问输入中存在y
                    if (answer.toLowerCase().startsWith("y")) {
                            process.exitCode = 0;
                            console.log(`Installing '${cli.package}'...`);
                            // 安装webpack 工具套件
                            runCommand(packageManager, installOptions.concat(cli.package))
                                    .then(() => runCli(cli))
                                    .catch(err => {
                                            console.error(err);
                                            process.exitCode = 1;
                                    });
                    } else {
                            console.error("You need to install 'webpack-cli' to use webpack via CLI.");
                    }
            });
    } else {
            runCli(cli);
    }
}