隨着前端技術的發展,工程化逐漸成爲了一種趨勢。但在實際開發時,搭建項目是一件很繁瑣的事情,尤爲是在對一個框架的用法還不熟悉的時候。因而不少框架都自帶一套腳手架工具,在初始化前端項目的時候就能夠不用本身從頭搭建,只要在命令行輸入初始化命令便可。前端
那麼,若是想自行開發出這樣一個命令行工具來初始化自定義項目,該怎麼作呢?研究的過程當中,偶然間發現了 commander.js 這個模塊,能夠幫助命令行工具的開發。因而邊研究邊整理了這篇筆記。node
1、commander.js的基本用法
1. 安裝npm
mkdir commander-example && cd commander-example npm install commander --save
2. 使用json
新建一個bin目錄,而後在該目錄下新建一個test.js文件,文件內容:數組
// 引入依賴 var program = require('commander'); // 定義版本和參數選項 program .version('0.1.0', '-v, --version') .option('-i, --init', 'init something') .option('-g, --generate', 'generate something') .option('-r, --remove', 'remove something'); // 必須在.parse()以前,由於node的emit()是即時的 program.on('--help', function(){ console.log(' Examples:'); console.log(''); console.log(' this is an example'); console.log(''); }); program.parse(process.argv); if(program.init) { console.log('init something') } if(program.generate) { console.log('generate something') } if(program.remove) { console.log('remove something') }
而後在命令行裏輸入測試:框架
node bin\test --help
獲得以下結果:函數
Usage: test [options] Options: -v, --version output the version number -i, --init init something -g, --generate generate something -r, --remove remove something -h, --help output usage information Examples:
3. API解析工具
· version測試
做用:定義命令程序的版本號
用法示例:.version('0.0.1', '-v, --version')
參數解析:
① 版本號<必須>ui
② 自定義標誌<可省略>:默認爲 -V 和 --version
· option
做用:用於定義命令選項
用法示例:.option('-n, --name<path>', 'name description', 'default name')
參數解析:
① 自定義標誌<必須>:分爲長短標識,中間用逗號、豎線或者空格分割;標誌後面可跟必須參數或可選參數,前者用 <> 包含,後者用 [] 包含
② 選項描述<省略不報錯>:在使用 --help 命令時顯示標誌描述
③ 默認值<可省略>
· command
做用:添加命令名稱
用法示例:.command('rmdir <dir> [otherDirs...]', 'install description', opts)
參數解析:
① 命令名稱<必須>:命令後面可跟用 <> 或 [] 包含的參數;命令的最後一個參數能夠是可變的,像實例中那樣在數組後面加入 ... 標誌;在命令後面傳入的參數會被傳入到 action 的回調函數以及 program.args 數組中
② 命令描述<可省略>:若是存在,且沒有顯示調用action(fn),就會啓動子命令程序,不然會報錯
③ 配置選項<可省略>:可配置noHelp、isDefault等
· description
做用:定義命令的描述
用法示例:.description('rmdir desc')
· action
做用:定義命令的回調函數
用法示例:.action(fn)
· parse
做用:用於解析process.argv,設置options以及觸發commands
用法示例:.parse(process.argv)
2、使用commander.js開發本地模塊init-commander-tool
1. 新建目錄以下:
init-commander-tool |-bin |-init-project.js |-lib |-install.js |-templates
2. 運行 npm init 來初始化項目,項目名稱設置爲init-commander-tool,並安裝依賴包:
``` "devDependencies": { "chalk": "^2.4.1", "commander": "^2.15.1", "fs-extra": "^6.0.1", "path": "^0.12.7", "through2": "^2.0.3", "vinyl-fs": "^3.0.3", "which": "^1.3.1" } ```
npm init npm install
3. init-porject.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(); }
· #! /usr/bin/env node
指定腳本的執行程序,這裏是node。也能夠用!/usr/bin/node,但若是用戶將node安裝在非默認路徑下,會找不到node。因此最好選擇用env(包含環境變量)來查找node安裝目錄。
· vinyl-fs:
Vinyl用於描述文件的元數據對象;該模塊主要暴露了兩個方法src和dest,它們各自返回數據流;不一樣的是前者提供Vinyl對象,後者使用Vinyl對象;簡單的說就是一個讀取文件,另外一個往磁盤寫文件
param@src:src(globs[, options])
第一個參數爲字符串或字符串數組,代表文件位置。若是是數組,則會按照從前到後的順序來執行,但帶有!(非)符號的路徑應該放在後面。第二個參數爲選項對象,查看具體配置
param@dest:dest(folder[, options])
第一個參數爲文件夾路徑或函數,若是是後者,則它會被用於處理每個Vinyl文件對象,且該函數必須返回一個文件夾路徑。第二個參數爲選項對象,查看具體配置。該方法返回文件對象流,並將他們寫入磁盤以及傳遞到管道下游,因此你能夠繼續在管道中進行操做。若是文件擁有symlink屬性,就會建立一個符號連接。
· through2:
through2主要是對node中streams.Transform的簡單封裝,讓其使用起來更加簡單。具體用法可查看through2**
4. 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'); })
3、構建項目demo
此demo用於初始化項目副本,所以能夠根據本身的須要構建。咱們能夠利用一些腳手架工具來初始化項目,也能夠本身一步步搭建。
將搭建的項目複製進templates目錄下(node_modules下的文件及文件夾能夠不用複製),並重命名爲demo1;而後在init-commander-tool目錄下運行 node bin\init-project --help 測試全部命令是否能正常顯示。
4、全局使用
在package.json裏面添加bin字段:
// bin項用來指定各個內部命令對應的可執行文件的位置 "bin": { "initP": "./bin/init-project" },
而後在init-commander-tool目錄下運行 npm link
,將本地模塊連接到全局環境下,這樣就能夠在任何地方使用initP命令了。
在命令行中鍵入:initP --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
最後在須要初始化項目的地方運行 initP --init myProject
便可,項目名稱能夠本身定義。