命令行應(yīng)用開發(fā)初學(xué)者指南:腳手架篇、UI 庫和交互工具
在日常的前端開發(fā)工作中,我們經(jīng)常依賴各種命令行工具來提高效率和代碼質(zhì)量。例如,create-react-app 和 eslint 等工具不僅簡化了項(xiàng)目的初始化過程,還能自動執(zhí)行代碼檢查和格式化任務(wù)。當(dāng)我們使用這些工具時,它們通常會通過一系列互動式的問答來收集必要的信息,從而根據(jù)我們的選擇進(jìn)行相應(yīng)的配置和安裝。
以 eslint 工具為例(如下圖所示),當(dāng)你首次運(yùn)行 eslint --init 命令時,它會引導(dǎo)你完成一系列選擇題,包括你使用的框架(如 React、Vue.js 或其他),以及其他配置選項(xiàng)。通過這種方式,eslint 能夠?yàn)槟闵梢粋€最適合項(xiàng)目需求的配置文件。

本篇文章將介紹在開發(fā)命令行工具過程中常用的第三方庫。這些庫主要分為三類:
-
腳手架框架:用于解析命令行參數(shù),例如
eslint --init中的 --init。常用的腳手架框架有yargs和command。 -
命令行輸出美化庫:基于
ANSI Escape規(guī)范,用于對命令行輸出進(jìn)行顏色和樣式美化。常用的美化庫有chalk和ora。 -
交互式命令行庫:用于創(chuàng)建交互式的命令行界面,例如
eslint初始化過程中會提出的問答,如 "Which framework does your project use?"。常用的交互式命令行庫有inquirer。
腳手架框架
首先,讓我們簡單回顧一下 Node.js 腳手架的開發(fā)流程:
- 創(chuàng)建 npm 項(xiàng)目:使用
npm init命令創(chuàng)建一個新的 npm 項(xiàng)目,并填寫相關(guān)項(xiàng)目信息。 - 創(chuàng)建腳手架入口文件:在項(xiàng)目根目錄下創(chuàng)建一個入口文件,例如 index.js,并在文件頂部添加
#!/usr/bin/env node以便將其識別為可執(zhí)行文件。 - 配置 package.json:在
package.json文件中添加bin屬性,指定腳手架的入口文件路徑。 - 添加 npm link:使用
npm link命令將項(xiàng)目鏈接到全局環(huán)境中,這樣就可以在本地通過短指令訪問腳手架。
詳細(xì)的創(chuàng)建及實(shí)現(xiàn)功能邏輯可以參考文章《Node.js 構(gòu)建命令行工具:實(shí)現(xiàn) ls 命令的 -a 和 -l 選項(xiàng)》
例如,假設(shè)我們創(chuàng)建了一個名為 ice-cli 的項(xiàng)目,并在其中添加了 --init 指令的執(zhí)行邏輯。那么,我們?nèi)绾沃烙脩糨斎肓诉@項(xiàng)指令呢?這就需要我們在項(xiàng)目中解析命令行參數(shù)。
自行解析參數(shù)
在 Node.js 中,我們可以利用內(nèi)置的 process 對象來解析命令行參數(shù)。具體來說,可以在入口文件 index.js 中執(zhí)行以下代碼:
const argv = require("process").argv;
console.log("argv", argv);
當(dāng)用戶在命令行中輸入 ice-cli create project --help 這一長串指令后,通過 process.argv 獲取到的是一個數(shù)組。數(shù)組的第一個元素代表 Node.js 的執(zhí)行路徑,第二個元素代表當(dāng)前指令文件的路徑,從第三個元素開始則是用戶輸入的內(nèi)容。
例如,對于命令 ice-cli create project --help,process.argv 的輸出可能如下所示:
[
'/usr/local/bin/node', // Node.js 執(zhí)行路徑
'/usr/local/lib/node_modules/ice-cli/index.js', // 當(dāng)前指令文件路徑
'create', // 用戶輸入的第一個參數(shù)
'project', // 用戶輸入的第二個參數(shù)
'--help' // 用戶輸入的第三個參數(shù)
]
拿到用戶輸入的內(nèi)容后,我們需要對其進(jìn)行進(jìn)一步的拆分和處理。用戶輸入的內(nèi)容通常包括 命令(command) 和 選項(xiàng)(options)。例如,在命令 webpack config ./webpack.config.js 中,config 是命令,./webpack.config.js 是命令后面的參數(shù);而在命令 webpack --help 中,--help 是選項(xiàng)。
通過解析 process.argv 數(shù)組,我們可以提取出命令和選項(xiàng),并根據(jù)它們執(zhí)行相應(yīng)的邏輯。例如:
const command = argv[2]; // 獲取命令
const args = argv.slice(3); // 獲取命令后面的參數(shù)
if (command === 'create') {
if (args.includes('--help')) {
console.log('Usage: ice-cli create <project-name>');
} else {
const projectName = args[0];
console.log(`Creating project ${projectName}...`);
}
}
在日常開發(fā)中,我們通常不會自己去解析命令行參數(shù),因?yàn)檫@涉及到大量的邊界情況和錯誤處理。使用社區(qū)廣泛認(rèn)可的第三方庫可以更加高效和嚴(yán)謹(jǐn)。其中,yargs 和 commander 是兩個非常優(yōu)秀的推薦庫。
yargs
yargs 是一個功能強(qiáng)大且易于使用的命令行參數(shù)解析庫。它提供了豐富的 API,可以幫助你輕松地解析命令和選項(xiàng),并生成詳細(xì)的幫助信息。
安裝 yargs
首先,通過 npm 安裝 yargs:
npm install yargs
實(shí)現(xiàn) --help 和 --version 功能
通過簡單的代碼就可以實(shí)現(xiàn) --help 和 --version 功能:
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");
const arg = hideBin(process.argv);
yargs(arg).argv;
運(yùn)行 ice-cli --help ,結(jié)果如下圖所示:

