第三章:命令行工具入门
说实话,很多前端开发者对命令行工具又爱又恨。爱的是它确实能提升效率,恨的是总觉得门槛有点高。但你想过没有,我们每天用的 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" 带空格怎么办?如果参数顺序乱了怎么办?
--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 定义放在一个单独的文件里,这样主入口文件不会太臃肿。
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,因为用户点几下就完事了,不用打字。
3.5 避坑指南:我踩过的三个坑
- 参数顺序问题:commander 的
.parse()必须在所有.option()和.command()之后调用。我曾经把.parse()写在中间,结果后面的 option 全没生效。 - inquirer 的异步陷阱:
inquirer.prompt()返回的是 Promise,如果你在commander的.action()里调用,记得用async/await或者.then()。否则程序会直接退出,用户还没选完呢。 - 跨平台路径问题:Windows 用
\,Mac/Linux 用/。永远用path.join()拼接路径,别手写字符串。
3.6 本章小结
说白了,命令行工具就是「接收输入 → 处理逻辑 → 输出结果」的循环。process.argv 是底层 API,commander 帮你封装了参数解析,inquirer 让交互更友好。这三者组合起来,你就能做出像模像样的脚手架了。
下一章,我们会把这些知识串起来,做一个完整的脚手架项目。到时候你会发现——原来那些高大上的 CLI 工具,原理就这么简单。