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()加载执行。
- ES Module:用
/**
* 运行 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);
}
}