寫一個屬於你的前端腳手架工具

你可能用到過不少前端腳手架工具,有沒有試想過到底如何寫一個屬於你的腳手架呢?javascript

腳手架依賴工具

commander.js 命令行工具前端

download-git-repo git倉庫代碼下載java

chalk 命令行輸出樣式美化node

Inquirer.js 命令行交互react

ora 命令行加載中效果git

項目搭建

初始化項目

建立項目目錄後執行npm init按照提示完成初始化項目。github

安裝依賴

安裝上面咱們提到過的這幾個腳手架依賴工具,執行npm install chalk commander download-git-repo inquirer ora --save完成npm

安裝。json

項目結構

項目初始化完成後,建立bin文件和commands文件。bin文件爲可執行命令入口目錄,commands則負責編寫一些命令交互。後端

bin 目錄下 react-cli 文件

#!/usr/bin/env node
process.env.NODE_PATH = __dirname + '/../node_modules/'
const { resolve } = require('path')
const res = command => resolve(__dirname, '../commands/', command)
const program = require('commander')

program.version(require('../package').version )

program.usage('<command>')

program.command('init')
  .option('-f, --foo', 'enable some foo')
  .description('Generate a new project')
  .alias('i')
  .action(() => {
    require(res('init'))
  })

if(!program.args.length){
  program.help()
}
複製代碼

建立該react-cli.js爲可執行命令入口文件,而且定義了一個'init'命令,執行命令後會去commands 目錄下尋找對應的init.js文件

commands 目錄下 init 文件

const {prompt} = require('inquirer')
const program = require('commander')
const chalk = require('chalk')
const download = require('download-git-repo')
const ora = require('ora')
const fs = require('fs')
const path = require('path')

const option =  program.parse(process.argv).args[0]
const defaultName = typeof option === 'string' ? option : 'react-project'
const tplList = require(`${__dirname}/../templates`)
const tplLists = Object.keys(tplList) || [];
const question = [
  {
    type: 'input',
    name: 'name',
    message: 'Project name',
    default: defaultName,
    filter(val) {
      return val.trim()
    },
    validate(val) {
      const validate = (val.trim().split(" ")).length === 1
      return validate || 'Project name is not allowed to have spaces ';
    },
    transformer(val) {
      return val;
    }
  }, {
    type: 'list',
    name: 'template',
    message: 'Project template',
    choices: tplLists,
    default: tplLists[0],
    validate(val) {
      return true;
    },
    transformer(val) {
      return val;
    }
  }, {
    type: 'input',
    name: 'description',
    message: 'Project description',
    default: 'React project',
    validate (val) {
      return true;
    },
    transformer(val) {
      return val;
    }
  }, {
    type: 'input',
    name: 'author',
    message: 'Author',
    default: 'project author',
    validate (val) {
      return true;
    },
    transformer(val) {
      return val;
    }
  }
]
module.exports = prompt(question).then(({name, template, description, author}) => {
  const projectName = name;
  const templateName = template;
  const gitPlace = tplList[templateName]['place'];
  const gitBranch = tplList[templateName]['branch'];
  const spinner = ora('Downloading please wait...');
  spinner.start();
  download(`${gitPlace}${gitBranch}`, `./${projectName}`, (err) => {
    if (err) {
      console.log(chalk.red(err))
      process.exit()
    }
    fs.readFile(`./${projectName}/package.json`, 'utf8', function (err, data) {
      if(err) {
        spinner.stop();
        console.error(err);
        return;
      }
      const packageJson = JSON.parse(data);
      packageJson.name = name;
      packageJson.description = description;
      packageJson.author = author;
      var updatePackageJson = JSON.stringify(packageJson, null, 2);
      fs.writeFile(`./${projectName}/package.json`, updatePackageJson, 'utf8', function (err) {
        if(err) {
          spinner.stop();
          console.error(err);
          return;
        } else {
          spinner.stop();
          console.log(chalk.green('project init successfully!'))
          console.log(` ${chalk.bgWhite.black(' Run Application ')} ${chalk.yellow(`cd ${name}`)} ${chalk.yellow('npm install')} ${chalk.yellow('npm start')} `);
        }
      });
    });
  })
})
複製代碼

1.program.parse(process.argv) 能夠解析執行init 時候傳入的參數, 咱們能夠拿到這個參數作爲項目建立的目錄名,若是沒有傳入該參數則爲其設置一個默認目錄名稱。

2. 命令行交互問答

  • question 數組爲交互命令配置,數組中每個對象都對應一個執行命令時候的一個問題
  • type爲該提問的類型,name爲該問題的名字,能夠在後面經過name拿到該問題的用戶輸入答案
  • message爲問題的提示
  • default則爲用戶沒輸入時的默認爲其提供一個答案
  • validate方法能夠校驗用戶輸入的內容,返回true時校驗經過,若不正確能夠返回對應的字符串提示文案
  • transformer爲用戶輸入問題答案後將對應的答案展現到問題位置,須要有返回值,返回到字符串爲展現內容 具體使用文檔

3. 問答結束的回調

  • prompt方法中then裏的參數是一個對象,能夠由此拿到問題由name定義的用戶輸入內容。
  • 根據用戶輸入的內容,能夠對應爲其生成下載模版,這裏使用download-git-repo工具來下載git倉庫代碼
  • download方法第一個參數爲要下載代碼倉庫位置,若是爲GitHub代碼倉庫只須要寫用戶名和項目名稱便可,如'Hzy0913/react-template'即爲下載該倉庫master的代碼,若是須要切換對應分支則在倉庫地址後面加入對應分支名,如'Hzy0913/react-template#complete'
  • download方法第二個參數爲生成下載文件的文件名,我將他保存在命令執行目錄下,文件名使用用戶輸入的名字,如參數爲'./projectName',便可在當前執行命令目錄下生成對應的文件名。
  • ora模塊能夠爲咱們生成下載時候的旋轉圖標,ora方法傳入的第一個參數爲等待時候的提示文案並生成實例,在實例對象上調用start()方法開始出現旋轉動畫和提示,stop()方法中止。
  • 模版下載好之後須要爲package.json文件生成用戶自定義輸入的內容,node的fs模塊的readFile方法能夠幫助咱們獲取生成文件的內容,writeFile則能夠寫入內容
  • 最後完成後能夠在命令行面板上使用console方法給出一些提示內容,chalk 模塊能夠幫助咱們美化輸出內容。

文章中腳手架示例代碼能夠見 build-react-cli

工具測試及發佈

  • 在寫的過程當中免不了在咱們本地進行測試,由於自己項目爲node的工具,咱們能夠在項目目錄下執行 node bin/react-cli運行。
  • 固然在發佈之後確定不能使用該命令,此時在發佈前,添加package.json中的bin對象,key爲腳本執行的名字,value爲執行目錄,如"bin": {"build-react": "bin/react-cli"} ,便可在輸入build-react的時候等同於執行 node bin/react-cli命令,在咱們全局安裝腳手架的時候,bin對象裏面的內容便可變成全局可執行命令。
  • 發佈npm包,npm包發佈很是簡單,註冊npm帳號後本地登陸便可,在項目目錄下執行npm publish便可發佈,注意包名不能與現有的npm裏的相同、每次發佈新版本的包時須要修改package.json裏的版本號,發佈的包只有在24小時內能夠刪除。

最後推薦兩個我寫的腳手架工具

build-react-cli是幫助你快速建立生成react項目的腳手架工具,配置了多種可選擇的不一樣類型項目模版。

LiveNode超簡單的前端跨域、先後端分離解決方案

相關文章
相關標籤/搜索