小龙虾学习基础知识2. isMain Pattern

6 阅读2分钟

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.tsprocess.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.tstrue当前文件是入口文件
import { x } from './index'false当前文件被导入使用

5. 短路判断 process.argv[1] &&

  • 防止 process.argv[1]undefined 时出错
  • 确保安全性,避免空值比较

实际用途

if (isMain) {
  program.parseAsync(process.argv);  // 启动 CLI
}
使用方式行为
直接运行启动 CLI 程序,解析命令行参数
作为模块导入只导出功能,不执行 CLI 逻辑

优势

  1. 双重用途:同一个文件既是可执行脚本,又是可导入的模块库
  2. 测试友好:导入模块进行单元测试时不会意外启动程序
  3. 代码复用:逻辑和入口可以写在同一个文件中

对比 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);
}

参考资料