用commander.js構建本身的腳手架工具

隨着前端技術的發展,工程化逐漸成爲了一種趨勢。但在實際開發時,搭建項目是一件很繁瑣的事情,尤爲是在對一個框架的用法還不熟悉的時候。因而不少框架都自帶一套腳手架工具,在初始化前端項目的時候就能夠不用本身從頭搭建,只要在命令行輸入初始化命令便可。前端

那麼,若是想自行開發出這樣一個命令行工具來初始化自定義項目,該怎麼作呢?研究的過程當中,偶然間發現了 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 便可,項目名稱能夠本身定義。

相關文章
相關標籤/搜索