隨着NodeJs
的不斷髮展,對於前端來講要作的東西也就更多,Vue
腳手架React
腳手架等等等一系列的東西都脫穎而出,進入到人們的視野當中,對於這些腳手架工具來說也只是停留在應用階段,歷來沒有想過腳手架是如何實現的?vue init webpack 項目名稱
是如何經過這樣的命令建立了一個項目,其最重要的模塊就是今天要說的Commander
。javascript
Commander
模塊又國外TJ
大神所編寫前端
項目地址:Commandervue
Commander基本用法
Commander
文檔寫的很詳細,跟着文章詳細的學習一下,Commander
是一個Nodejs
模塊,須要在Node
環境中運行,在使用前確認一下Node
環境是否已安裝。java
npm install commander --save
在Commander
模塊下存在option
方法用來定義commander
的選項options
,用來做爲選項的文檔。node
var program = require('commander'); program .option('-g, --git [type]', 'Add [marble]', 'Angie') .parse(process.argv); console.log("process.argv",process.argv) console.log("program.args",program.args) console.log('you ordered a pizza with:'); if (program.git) console.log(' - git'); console.log(' - %s git', program.git);
上面的示例將解析來自process.argv
的args
和options
,而後將剩下的參數(未定義的參數)賦值給commander
對象的args
屬性(program.args
),program.args
是一個數組。react
打印輸出一下process.argv
和program.args
並查看了一下輸出結果以下,使用以下命令運行一下文件:webpack
node index -g type Aaron
process.argv ['F:\\node\\installation\\node.exe', 'C:\\Users\\wo_99\\Desktop\\cli-dome\\index', '-g', 'type', 'Aaron' ] program.args [ 'Aaron' ]
option
方法能夠接收三個參數:git
必須
:分爲長短標識,中間用逗號、豎線或者空格分割;標誌後面可跟必須參數或可選參數,前者用<>
包含,後者用[]
包含。省略不報錯
:在使用 --help 命令時顯示標誌描述可省略
:當沒有傳入參數時則會使用默認值若咱們執行node index -g
獲得的結果則是Angie git
其第三個參數則做爲了默認值填寫在了對應的位置上。除了上面所說還可使用以下命令:github
// 執行 -g 參數 a // 執行 -b 參數 s node index -g a -b s // 執行 -g和-b 傳入a參數給-g // -b 參數暫時不知道怎麼傳入 node index -gb a
調用版本會默認將-V
和--version
選項添加到命令中。當存在這些選項中的任何一個時,該命令將打印版本號並退出。web
var program = require('commander'); program .version('0.0.1') .parse(process.argv); // 執行命令 // node index -V // 輸出結果 // 0.0.1
若是但願程序響應-v
選項而不是-V
選項,只需使用與option
方法相同的語法將自定義標誌傳遞給version
方法,版本標誌能夠被命名爲任何值,可是長選項是必需的。
var program = require('commander'); program .version('0.0.1', '-e, --version');
該方法容許使用命令行去執行一段命令,也就是一段:
var program = require('commander'); program .version('0.0.1', '-V, --version') .command('rm <dir>') .action(function (dir, cmd) { console.log('remove ' + dir + (cmd.recursive ? ' recursively' : '')) }); program.parse(process.argv); // 執行命令 // node index rm /aaa -r // 輸出結果 // remove /aaa recursively 即:代碼中console內容
command
函數接收三個參數:
必須
:命令後面可跟用<>
或[]
包含的參數;命令的最後一個參數能夠是可變的,像實例中那樣在數組後面加入...
標誌;在命令後面傳入的參數會被傳入到action
的回調函數以及program.args
數組中。可省略
:若是存在,且沒有顯示調用action(fn)
,就會啓動子命令程序,不然會報錯可省略
:可配置noHelp、isDefault
等使執行命令時,將驗證該命令的options
,任何未知的option
都將報錯。可是,若是基於action
的命令若是沒有定義action
,則不驗證options
。
var program = require('commander'); program .version('0.0.1', '-V, --version') .command('rm <dir>') .option('-r, --recursive', 'Remove recursively') .action(function (dir, cmd) { console.log('remove ' + dir + (cmd.recursive ? ' recursively' : '')) }); program.parse(process.argv); console.log(program.args) // 執行命令 // node index rm /aaa -r /ddd // 輸出結果 // remove /ddd recursively // [ '/aaa' ]
提供幫助信息
var program = require('commander'); program .version('0.1.0') .helpOption('-h,--HELP') .option('-f, --foo', 'enable some foo') .option('-b, --bar', 'enable some bar') .option('-B, --baz', 'enable some baz'); program.parse(process.argv); // 執行命令 // node index -h 或 node index --HELP /* 輸出結果 * Options: * -V, --version output the version number * -f, --foo enable some foo * -b, --bar enable some bar * -B, --baz enable some baz * -h,--HELP output usage information */
輸出幫助信息並當即退出。可選的回調cb容許在顯示幫助文本以前對其進行後處理。helpOption
也提供長名
,-h,--HELP
前面爲短名
後面爲長名
調用時使用兩這都是能夠的。與version
的使用是相似的。
用來描述命令,也就是命令的說明,上面說過command
第二個參數也一樣是命令的描述,當與description
同時存在的話,則會優先於第二個參數的描述,description
則會做爲全局描述在最頂部顯示。該描述只用在使用-HELP
的時候才能看見。
var program = require('commander'); program .version('0.0.1', '-V, --version') .command('rm <dir>',"arg is description") .description("this is description") .option('-r, --recursive', 'Remove recursively') .action(function (dir, cmd) { console.log('remove ' + dir + (cmd.recursive ? ' recursively' : '')) }); program.parse(process.argv); // 執行命令 // node index -h // 輸出結果 /* this is description Options: -V, --version output the version number -r, --recursive Remove recursively -h, --help output usage information Commands: rm <dir> arg is description help [cmd] display help for [cmd] */
經過上面的輸出結果能夠看的出,rm
命令最後的描述是arg is description
,若刪除第二個參數則會輸出this is description
。
用於捕獲option
與command
,當其被使用賊會被觸發函數。
var program = require('commander'); program .version('0.0.1', '-V, --version') .command('rm <dir>',"arg is description") .option('-r, --recursive', 'Remove recursively') .option('-g, --git [type]', 'Add [marble]', 'Angie') .option('-a, --am',"ampm") .action(() => { console.log(123) }); program.on('option:am', function () { console.log("on:am") }); program.on('option:recursive', function () { console.log("option:recursive") }); program.on('command:rm', function () { console.log("command:rm") }); program.on('option:git', function () { console.log("option:git") }); program.on('command:*', function () { console.log(987) console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); process.exit(1); }); program.on('--help', function() { console.log('****************'); console.log('Examples:'); console.log('****************'); console.log(' $ deploy exec sequential'); console.log(' $ deploy exec async'); }); program.parse(process.argv);
分別執行command
和option
,會依次觸發對應的函數,可是command:*
具體是何時觸發的?
command
和option
已經定義可是沒有進行事件捕獲時會觸發以上狀況就會觸發command:*
對應的事件,option:
牢牢跟隨的是option
的長名。纔會捕獲到該事件。
開發本地模塊
建立項目文件以下:
├─bin │ └─init-project.js ├─lib │ └─install.js └─templates └─dome1
建立好項目目錄之後,安裝以下依賴包:
命令:npm install --save-dev chalk commander fs-extra through2 vinyl-fs which path
首先在init-project.js
中第一行添加#! /usr/bin/env node
,這是用來指定腳本的執行程序,這裏的Node
能夠用!/usr/bin/node
,若用戶將Node
安裝在非默認路徑下會找不到Node
。So~最好選擇env
環境變量查找Node
安裝目錄。
init-project.js
#! /usr/bin/env node // 引入依賴 var program = require('commander'); var vfs = require('vinyl-fs'); var through = require('through2'); const chalk = require('chalk'); const fs = require('fs-extra'); const path = require('path'); // 定義版本號以及命令選項 program .version('1.0.0') .option('-i --init [name]', 'init a project', 'myFirstProject') program.parse(process.argv); if (program.init) { // 獲取將要構建的項目根目錄 var projectPath = path.resolve(program.init); // 獲取將要構建的的項目名稱 var projectName = path.basename(projectPath); console.log(`Start to init a project in ${chalk.green(projectPath)}`); // 根據將要構建的項目名稱建立文件夾 fs.ensureDirSync(projectName); // 獲取本地模塊下的demo1目錄 var cwd = path.join(__dirname, '../templates/demo1'); // 從demo1目錄中讀取除node_modules目錄下的全部文件並篩選處理 vfs.src(['**/*', '!node_modules/**/*'], { cwd: cwd, dot: true }). pipe(through.obj(function (file, enc, callback) { if (!file.stat.isFile()) { return callback(); } this.push(file); return callback(); })) // 將從demo1目錄下讀取的文件流寫入到以前建立的文件夾中 .pipe(vfs.dest(projectPath)) .on('end', function () { console.log('Installing packages...') // 將node工做目錄更改爲構建的項目根目錄下 process.chdir(projectPath); // 執行安裝命令 require('../lib/install'); }) .resume(); }
install.js
// 引入依賴 var which = require('which'); const chalk = require('chalk'); var childProcess = require('child_process'); // 開啓子進程來執行npm install命令 function runCmd(cmd, args, fn) { args = args || []; var runner = childProcess.spawn(cmd, args, { stdio: 'inherit' }); runner.on('close', function (code) { if (fn) { fn(code); } }) } // 查找系統中用於安裝依賴包的命令 function findNpm() { var npms = ['tnpm', 'cnpm', 'npm']; for (var i = 0; i < npms.length; i++) { try { // 查找環境變量下指定的可執行文件的第一個實例 which.sync(npms[i]); console.log('use npm: ' + npms[i]); return npms[i] } catch (e) { } } throw new Error(chalk.red('please install npm')); } var npm = findNpm(); runCmd(which.sync(npm), ['install'], function () { console.log(npm + ' install end'); })
完成如上代碼以後,更改package.json
添加屬性以下:
{ "bin": { "q-init": "./bin/init-project.js" } }
注:自定義指令後指定的文件,必定要添加.js
後綴文件名,不然會拋出錯誤。
接下來剩下的就是測試了,對於測試來講不須要把安裝包推到npm
中,npm
爲了方便,提供了npm link
命令,能夠實現預發佈
。在項目根目錄中使用npm link
沒有報錯的話,就說明推送成功了。如今就能夠在全局中使用q-init
了。
在全局中使用initP -h
命令,可以輸出所編譯的help
信息就說明能夠初始化項目了。
Usage: init-project [options] Options: -V, --version output the version number -i --init [name] init a project (default: "myFirstProject") -h, --help output usage information
總結
commander
在Vue-cli、creat-app(react)
中都起到了很大的做用,這種建立腳手架的方式與vue-cli
的方式不一樣,vue-cli
則是使用git
遠程拉取項目再完成初始化,這樣一來要比這種更加的方便靈活,每次模板變動不須要再次上傳包,只須要更改git
倉庫就行了,方便快捷。