隨着前端技術的發展,工程化逐漸成為了一種趨勢。但在實際開發時,搭建項目是一件很繁瑣的事情,尤其是在對一個框架的用法還不熟悉的時候。於是很多框架都自帶一套腳手架工具,在初始化前端項目的時候就可以不用自己從頭搭建,只要在命令行輸入初始化命令即可。
那么,如果想自行開發出這樣一個命令行工具來初始化自定義項目,該怎么做呢?研究的過程中,偶然間發現了 commander.js 這個模塊,可以幫助命令行工具的開發。於是邊研究邊整理了這篇筆記。
一、commander.js的基本用法
1. 安裝
mkdir commander-example && cd commander-example
npm install commander --save
2. 使用
新建一個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')
參數解析:
① 版本號<必須>
② 自定義標志<可省略>:默認為 -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)
二、使用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'); })
三、構建項目demo
此demo用於初始化項目副本,因此可以根據自己的需要構建。我們可以利用一些腳手架工具來初始化項目,也可以自己一步步搭建。
將搭建的項目復制進templates目錄下(node_modules下的文件及文件夾可以不用復制),並重命名為demo1;然后在init-commander-tool目錄下運行 node bin\init-project --help 測試所有命令是否能正常顯示。
四、全局使用
在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
即可,項目名稱可以自己定義。