日常開發中,編寫 Node.js 命令行工具來完成一些小任務是很常見的操作。其編寫也不難,和日常編寫 Node.js 代碼並無二致。
package.json
中的 bin
字段
一個 npm 模塊,如果在 package.json
中指定了 bin
字段,那說明該模塊提供了可在命令行執行的命令,這些命令就是在 bin
字段中指定的。
package.json
{
"bin": {
"myapp": "./cli.js"
}
}
程序安裝后會可在命令行執行 myapp
命令,實際執行的就是指定的這個 cli.js
文件。如果是全局安裝,會將這個目標 js 文件映射到 prefix/bin
目錄下,而如果是在項目中安裝,則映射到 ./node_modules/.bin/
目錄下。
比如上面的示例,全局安裝后會將 cli.js
映射到 /usr/local/bin/myapp
目錄下。
# 查看項目中安裝的所有可執行模塊
$ ll node_modules/.bin
...
webpack -> ../webpack/bin/webpack.js
...
如果你的 npm 包只提供了一個可執行的命令,可直接將 bin
字段設置為目標文件,此時命令行中可執行的 CLI 命令名為 npm 包名(即 name
字段)。
{
"name": "my-program",
"version": "1.2.5",
"bin": "./path/to/program"
}
所以上面的配置和下面這個配置是等效的。
{
"name": "my-program",
"version": "1.2.5",
"bin": {
"my-program": "./path/to/program"
}
}
執行:
$ my-program
CLI 命令的編寫
為項目添加 README 文件是很常見的操作,每次都從零開始是沒必要的。這時候就可以通過創建一個 README 模板,然后寫一個 CLI 工具來生成到項目中。這樣,將工具安裝到全局或通過 npx
就可以方便地完成 README 文件的創建。
$ npx mkreadme
README.md created
初始化項目
$ npm init -y
修改 package.json
文件為如下內容:
{
"name": "mkreadme",
"bin": "./cli.js",
"version": "0.1.0",
"license": "MIT"
}
創建入口文件
$ touch cli.js
入口文件即 bin
字段所指向的文件,它可以是任何文件名,只需要在行首指定運行環境即可。Node.js 的 CLI 命令,期望的運行環境當然是 Node.js。
cli.js
#!/usr/bin/env node
// 其他代碼...
然后添加我們的功能代碼,從遠端獲取一個 README 模板文件到本地。
cli.js
#!/usr/bin/env node
const fs = require("fs");
const https = require("https");
const TEMPLATE_FILE =
"https://raw.githubusercontent.com/wayou/readme-template/master/README.md";
const file = fs.createWriteStream("README.md");
https.get(TEMPLATE_FILE, resposne => {
resposne.pipe(file);
console.log("README.md created");
});
這里,我們將模板文件放遠端的一個位置然后通過網絡請求下載下來,而不是直接放到 npm 模塊中。這樣做的好處是后面可以隨時更新我們的模板文件而無須重新發布這個 npm 模塊。
調試
通過在當前開發目錄進行 link
操作可進行本地調試。
$ npm link
link 操作的輸出信息
npm WARN mkreadme@0.1.0 No description
npm WARN mkreadme@0.1.0 No repository field.
up to date in 3.435s
found 0 vulnerabilities
/Users/wayou/.nvm/versions/node/v11.14.0/bin/mkreadme -> /Users/wayou/.nvm/versions/node/v11.14.0/lib/node_modules/mkreadme/cli.js
/Users/wayou/.nvm/versions/node/v11.14.0/lib/node_modules/mkreadme -> /Users/wayou/Documents/dev/github/mkreadme
然后就可以在任何地方執行剛剛創建的 CLI 命令了。
$ mkreadme
README.md created
參數的獲取
讓命令支持參數可以實現更加靈活的功能。通過 process.argv
在代碼中能夠獲取到來自命令行的輸入。但需要注意它返回的參數列表中前兩位是 Node.js 的路徑和當前項目的路徑,從第三個元素開始才是命令中用戶輸入的數據。
#!/usr/bin/env node
const fs = require("fs");
const https = require("https");
const TEMPLATE_FILE =
"https://raw.githubusercontent.com/wayou/readme-template/master/README.md";
+ const [, , ...args] = process.argv;
const file = fs.createWriteStream("README.md");
+ const url = args[0] || TEMPLATE_FILE;
+ https.get(url, resposne => {
resposne.pipe(file);
console.log("README.md created");
});
通過添加參數的支持,我們可以讓使用者手動指定一個模板地址以下載對應的模板文件。
發布
最后一步就是發布出去,這樣所有人就能安裝使用了。
$ npm publish --access public
安裝與使用
$ npm i -g mkreadme
$ mkreadme
README.md created
除了像上面將命令安裝到全局使用外,個人更加推薦的方式是通過 npx
。npx
會自動查找本機是否有安裝相應模塊,如果沒有的話,自動去遠端查找並執行。通過 npx
就不用安裝到本地,每次運行都可以使用遠端最新的版本。
$ npx mkreadme
README.md created
后續的優化
示例中只實現了基本的功能,作為一個功能健全的實用工具,可以做以下的優化:
- 生成時做重名判斷,如果已經存在 README 文件則提示是否覆蓋。
- 文件下載和創建過程中的異常處理及提示。
- 提供並打印幫助信息,對使用者更加友好。
- 對輸出進行格式化,高亮輸出相關信息,使信息更易讀。
完整的示例
上面示例中的代碼可在 mkreadme 這個倉庫中找到。同時也發布到了 npm,可直接使用體驗該工具。
三方工具
命令行工具能夠打印幫助和使用信息是很重要的,如果自己輸出的話,會面臨格式化這些內容的麻煩。
如果提供的參數很多,解析處理用戶輸入的參數也是件很麻煩的事。
像參數校驗,錯誤提示及幫助信息的輸出,這些命令行工具基本的功能已經有三方庫比較成熟地解決了,比如 commander.js。通過這個庫可方便地編寫更加復雜的命令行工具。
至於將輸出信息進行高亮加彩色進行展示,也有相應三方庫比如 chalk。