第三章:命令行工具入门

说实话,很多前端开发者对命令行工具又爱又恨。爱的是它确实能提升效率,恨的是总觉得门槛有点高。但你想过没有,我们每天用的 Vue CLI、Create React App,本质上都是命令行工具。这一章,我就带你亲手打造一个。

3.1 process.argv:最朴素的参数解析

Node.js 里有个隐藏的宝藏——process.argv。它是个数组,存着你在终端输入的所有内容。

// 假设你在终端输入:node index.js init --name=my-project
console.log(process.argv);
// 输出:['node', '/path/to/index.js', 'init', '--name=my-project']

我在刚学 Node 时,就用这个写过一个小工具。当时觉得挺酷,后来才发现——这玩意儿处理复杂参数简直要命。你想想看,如果用户输入 --name "my project" 带空格怎么办?如果参数顺序乱了怎么办?

我曾经踩过的坑:直接用 process.argv 做参数校验,结果用户传了 --name=my project(注意等号两边没空格),我的解析逻辑直接崩了。从那以后,我明白了——手写解析器,除非你闲得慌。

3.2 commander:命令行界的瑞士军刀

既然手写不靠谱,那就用社区方案。commander 是我最常用的库,没有之一。它帮你处理参数解析、帮助信息、版本号,甚至子命令。

// 安装:npm install commander
const { program } = require('commander');

program
  .version('1.0.0')
  .description('我的脚手架工具')
  .option('-n, --name <name>', '项目名称')
  .option('-t, --template <template>', '模板类型', 'vue')
  .parse(process.argv);

console.log('项目名称:', program.name);
console.log('模板类型:', program.template);

你看,代码瞬间清爽了。commander 会自动生成 --help 信息,还会校验必填参数。我个人习惯把 option 定义放在一个单独的文件里,这样主入口文件不会太臃肿。

小技巧:commander 支持链式调用,你可以把 .version()、.option()、.action() 串起来写。但别串太长,超过 5 个链式调用就该考虑拆分了。

3.3 实战:搭建一个简易脚手架

光说不练假把式。我们来写一个真实的脚手架——它接受项目名称和模板类型,然后创建对应的目录结构。

const { program } = require('commander');
const fs = require('fs');
const path = require('path');

program
  .command('create <project-name>')
  .description('创建一个新项目')
  .option('-t, --template <type>', '模板类型:vue/react', 'vue')
  .action((projectName, options) => {
    const targetDir = path.join(process.cwd(), projectName);
    
    if (fs.existsSync(targetDir)) {
      console.error(`目录 ${projectName} 已存在!`);
      process.exit(1);
    }

    fs.mkdirSync(targetDir);
    console.log(`✅ 项目 ${projectName} 创建成功`);
    console.log(`📦 模板类型:${options.template}`);
  });

program.parse(process.argv);

嗯,这里要注意:process.exit(1) 会直接终止进程。我在项目中遇到过用户误操作,所以加了目录存在性检查。你想想看,如果用户不小心覆盖了已有项目,那得多崩溃。

3.4 inquirer:让命令行学会聊天

commander 解决了参数解析,但交互体验还不够。比如你想让用户选择模板类型,或者确认是否覆盖已有文件。这时候 inquirer 就派上用场了。

const inquirer = require('inquirer');

const questions = [
  {
    type: 'input',
    name: 'projectName',
    message: '请输入项目名称:',
    validate: (input) => input.trim() ? true : '项目名称不能为空'
  },
  {
    type: 'list',
    name: 'template',
    message: '请选择模板类型:',
    choices: ['Vue', 'React', 'Angular'],
    default: 'Vue'
  },
  {
    type: 'confirm',
    name: 'confirmOverwrite',
    message: '目录已存在,是否覆盖?',
    default: false
  }
];

inquirer.prompt(questions).then(answers => {
  console.log('你的选择:', answers);
});

inquirer 支持的类型很多:input(输入)、list(单选)、checkbox(多选)、confirm(确认)。我个人最常用的是 list 和 confirm,因为用户点几下就完事了,不用打字。

核心要点:commander 负责「命令行参数」,inquirer 负责「交互式问答」。两者配合使用,才能做出真正好用的脚手架工具。

3.5 避坑指南:我踩过的三个坑

  1. 参数顺序问题:commander 的 .parse() 必须在所有 .option().command() 之后调用。我曾经把 .parse() 写在中间,结果后面的 option 全没生效。
  2. inquirer 的异步陷阱:inquirer.prompt() 返回的是 Promise,如果你在 commander.action() 里调用,记得用 async/await 或者 .then()。否则程序会直接退出,用户还没选完呢。
  3. 跨平台路径问题:Windows 用 \,Mac/Linux 用 /。永远用 path.join() 拼接路径,别手写字符串。

3.6 本章小结

说白了,命令行工具就是「接收输入 → 处理逻辑 → 输出结果」的循环。process.argv 是底层 API,commander 帮你封装了参数解析,inquirer 让交互更友好。这三者组合起来,你就能做出像模像样的脚手架了。

下一章,我们会把这些知识串起来,做一个完整的脚手架项目。到时候你会发现——原来那些高大上的 CLI 工具,原理就这么简单。