本文帶你瞭解建立一個Node-CLI工具所需知識點。
一、命令列引數解析
在NodeJS中可以通過以下程式碼獲取命令列中傳遞的引數:
process.argv.slice(2)
複製程式碼
但是這對於構建一個CLI工具遠遠不夠,首先需要考慮引數輸入的各種風格:
- Unix引數風格:前面加-,不過後面跟的是單個字元,例如-abc解析為['a', 'b', 'c']。
- GNU引數風格:前面加--,例如npm中的命令,npm --save-dev webpack。
- BSD引數風格:前面不加修飾符。
這裡可以通過正規表示式對process.argv進行加工:
/**
* 解析Unix、BSD和GNU引數風格
* @param {Array} argv 命令列引數陣列
* @returns
*/
function parseArgv (argv) {
const max = argv.length
const result = {
_: []
}
for (let i = 0; i < max; i++) {
const arg = argv[i]
const next = argv[i + 1]
if (/^--.+/.test(arg)) {
// GNU風格
const key = arg.match(/^--(.+)/)[1]
if (next != null && !/^-.+/.test(next)) {
result[key] = next
i++
} else {
result[key] = true
}
} else if (/^-[^-]+/.test(arg)) {
// Unix風格
const items = arg.match(/^-([^-]+)/)[1].split('')
for (let j = 0, max = items.length; j < max; j++) {
const item = items[j]
// 非字母不解析
if (!/[a-zA-Z]/.test(item)) {
continue
}
if (next != null && !/^-.+/.test(next) && j === max - 1) {
result[item] = next
i++
} else {
result[item] = true
}
}
} else {
// BSD風格
result._.push(arg)
}
}
return result
}
複製程式碼
通過以上的方法可以得到如下結果:
node example1.js --save-dev -age 20 some
// => 結果
{
_: ['some'],
'save-dev': true,
a: true,
g: true,
e: 20
}
複製程式碼
上面這個示例不僅僅為了展示解析的結果,而且還強調了Unix引數風格只解析單個字母,所以這種風格的引數可能表達的意思不太明確並且數量有限,那麼就需要在正確的場景中使用這種風格的引數:
npm --save-dev webpack
npm -D webpack
複製程式碼
npm中採用Unix引數風格表示簡寫,這就是一種很恰當的方式,那麼前面示例中的-age按照語義應該改為--age更加合理一點。
二、命令列介面
NodeJS中的readline模組提供question和prompt方法構建命令列介面,下面是一個簡單的問答式的互動介面:
const readline = require('readline');
const question = ['請輸入您的姓名', '請輸入您的年齡']
const result = []
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: `?${question[0]} `
});
rl.prompt();
rl.on('line', (line) => {
result.push(line.trim())
const max = result.length
if (max === question.length) {
rl.close()
}
rl.setPrompt(`?${question[max]} `)
rl.prompt();
}).on('close', () => {
console.log(`謝謝參與問答 *** 姓名: ${result[0]} 年齡: ${result[1]}`);
process.exit(0);
});
複製程式碼
當然互動介面的元素並不只有這一種,在使用各類CLI工具時,你應該會遇到諸如:單項選擇、下載進度條...
下面可以嘗試實現一個單項選擇互動介面:
const readline = require('readline')
let selected = 0
const choices = ['javascript', 'css', 'html']
let lineCount = 0
const rl = readline.createInterface(process.stdin, process.stdout)
function reader () {
let str = ''
for (let i = 0; i < choices.length; i++) {
lineCount++
str += `${selected === i ? '[X]' : '[ ]'} ${choices[i]}\r\n`
}
process.stdout.write(str)
}
reader()
process.stdin.on('keypress', (s, key) => {
const name = key.name
const max = choices.length
if (name === 'up' && selected > 0) {
selected--
} else if (name === 'down' && selected < max - 1) {
selected++
} else if (name === 'down' && selected === max - 1) {
selected = 0
} else if (name === 'up' && selected === 0) {
selected = max - 1
} else {
return true
}
// 移動游標至起始位置,確保後續輸入覆蓋當前內容
readline.moveCursor(process.stdout, 0, -lineCount)
lineCount -= choices.length
reader()
})
rl.on('line', () => {
console.log(`you choose ${choices[selected]}`)
process.exit(0)
}).on('close', () => {
rl.close()
})
複製程式碼
三、定製樣式
為了有效的區別命令列介面中資訊的差異性,我們可以為這裡輸出資訊新增適當的樣式。
這裡介紹一下字串新增樣式的語法:
\x1b[背景顏色編號;字型顏色編號m
複製程式碼
每條樣式都要以\x1b[開頭:
// \x1b[0m 清除樣式
process.stdout.write('\x1b[44;37m OK \x1b[0m just do it\n')
複製程式碼
四、自定義Node命令
接下來就是自定義Node命令,首先需要建立一個命令執行的檔案:
// hello.js 首行需要指定指令碼的解釋程式
#!/usr/bin/env node
console.log('hello')
複製程式碼
再利用package.json中的bin配置:
{
"bin": {
"hello": "./hello.js"
},
}
複製程式碼
執行npm的link命令:
npm link
# 輸入自定義命令
hello
# 輸出 hello
複製程式碼
五、總結
上面介紹了開發Node-CLI時所需要的一些基本知識,但是對於用過諸如webpack-cli、vue-cli工具的你可能會發現這些優秀的CLI工具還具有:
- git風格的子命令;
- 自動化的幫助資訊;
- ....
那麼下面這些成熟的框架會給你很大的幫助: