commanderJs編寫命令列工具(cli)
前言:
最近需要做一個內部的node cli來獨立構建流程,對整個命令列工具實現流程有了大致瞭解,下面來解釋一下如何實現一個cli,和如何使用 commander 庫。新手誤區:
在開始實現之前,我知道有 commander 這個node庫,有很多cli使用了它,對它的大致瞭解就是它可以幫助開發者簡化實現命令流程。 於是最初以為不利用庫要去實現命令列會很麻煩, commander 幫我們解決了各種相容問題等等。 後來發現並不是的,沒有 commander 我們也可以不用化多大力氣實現命令列, commander一個簡單的cli:
bin:
package.json 中有一個 bin 欄位,指定各個內部命令對應的可執行檔案的位置。 在包安裝時,如果是全域性安裝,npm 將會把 package.json 裡定義的 bin 檔案軟連線到全域性 node_modules/bin,如果是非全域性安裝,會軟連結到專案資料夾./node_modules/.bin/。 根據下面程式碼的配置,當我們全域性安裝此包後,在任意位置執行 cli-test
/*cli-test 的 package.json*/ "bin": { "cli-test": "./bin/cli-test" }
如果我們把cli安裝在專案A node_modules中,通過設定專案中 package.json 的 scripts,執行 npm run cli,npm 就會在專案的 node_modules/.bin 尋找並執行 cli-test 檔案。
/*專案A package.json*/ "scripts": {"cli": "cli-test" }
cli檔案:
下面是 cli-test 檔案,第一行必寫,是告訴Unix和Linux系統這個檔案中的程式碼用node可執行程式去執行它。 後面就做我們要做的事情就行了 。
#!/usr/bin/env node //do something
好了,到這裡我們的cli就完成了。 其實有很多三方cli也並沒有用類似 commander 的 node 庫,如果我們的 cli 足夠簡單,以上這樣就可以了。 下面接著講 commander。
commander:
現在我們先寫一個簡單的檔案來理解(也推薦先自行預覽一下 commander 官方文件),下面是 bin 資料夾的 cli-test,程式碼如下:
#!/usr/bin/env node const program =require('commander'); program .usage('[option]', '--type required') .option('--type [typeName]', 'type: dev && build') .parse(process.argv); const {type} = program; if(type == 'dev'){ console.log('do something', type) }else if(type == 'build'){ console.log('do something', type) }else{ console.log('params error'); program.help(); }
解釋一下上面的程式碼,從檢視原始碼裡發現 require('commander') 會 new一個commander 內部的單例物件並返回,program 已經是一個例項,。 .usage 僅僅描述了引數規則,會在 --help 中打印出來。.option 定義了一個引數名和描述, parse 會解析命令之中的引數,根據上面定義好的規則執行相關命令。 比如上面的程式碼定義了 option 型別的引數 --type,執行 .parse 的時候,parse 根據 process.argv 之中的引數,獲取到 --type,並把引數命和引數值儲存在內部 commander 例項的屬性之中,因此後面的程式碼就能從 program 之中取到 type,如果 type 不存在或者不是我們約定的值,最後我們列印引數錯誤,並執行help方法列印了 --help。 如下截圖,我們 node 執行 cli-test,因為沒有約定引數,所以執行了 else 的程式。(因為這裡是本地的demo程式,所以直接使用node命令)
接著,我們執行正確的命令引數,如下
這樣一個簡單的demo就實現了,看起來也挺簡單的,commander 封裝了一些也不算很複雜的功能。
再來一個例子:
新建了兩個檔案,要以 bin 命令的執行檔案命後面加上 -name,作為子命令檔案
cli-test:
#!/usr/bin/env node const program =require('commander'); program .usage('<command> [option]', 'option --type required') .command('h5', 'to h5') .command('rn', 'to rn') .parse(process.argv);
cli-test-h5:
#!/usr/bin/env node const program =require('commander'); program .option('--type [typeName]', 'type: dev && build') .parse(process.argv); const {type} = program; if(type == 'dev'){ console.log('do something h5', type) }else if(type == 'build'){ console.log('do something h5', type) }else{ console.log('params error'); program.help(); }
cli-test-rn:
#!/usr/bin/env node const program =require('commander'); program .option('--type [typeName]', 'type: dev && build') .parse(process.argv); const {type} = program; if(type == 'dev'){ console.log('do something rn', type) }else if(type == 'build'){ console.log('do something rn', type) }else{ console.log('params error'); program.help(); }
先直接執行3個命令執行程式,看下結果,而後分別解釋一下:
node ./bin/cli-test:
定義了.command子命令卻沒有相應執行引數,commander物件會直接列印-help,並process.exit退出程序。
node ./bin/cli-test h5 --type dev:
cli-test 通過 command 方法約定子命令名稱和描述,如 h5,當執行 node cli-test h5 --type dev的時候,cli-test 執行到 .command('h5', 'to h5') ,會在當前 commander 例項內部,new 一個 name 為 h5 的子 commander,儲存在當前父例項的 commands 陣列中,當 .parse(process.argv) 執行,獲取到引數中 h5 後,在 commands 裡查詢是否有 name 為 h5 的 commander 子例項,如果查詢到,啟動一個子程序按照命名規則執行 cli-test-h5 檔案並帶入後面的 option 引數。 這樣 commander 就幫助我們實現了多檔案命令劃分,我們可以把不同型別的執行程式碼放在不同的檔案中。
node ./bin/cli-test h5 --type dev:
同上
小結
bin 資料夾下的這3個 node 檔案他們都是 commander 例項,commander 庫只是一個簡單的封裝,幫助定義 多檔案命令、執行引數 、簡易文件,引數驗證等。 以上就是 commander 的大致使用和我對其的理解。 原始碼不多,建議可以深入學習一下。
最後
到最後大家結合實現以上所說的 cli 和 commander,一個 commander 實現的命令列工具就能完成了,是不是很簡單!?
注意
如果執行命令發現報錯為 error: xx(1) not executable. try chmod or run with root,要注意下建立的檔案型別。