常用屬性
yargs 采用鏈?zhǔn)秸{(diào)用的方式為命令設(shè)置屬性。以下是一些常用的屬性:
- usage() :在輸入 --help 時會顯示的提示信息。
- demandCommand() :最少要輸入的命令數(shù)量,以及當(dāng)沒有輸入命令時的提示。
- recommendCommands() :如果輸入的指令不完整,會給出最近似命令的提示,例如:“Did you mean xx?”
- strict() :嚴(yán)格模式,輸入錯誤命令時會給出提示。
- alias() :為指令取別名。
- options() :定義多個全局選項(xiàng),在任何場景都可以訪問到。
- option() :定義單個全局選項(xiàng),在任何場景都可以訪問到。
- group() :將一些命令聚合到一個分類中。
- command() :定義指令。
- epilogue() :定義結(jié)尾信息。
示例代碼
將上述命令組合起來,示例如下:
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");
const arg = hideBin(process.argv);
const cli = yargs(arg);
cli
.usage("Usage: ice-ls [command] <options>")
.demandCommand(
1,
"A command is required. Pass --help to see all avaiable commands and options."
)
.recommendCommands()
.strict()
.alias("h", "help")
.options({
debug: {
type: "boolean",
describe: "Bootstarap debug mode",
alias: "d",
},
})
.group(["debug"], "Dev options:")
.command({
command: "list",
aliases: ["ls", "la", "ll"],
describe: "List total packages",
builder: (argv) => {
console.log("builder", argv);
},
handler: (argv) => {
console.log("handler", argv);
},
})
.epilogue("You own footer description").argv;
當(dāng)執(zhí)行 ice-ls 命令時,輸出如下:
- 第一行出現(xiàn)
usage函數(shù)配置的提示:Usage: ice-ls [command]。 - 接著是
command函數(shù)配置的指令 list,以及它的別名 ls, la, ll。 - 然后是通過
group函數(shù)分組的 Dev options。 - 下面是
yargs默認(rèn)提供的選項(xiàng) --version 和 --help。 - 接著是
epilogue函數(shù)配置的尾部描述。 - 最后一行是
demandCommand提示: A command is required. Pass --help to see all avaiable commands and options。因?yàn)閳?zhí)行命令 ice-ls 的時候沒有提供具體指令。
執(zhí)行 ice-cli list 和 ice-ls list --debug,此時程序進(jìn)入 command 函數(shù)中,執(zhí)行 builder 函數(shù) 和 handler 函數(shù),可以在這里編寫實(shí)際的功能邏輯。

