使用 Node,如何制作一個(gè)專業(yè)的命令行工具?
使用 Node 開發(fā)一個(gè)命令行工具很有意思,較為其它語言而言也更加簡單,今天山月寫一篇文章總結(jié)一下如何寫一個(gè)友好且健壯的 cli 工具。
在閱讀完本篇文章之后,「強(qiáng)烈推薦閱讀」 Github 上的一篇文章: Node CLI 工具最佳實(shí)踐。正如它的標(biāo)題而言,在 Github 上用于 2000 顆星星的該文章的確稱得上最佳實(shí)踐。
長按識(shí)別二維碼查看原文
標(biāo)題:Node CLI 工具最佳實(shí)踐 ? ? ?「目錄」
1. 命令行工具與環(huán)境變量 PATH2. 原理3. 從 package.json 說起4. 一個(gè)執(zhí)行環(huán)境5. 解析命令輸入6. 用戶體驗(yàn)與豐富的色彩7. 可交互性8. 發(fā)布與安裝9. 總結(jié)
1. 命令行工具與環(huán)境變量 PATH
什么是命令行工具?
最初印象大致是 ls,pwd 這些能夠「在終端執(zhí)行的系統(tǒng)命令」,這樣的命令很多,數(shù)不勝數(shù),被稱為系統(tǒng)內(nèi)置命令。
如果使用 which 查看他們的來歷,則能夠發(fā)現(xiàn)他們的廬山真面目:
# 由此可知,pwd 為一個(gè)系統(tǒng)內(nèi)置命令
$ which pwd
pwd: shell built-in command
隨著對(duì) Linux/Unix 系統(tǒng)理解及使用的逐漸加深,發(fā)現(xiàn)了諸多的非內(nèi)置命令:
top
ps
netstat
dig
man
使用 which 追根究底,發(fā)現(xiàn)它們實(shí)際執(zhí)行的路徑在某一個(gè) bin 目錄
$ which top
/usr/bin/top
$ which ps
/bin/ps
而這些 bin 目錄在環(huán)境變量 PATH 中,豁然開朗。簡而言之: 「在環(huán)境變量的 PATH 中路徑的命令可在其它任意地方執(zhí)行」。
export PATH=$HOME/bin:/usr/local/bin:$PATH
說起環(huán)境變量,你可以使用
env列出所有的環(huán)境變量。$ env
LANG=zh_CN.UTF-8
USER=root
LOGNAME=root
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
你好像想起點(diǎn)什么?在大學(xué)配置 Java 時(shí)被環(huán)境變量支配的各種恐懼?
是了,所有語言的可執(zhí)行命令都要放在 PATH 下,只不過其它語言的安裝工具自動(dòng)給你做了這件事,而 Java 讓你自己來處理它。
java
python
pip
node
npm
「開發(fā)命令行的原理也是如此,將你開發(fā)的命令行工具腳本置于環(huán)境變量 PATH 下的路徑之中」,而本篇文章的目標(biāo)就是:
使用 Javascript 這門前端開發(fā)者熟悉的語言,借助 Node 環(huán)境,開發(fā)一個(gè)命令行工具。
2. 原理
先看兩個(gè) Node 的命令行工具:
[serve](https://github.com/vercel/serve)一個(gè)流行的靜態(tài)文件服務(wù)器,大名鼎鼎 Vercel 團(tuán)隊(duì)(Next.js 團(tuán)隊(duì))的力作markdown一個(gè)山月自己寫的解析 URL 到 markdow 的命令行。通過命令解析出他們指向的符號(hào)鏈接
$ ls -lah $(which serve)
lrwxr-xr-x 1 xiange admin 65B 7 12 2020 /usr/local/bin/serve -> ../../../Users/shanyue/.config/yarn/global/node_modules/.bin/serve
$ ls -lah $(which markdown)
lrwxr-xr-x 1 xiange admin 48B 1 28 20:06 /usr/local/bin/markdown -> ../lib/node_modules/markdown-read/md-read-cli.js
從中可以看出關(guān)于 Node 全局命令行的原理:
npm 全局下載某個(gè) package 到路徑
/usr/local/lib/node_modules下 (yarn 同理,對(duì)應(yīng)路徑~/.config/yarn/global/node_modules)根據(jù)該庫的 package.json 中
bin字段的指示,把對(duì)應(yīng)的命令行路徑通過符號(hào)索引掛載到 PATH 路徑對(duì)應(yīng)的二進(jìn)制腳本添加 x 權(quán)限 (可執(zhí)行文件權(quán)限)
簡而言之,Node 環(huán)境下的命令行工具,借助的原理無非是「環(huán)境變量 Path」與一個(gè)「符號(hào)鏈接」
3. 從 package.json 說起
在 「package.json 中的 bin 字段」,用以指定最終的命令行工具的名字
{
"bin": {
"serve": "./bin/serve.js"
}
}
如上所示,server 是最終在終端執(zhí)行的命令,而 ./bin/serve.js 是該命令實(shí)際執(zhí)行的腳本文件。
對(duì)于最終可執(zhí)行的命令行工具,Node 項(xiàng)目一般傾向置文件于 bin 目錄下,如以下 Typescript 關(guān)于它命令行的配置:
{
"bin": {
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"
},
}
4. 一個(gè)執(zhí)行環(huán)境
對(duì)于可直接執(zhí)行的文件,需要指明執(zhí)行環(huán)境,首行添加一行說明:
#!/usr/bin/env node
// code 往下寫
這一句話是啥子意思了?
#!加解釋器,標(biāo)明該文件使用/usr/bin/env node來執(zhí)行/usr/bin/env為 env 的絕對(duì)路徑,用以在 PATH 路徑中執(zhí)行命令 (在各種不同的系統(tǒng)中,node 命令行的位置不同,因此使用env node找到路徑并執(zhí)行)env node在人為層面可理解為執(zhí)行node命令
所以這句話的意思是: 「使用 node 解釋器來執(zhí)行這個(gè)腳本,而通過 env node 能夠正確定位到 node 解釋器的位置」
// 如果不寫 #!/usr/bin/env node,需要這么執(zhí)行
$ node serve .
// 如果寫上 #!/usr/bin/env node,可以直接執(zhí)行
$ serve .
5. 解析命令輸入
在服務(wù)端應(yīng)用中,可以通過 request.url.querystring 及 request.body 獲取用戶輸入。
而在命令行工具中,可通過 progress.argv 可獲取用戶輸入。請(qǐng)看以下示例:
$ node cmd.js 1 2 3
// Output: [
// '/usr/local/bin/node',
// '/Users/shanyue/cmd.js',
// '1',
// '2',
// '3',
// ]
process.argv
根據(jù)解析 process.argv 可以定制格式來獲取各式各樣的參數(shù)作為命令行的輸入。`
當(dāng)然解析參數(shù)也要參照 POSIX 兼容的基本規(guī)律: 格式、可選、必選、簡寫、說明、幫助等等。命令行工具命名協(xié)議 文章中已說的足夠詳細(xì)。
長按識(shí)別二維碼查看原文
標(biāo)題:命令行工具命名協(xié)議 ? ? ?// 一個(gè)較為規(guī)整的命令行幫助
$ node --help
Usage: node [options] [ script.js ] [arguments]
node inspect [options] [ script.js | host:port ] [arguments]
Options:
- script read from stdin (default if no file name is provided,
interactive mode if a tty)
-- indicate the end of node options
--abort-on-uncaught-exception aborting instead of exiting causes a core file to be generated for
analysis
-c, --check syntax check script without executing
--completion-bash print source-able bash completion script
--cpu-prof Start the V8 CPU profiler on start up, and write the CPU profile to
disk before exit. If --cpu-prof-dir is not specified, write the profile
to the current working directory.
因?yàn)?POSIX 兼容繁雜的規(guī)則,以此衍生出了關(guān)于解析命令參數(shù)的多個(gè)庫,站在巨人的肩膀上,在實(shí)際工作中就直接開用吧!
yargs: Star 8.5K,周下載量 4900K
長按識(shí)別二維碼查看原文
標(biāo)題:yargs ? ? ?commander: Star 19.7K,周下載量 5300K,tj 大神的作品
長按識(shí)別二維碼查看原文
標(biāo)題:commander ? ? ?
以下是一個(gè) Demo: 使用 commander 解析不同的輸入指令
const { program } = require('commander')
// 解析不同的指令輸入
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.option('-p, --pizza-type ' , 'flavour of pizza')
program.parse(process.argv)
const options = program.opts()
console.log(options)
6. 用戶體驗(yàn)與豐富的色彩
Next 構(gòu)建輸出以上是 next build 命令行的標(biāo)準(zhǔn)輸出,擁有色彩多樣的高亮格式與豐富的列表展示,可以提供更豐富的用戶體驗(yàn)。
目前大部分終端已支持彩色輸出,即通過 ANSI 編碼進(jìn)行控制,并擁有成熟的庫控制色彩。如 ansi-styles
長按識(shí)別二維碼查看原文
標(biāo)題:ansi-styles ? ? ?import styles from 'ansi-styles';
console.log(`${styles.green.open}Hello world!${styles.green.close}`);
豐富的高亮色彩如同代碼高亮一樣使用戶可以快速抓住重點(diǎn)。把異常、警告、成功的信息用不同的顏色標(biāo)出,命令行工具的輸出一目了然。在現(xiàn)代構(gòu)建工具,如 Webpack 下,也大都支持彩色輸出。
以下是在命令行工具中常用的兩個(gè)較為高級(jí)的色彩庫,支持多種多樣色彩的輸出,當(dāng)然基本原理仍是 ANSI 編碼。
chalk
長按識(shí)別二維碼查看原文
標(biāo)題:chalk ? ? ?colors
長按識(shí)別二維碼查看原文
標(biāo)題:colors ? ? ?
以下是 chalk 示例,Error 與 Warning 信息用不同的顏色表示
const chalk = require('chalk')
const error = chalk.bold.red
const warning = chalk.keyword('orange')
console.log(error('Error!'))
console.log(warning('Warning!'))
7. 可交互性
在 Web 中,可使用 Input 來展現(xiàn)豐富多彩的表單,如開關(guān)、多選、單選、輸入框等。而在命令行工具中,也可借用多種庫來實(shí)現(xiàn)強(qiáng)交互性。
enquirer
長按識(shí)別二維碼查看原文
標(biāo)題:enquirer ? ? ?ora
長按識(shí)別二維碼查看原文
標(biāo)題:ora ? ? ?ink
長按識(shí)別二維碼查看原文
標(biāo)題:ink ? ? ?
8. 發(fā)布與安裝
在辛苦努力寫完一個(gè) cli 工具后,就是檢驗(yàn)成果的時(shí)候。發(fā)布到 npm 倉庫,可使所有人使用你的命令行工具,這也是最重要的一步
# 發(fā)布之前需要 npm login,登錄到 npm registory
$ npm publish
發(fā)版成功后全局下載命令行工具,開始使用,示例用它抓取下我的博客首頁
$ npm i -g markdown-read
/usr/local/bin/markdown -> /usr/local/lib/node_modules/markdown-read/md-read-cli.js
+ [email protected]
added 102 packages from 72 contributors and updated 10 packages in 33.15s
$ markdown https://shanyue.tech
## [#](#山月的瑣碎博客記錄) 山月的瑣碎博客記錄
本博客關(guān)于平常工作中在前端,后端以及運(yùn)維中遇到問題的一些文章總結(jié)。以后也會(huì)做系列文章進(jìn)行輸出,如前端高級(jí)進(jìn)階系列,個(gè)人服務(wù)器指南系列。個(gè)人微信 shanyue94,歡迎添加交流
## [#](#名字由來) 名字由來
9. 總結(jié)
本篇文章由淺至深講解了以下幾方面的內(nèi)容:
一個(gè)全局可執(zhí)行的命令行工具的原理是什么
在 Node 中開發(fā)一個(gè)命令行工具所需要的配置
開發(fā)命令行工具時(shí)如何解析參數(shù)
并根據(jù)實(shí)踐,開發(fā)了一個(gè)從 URL 中讀取 Markdown 的小工具: markdown-read,歡迎 Star、下載及使用。
長按識(shí)別二維碼查看原文
標(biāo)題:markdown-read ? ? ?另外,我基于此命令行做了一個(gè) Web 版,歡迎來體驗(yàn): https://devtool.tech/html-md
HTML To Markdown 網(wǎng)頁版