前言:
新手誤區:
一個簡單的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 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 rn --type build:
同上
小結
bin 文件夾下的這3個 node 文件他們都是 commander 實例,commander 庫只是一個簡單的封裝,幫助定義 多文件命令、執行參數 、簡易文檔,參數驗證等。 以上就是 commander 的大致使用和我對其的理解。 源碼不多,建議可以深入學習一下。
最后
到最后大家結合實現以上所說的 cli 和 commander,一個 commander 實現的命令行工具就能完成了,是不是很簡單!?
注意
如果執行命令發現報錯為 error: xx(1) not executable. try chmod or run with root,要注意下創建的文件類型。