commander
commander 也是一個功能強(qiáng)大的命令行參數(shù)解析庫,但它在使用方式和 API 設(shè)計(jì)上和 yargs 有一些差異。
安裝 commander
首先,通過 npm 安裝 commander:
npm install commander
實(shí)現(xiàn) --help 和 --version 功能
commander 通過簡單的配置就可以生成 usage 提示以及 --help 和 --version 功能。
const commander = require("commander");
const pkg = require("../package.json");
const program = new commander.Command();
program
.name(Object.keys(pkg.bin)[0])
.usage("<command> [options]")
.version(pkg.version);
program.parse(process.argv);
執(zhí)行上述代碼后,運(yùn)行 ice-cli --help 的輸出如下所示:
Usage: ice-cli <command> [options]
Options:
-V, --version output the version number
-h, --help display help for command
注冊指令
commander 和 yargs 在注冊指令的語法上有一些區(qū)別。yargs 使用鏈?zhǔn)秸{(diào)用,而 commander 注冊指令后返回值并不是自身,因此不能通過鏈?zhǔn)秸{(diào)用來注冊多個指令。
// 注冊 clone 命令
program
.command("clone <source> [destination]")
.description("clone a repository")
.option("-f --force", "是否強(qiáng)制克隆")
.usage("[options]")
.action((source, destination, cmdObj) => {
console.log("do clone", source, destination);
});
// 劫持所有未定義的指令
program
.arguments("<cmd> [options]")
.description("test command", {
cmd: "command to run",
options: "options for command",
})
.action((cmd, options) => {
console.log(cmd, options);
});
當(dāng)執(zhí)行 ice-cli clone a b 時,輸出為 "do clone a b"。當(dāng)執(zhí)行 ice-cli create c 時,輸出為 "create c"。
注冊子命令
commander 注冊子命令的方式也非常簡單。以下是一個示例:
const service = new commander.Command("service");
service
.command("start [port]")
.description("start service at some port")
.action((port) => {
console.log('>>>service start', port)
});
service
.command("stop")
.description("stop service")
.action(() => {
console.log('>>>service stop')
});
當(dāng)執(zhí)行 ice-cli service start 8000 時,會輸出 ">>>service start 8000"。當(dāng)執(zhí)行 ice-cli service stop 時,會輸出 ">>>service stop"。
yargs 和 commander 解析命令行參數(shù),生成幫助信息,并注冊命令。它們提供了強(qiáng)大的命令行接口構(gòu)建能力,使得命令行工具更加靈活和易用。
命令行輸出美化庫
在進(jìn)行命令行交互時,經(jīng)常需要對某些內(nèi)容加粗、加字體顏色,以區(qū)分用戶選中的內(nèi)容和需要重點(diǎn)關(guān)注的問題。為了實(shí)現(xiàn)這些效果,存在一個命令行渲染標(biāo)準(zhǔn),稱為 ANSI escape code。此外,還有一些成熟的第三方庫,如 chalk 和 ora,可以幫助我們更方便地實(shí)現(xiàn)這些功能。
ANSI escape code
ANSI escape code 是一種用于控制終端輸出的標(biāo)準(zhǔn)。通過特定的編碼序列,可以在命令行中實(shí)現(xiàn)顏色、加粗等效果。
示例
在 bin 文件夾下創(chuàng)建 ansi.js 文件,文件中定義如下代碼:
console.log("\x1B[31mThis text is red\x1B[0m");
通過 node 執(zhí)行該 JS 文件,顯示的是紅色文本,內(nèi)容為 "This text is red"。如圖所示:

編碼解析
即使我們沒有借助任何第三方庫,僅通過一行文本就能實(shí)現(xiàn)命令行中的顏色和樣式變化。這一行看似“亂碼”的文本實(shí)際上是由 ANSI Escape Codes 組成的。下面是對這些字符的詳細(xì)拆解:
- \x1B:這是轉(zhuǎn)義字符,表示 ASCII 值為 27 的字符,也常表示為 ESC。它是所有 ANSI Escape Codes 的前綴。
- [:這是一個分隔符,表示接下來是一個控制序列。
- 31:這是一個數(shù)字代碼,表示設(shè)置前景色為紅色。
- m:這是一個終止符,表示控制序列的結(jié)束。
- \x1B[0m:重置所有文本屬性,包括顏色和樣式。
查找期望的樣式
要找到期望的樣式,可以在 ansi escape code 官網(wǎng) 查找。例如,31 代表紅色前景色,41 代表紅色背景色。

雖然可以直接使用 ANSI Escape Codes 來實(shí)現(xiàn)顏色和樣式變化,但在實(shí)際開發(fā)中這樣做會非常繁瑣。你需要手動定義轉(zhuǎn)義字符、分隔符,還要查找每個顏色對應(yīng)的編碼。幸運(yùn)的是,已經(jīng)有成熟的第三方庫可以幫助我們解決這些問題,例如 chalk 和 ora。
chalk
chalk 是一個用于顏色渲染的庫,其語法非常簡單,通過方法名就能知道其用途。常見的方法包括:
rgb(r, g, b):定義自定義顏色。blue:設(shè)置藍(lán)色字體。bold:設(shè)置字體加粗。green:設(shè)置綠色字體。underline:設(shè)置下劃線。
這些方法名與 CSS 中的樣式名稱相似,使得使用起來非常直觀。chalk 支持多種使用形式,包括直接使用、拼接、鏈?zhǔn)秸{(diào)用、傳入多個參數(shù)和嵌套調(diào)用。
安裝
首先通過 npm 安裝 chalk
npm install chalk
基本使用
chalk 是以 ES module 方式實(shí)現(xiàn)的,需要通過 import 方式引入。如果希望在 Node.js 環(huán)境中運(yùn)行,可以將文件后綴名定義為 .mjs。
例如以下代碼:
import chalk from "chalk";
// 直接使用
console.log("hello chalk");
// 定義自定義顏色
console.log(chalk.rgb(255, 0, 0)("hello nodejs"));
// 拼接不同樣式
console.log(chalk.blue.bold("hello ") + chalk.green("world"));
// 使用十六進(jìn)制顏色
console.log(chalk.hex("#ff0000")("it is a nice day"));
// 鏈?zhǔn)秸{(diào)用和嵌套調(diào)用
console.log(
chalk.green(
"I am a green line " +
chalk.blue.underline.bold("with a blue substring") +
" that becomes green again!"
)
);
執(zhí)行上述代碼后,命令行中的輸出效果如下所示:

ora
ora 是一個用于顯示加載動畫的庫,非常適合在命令行應(yīng)用中顯示進(jìn)度和狀態(tài)。它以 ES module 方式導(dǎo)出,需要定義 .mjs 文件。
安裝
首先通過 npm 安裝 ora:
npm install ora
基本使用
ora 在使用時需要手動調(diào)用開始和結(jié)束方法。以下是一個簡單的示例,顯示一個加載動畫:
import ora from "ora";
const spinner = ora({
text: "loading",
spinner: "dots",
}).start();
執(zhí)行上述代碼后,命令行中會顯示一個持續(xù)的加載動畫,如下圖所示:

自定義屬性
ora 還支持定義其他屬性,如加載動畫效果、顏色、前綴文本等。
屬性說明:
- text:初始加載文本。
- spinner:加載動畫效果,可以是一個預(yù)定義的字符串(如 dots、line 等)或自定義對象。
- color:加載動畫的顏色。
- prefixText:加載文本的前綴。
- start():啟動加載動畫。
- stop():停止加載動畫。
- succeed(message):停止加載動畫并顯示成功消息。
- fail(message):停止加載動畫并顯示失敗消息。
- warn(message):停止加載動畫并顯示警告消息。
- info(message):停止加載動畫并顯示信息消息。
以下是一個更復(fù)雜的示例,展示了如何動態(tài)更新加載文本并最終停止加載動畫:
import ora from "ora";
const spinner = ora({
text: "loading",
spinner: "dots",
}).start();
// 設(shè)置加載顏色和前綴文本
spinner.color = "red";
spinner.prefixText = "download ora:";
let percent = 0;
let task = setInterval(() => {
percent += 10;
spinner.text = "Loading..." + percent + "%";
if (percent === 100) {
spinner.stop();
spinner.succeed("download success");
clearInterval(task);
}
}, 1000);
按以上邏輯,執(zhí)行過程的中間態(tài)如下所示:

交互式命令行
在命令行應(yīng)用中,經(jīng)常會涉及到一些交互邏輯,例如在 eslint 初始化過程中會詢問用戶當(dāng)前使用的框架是 React、Vue 還是其他框架。用戶可以通過鍵盤的上下左右鍵和回車進(jìn)行選擇。inquirer 就是這樣一個用于命令行交互的第三方庫。
inquirer
安裝
inquirer 是一個強(qiáng)大的命令行交互庫,可以輕松地創(chuàng)建用戶友好的命令行界面。
npm install inquirer
基本使用
以下是一個簡單的示例,展示了如何使用 inquirer 創(chuàng)建一個列表選擇:
import inquirer from "inquirer";
inquirer
.prompt([
{
type: "list",
name: "language",
message: "language",
choices: [
{
value: 1,
name: "react",
},
{
value: 2,
name: "vue",
},
{
value: 3,
name: "angular",
},
],
},
])
.then((res) => {
console.log("anwser", res);
});
執(zhí)行上述代碼后,命令行中會出現(xiàn)一個選擇列表,如下圖所示:

多種交互類型
inquirer 支持多種交互類型,不僅限于列表選擇,還可以輸入文本、密碼、多選等。
以下是一個示例,展示了多種類型的交互問題:
import inquirer from "inquirer";
inquirer
.prompt([
{
type: "input",
name: "yourName",
message: "Your name",
},
{
type: "list",
name: "language",
message: "language",
choices: [
{
value: 1,
name: "react",
},
{
value: 2,
name: "vue",
},
{
value: 3,
name: "angular",
},
],
},
{
type: "expand",
name: "color",
message: "color",
choices: [
{
key: "R",
value: "red",
},
{
key: "G",
value: "green",
},
{
key: "B",
value: "blue",
},
],
},
{
type: "checkbox",
name: "fruits",
message: "fruits",
choices: [
{
value: 1,
name: "apple",
},
{
value: 2,
name: "banana",
},
{
value: 3,
name: "orange",
},
],
},
{
type: "password",
name: "password",
message: "password",
},
])
.then((res) => {
console.log("anwser", res);
});
執(zhí)行上述代碼后,命令行中會依次顯示多個交互問題,最終,所有的用戶輸入都會在 then 方法中統(tǒng)一獲取,如下圖所示:

通過 inquirer,你可以輕松地在命令行應(yīng)用中實(shí)現(xiàn)各種交互邏輯。其豐富的交互類型和靈活的校驗(yàn)機(jī)制使得 inquirer 成為一個非常實(shí)用的命令行交互庫。
通過結(jié)合這些工具和庫,可以輕松地構(gòu)建出功能強(qiáng)大、用戶體驗(yàn)良好的命令行應(yīng)用。
如果你對前端工程化有興趣,或者想了解更多相關(guān)的內(nèi)容,歡迎查看我的其他文章,這些內(nèi)容將持續(xù)更新,希望能給你帶來更多的靈感和技術(shù)分享~

浙公網(wǎng)安備 33010602011771號