Node.js 判断主模块模式 (isMain Pattern)
在 Node.js ES 模块中,判断当前文件是否作为主程序直接运行是一个常见需求。
代码示例
import { fileURLToPath } from 'node:url';
const isMain =
process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isMain) {
program.parseAsync(process.argv);
}
原理解析
1. process.argv[1]
process.argv是 Node.js 的命令行参数数组[0]: Node.js 可执行文件路径[1]: 被执行的入口文件路径(绝对路径)- 例如运行
node src/index.ts,process.argv[1]是/project/src/index.ts
2. import.meta.url
- ES 模块提供的元数据属性
- 返回当前模块的 URL 格式路径
- 示例:
file:///Users/suishi/project/src/index.ts
3. fileURLToPath()
- Node.js
url模块的函数 - 将
file://URL 转换为本地文件系统路径 - 示例转换:
file:///Users/.../index.ts→/Users/.../index.ts
4. 比较逻辑
fileURLToPath(import.meta.url) === process.argv[1]
| 场景 | 结果 | 说明 |
|---|---|---|
node src/index.ts | true | 当前文件是入口文件 |
import { x } from './index' | false | 当前文件被导入使用 |
5. 短路判断 process.argv[1] &&
- 防止
process.argv[1]为undefined时出错 - 确保安全性,避免空值比较
实际用途
if (isMain) {
program.parseAsync(process.argv); // 启动 CLI
}
| 使用方式 | 行为 |
|---|---|
| 直接运行 | 启动 CLI 程序,解析命令行参数 |
| 作为模块导入 | 只导出功能,不执行 CLI 逻辑 |
优势
- 双重用途:同一个文件既是可执行脚本,又是可导入的模块库
- 测试友好:导入模块进行单元测试时不会意外启动程序
- 代码复用:逻辑和入口可以写在同一个文件中
对比 CommonJS
在 CommonJS 中,使用 require.main === module 来判断:
// CommonJS 写法
if (require.main === module) {
main();
}
// ES Module 写法
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
main();
}
完整示例
#!/usr/bin/env node
import { Command } from 'commander';
import { fileURLToPath } from 'node:url';
const program = new Command();
program
.name('my-cli')
.description('示例 CLI 工具')
.version('1.0.0');
program
.command('hello')
.description('打招呼')
.action(() => {
console.log('Hello World!');
});
// 导出供其他模块使用
export { program };
// 作为主程序运行时启动 CLI
const isMain =
process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isMain) {
program.parseAsync(process.argv);
}