記一次nodejs開發CLI的過程

你們新年好!
年前在工做中接到任務要開發一個本身的CLI,便去了解了一下。發現並不難,只需運用nodejs的相關api便可。node

目前實現的功能爲:git

  1. 輸入 new 命令從github下載一個腳手架模版,而後建立對應的app。
  2. 輸入 create 命令能夠快速的建立一些樣板文件。

下面將分步去解析一個CLI的製做過程,也算是一次記錄回憶的過程。github

1.建立CLI項目

用 npm init 生成項目的package.json文件。而後編輯該文件主要加上npm

"bin": {
    "jsm": "./bin/jsm.js"
  },
複製代碼

而後在當前目錄建立你本身的腳本文件,對應上述配置爲 mkdir bin && touch bin/jsm.js
編輯建立好的文件,加上json

#!/usr/bin/env node
console.log('Hello CLI')
複製代碼

接下來在項目的根目錄運行一下 npm i -g, 如今就能夠在命令行使用jsm命令了。
注意: 必定要在開頭加上#!/usr/bin/env node, 不然沒法運行。api

詳解:package.json文件只有加上了bin字段,才能在控制檯使用你的命令,對應的這裏的命令就是jsm,對應的執行文件爲bin/jsm.js。 其實"jsm"命令就是 "node bin/jsm.js" 的別稱,只有你用npm i -g全局安裝後才能夠用,開發的過程當中直接用node bin/jsm.js便可。app

2.解析命令參數

一個CLI須要經過命令行輸入各類參數,能夠直接用nodejs的process相關api進行解析,可是更推薦使用commander這個npm包能夠大大簡化解析的過程。 npm i commander安裝, 而後更改以前的腳本文件添加函數

const program = require('commander');

 program
  .command('create <type> [name] [otherParams...]')
  .alias('c')
  .description('Generates new code')
  .action(function (type, name, otherParams) {
    console.log('type', type);
    console.log('name', name);
    console.log('other', otherParams);
    // 在這裏執行具體的操做
  });

program.parse(process.argv);
複製代碼

如今在終端執行一下 node bin/jsm.js c component myComponent state=1 title=HelloCLI 應該就能看到輸入的各類信息信息了。至此命令解析部分就基本ok了,其餘更多的用法能夠參考官方例子ui

詳解:command第一個參數爲命令名稱,alias爲命令的別稱, 其中<>包裹的爲必選參數 []爲選填參數 帶有...的參數爲剩餘參數的集合。spa

3.下載模版建立文件

接下來須要根據上一步輸入的命令去作一些事情。具體到一個腳手架CLI通常主要作兩件事,快速的生成一個新項目和快速的建立對應的樣板文件。 既然須要建立文件就少不了對nodejs的fs模塊的運用,這裏用的一個加強版的fs-extra

下面封裝兩個經常使用的文件處理函數

//寫入文件
function write(path, str) {
  fs.writeFileSync(path, str);
}
//拷貝文件
function copyTemplate(from, to) {
  from = path.join(__dirname, from);
  write(to, fs.readFileSync(from, 'utf-8'));
}
複製代碼

3.1 生成一個新項目

命令以下

program
  .command('new [name]')
  .alias('n')
  .description('Creates a new project')
  .action(function (name) {
    const projectName = name || 'myApp';
    init({ app: projectName })
  });
複製代碼

init函數主要作了兩件事:

  • 從github下載一個腳手架模版。(如用本地的腳手架模版可省略此步驟)
  • 拷貝腳手架文件到命令指定的目錄並安裝相應的依賴包。
const fs = require('fs-extra');
const chalk = require('chalk');
const {basename, join} = require('path');
const readline = require('readline');
const download = require('download-git-repo');
const ora = require('ora');
const vfs = require('vinyl-fs');
const map = require('map-stream');
const template = 'stmu1320/Jsm-boilerplate';

// 建立函數
function createProject(dest) {
  const spinner = ora('downloading template')
  spinner.start()
  if (fs.existsSync(boilerplatePath)) fs.emptyDirSync(boilerplatePath)
  download(template, 'boilerplate', function (err) {
    spinner.stop()
    if (err) {
      console.log(err)
      process.exit()
    }

    fs
    .ensureDir(dest)
    .then(() => {
      vfs
        .src(['**/*', '!node_modules/**/*'], {
          cwd: boilerplatePath,
          cwdbase: true,
          dot: true,
        })
        .pipe(map(copyLog))
        .pipe(vfs.dest(dest))
        .on('end', function() {
          const app = basename(dest);
          const configPath = `${dest}/config.json`;
          const configFile = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
          configFile.dist = `../build/${app}`;
          configFile.title = app;
          configFile.description = `${app}-project`;
          write(configPath, JSON.stringify(configFile, null, 2));
          // 這一部分執行依賴包安裝,具體代碼請查看文末連接
          message.info('run install packages');
          require('./install')({
            success: initComplete.bind(null, app),
            cwd: dest,
          });
        })
        .resume();
    })
    .catch(err => {
      console.log(err);
      process.exit();
    });
})
}

function init({app}) {
  const dest = process.cwd();
  const appDir = join(dest, `./${app}`);
  createProject(appDir);
}
複製代碼

3.2 快速生成樣板文件

生成樣板文件這一部分,其實就是拷貝一個文件到指定的地方而已,固然還應該根據參數改變文件的具體內容。

program
  .command('create <type> [name] [otherParams...]')
  .alias('c')
  .description('Generates new code')
  .action(function (type, name, otherParams) {
    const acceptList = ['component', 'route']
    if (!acceptList.find(item => item === type)) {
      message.light('create type must one of [component | route]')
      process.exit()
    }
    const params = paramsToObj(otherParams)
    params.name = name || 'example'
    generate({
      type,
      params
    })
  });

//生成文件入口函數
function generate({type, params}) {
  const pkgPath = findPkgPath(process.cwd())
  if (!pkgPath) {
    message.error('No \'package.json\' file was found for the project.')
    process.exit()
  }
  const dist = path.join(pkgPath, `./src/${type}s`);
  fs
    .ensureDir(dist)
    .then(() => {
      switch (type) {
        case 'component':
          // 具體代碼請查看文末連接
          createComponent(dist, params);
          break;

        case 'route':
          createRoute(dist, params);
          break;

        default:
          break;
      }
    })
    .catch(err => {
      console.log(err);
      process.exit(1);
    });
}
複製代碼

到這裏一個基本的腳手架CLI就差很少了,剩下的是幫助信息等友好提示的東西了。文章的全部源碼點擊這裏
也歡迎你們安裝試用一下 npm i -g jsm-cli

相關文章
相關標籤/搜索