1. 程式人生 > >commanderJs編寫命令列工具(cli)

commanderJs編寫命令列工具(cli)

前言:

  最近需要做一個內部的node cli來獨立構建流程,對整個命令列工具實現流程有了大致瞭解,下面來解釋一下如何實現一個cli,和如何使用  commander 庫。  

新手誤區:

  在開始實現之前,我知道有  commander 這個node庫,有很多cli使用了它,對它的大致瞭解就是它可以幫助開發者簡化實現命令流程。  於是最初以為不利用庫要去實現命令列會很麻煩, commander 幫我們解決了各種相容問題等等。  後來發現並不是的,沒有 commander 我們也可以不用化多大力氣實現命令列, commander
僅僅是一個本身也不太複雜的封裝(原始碼也只有1200行)。  我們的第一步還是應該先搞清楚,通過 npm如何實現命令列。  

一個簡單的cli:

bin:

  package.json 中有一個 bin 欄位,指定各個內部命令對應的可執行檔案的位置。   在包安裝時,如果是全域性安裝,npm 將會把 package.json 裡定義的 bin 檔案軟連線到全域性 node_modules/bin,如果是非全域性安裝,會軟連結到專案資料夾./node_modules/.bin/。  根據下面程式碼的配置,當我們全域性安裝此包後,在任意位置執行 cli-test

,都會執行全域性 node_modules 中的 cli-test 檔案。

/*cli-test 的 package.json*/
"bin": {
    "cli-test": "./bin/cli-test"
}

 

如果我們把cli安裝在專案A node_modules中,通過設定專案中 package.json scripts,執行 npm run clinpm 就會在專案的 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也並沒有用類似 commandernode 庫,如果我們的 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 裡查詢是否有 nameh5commander 子例項,如果查詢到,啟動一個子程序按照命名規則執行 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,要注意下建立的檔案型別。