隨着NodeJs
的不斷發展,對於前端來說要做的東西也就更多,Vue
腳手架React
腳手架等等等一系列的東西都脫穎而出,進入到人們的視野當中,對於這些腳手架工具來講也只是停留在應用階段,從來沒有想過腳手架是如何實現的?vue init webpack 項目名稱
是如何通過這樣的命令創建了一個項目,其最重要的模塊就是今天要說的Commander
。
Commander
模塊又國外TJ
大神所編寫
項目地址:Commander
Commander基本用法
Commander
文檔寫的很詳細,跟着文章詳細的學習一下,Commander
是一個Nodejs
模塊,需要在Node
環境中運行,在使用前確認一下Node
環境是否已安裝。
安裝依賴
npm install commander --save
Options 解析
在Commander
模塊下存在option
方法用來定義commander
的選項options
,用來作為選項的文檔。
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
是一個數組。
打印輸出一下process.argv
和program.args
並查看了一下輸出結果如下,使用如下命令運行一下文件:
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
方法可以接收三個參數:
- 自定義標志
必須
:分為長短標識,中間用逗號、豎線或者空格分割;標志后面可跟必須參數或可選參數,前者用<>
包含,后者用[]
包含。 - 選項描述
省略不報錯
:在使用 --help 命令時顯示標志描述 - 默認值
可省略
:當沒有傳入參數時則會使用默認值
若我們執行node index -g
得到的結果則是Angie git
其第三個參數則作為了默認值填寫在了對應的位置上。除了上面所說還可以使用如下命令:
// 執行 -g 參數 a
// 執行 -b 參數 s
node index -g a -b s
// 執行 -g和-b 傳入a參數給-g
// -b 參數暫時不知道怎么傳入
node index -gb a
版本選項
調用版本會默認將-V
和--version
選項添加到命令中。當存在這些選項中的任何一個時,該命令將打印版本號並退出。
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');
command 添加命令名稱
該方法允許使用命令行去執行一段命令,也就是一段:
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' ]
helpOption 幫助
提供幫助信息
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
的使用是類似的。
description 命令描述
用來描述命令,也就是命令的說明,上面說過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
創建好項目目錄以后,安裝如下依賴包:
- chalk
- commander
- fs-extra
- path
- through2
- vinyl-fs
- which
命令: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
倉庫就好了,方便快捷。