帶你了解項(xiàng)目腳手架的開發(fā)過程

背景介紹

每次開發(fā)新的項(xiàng)目,類型多樣,有vue的、也有小程序的,雖然已經(jīng)有各種類型的項(xiàng)目模板,但每次都需要去下載模板然后修改配置,就顯得特別麻煩,于是開發(fā)一個(gè)腳手架去管理這些項(xiàng)目。

解決思路

研究過vue-cli的一些代碼,它將項(xiàng)目模板和腳手架分離,模板存放在git上,然后在與用戶交互的時(shí)候下載不同的模板,這樣模板可以單獨(dú)維護(hù),即使有更新,也不用更新腳手架。

借鑒vue-cli就可以實(shí)現(xiàn)簡(jiǎn)單的項(xiàng)目管理了。

依賴庫

  • commander:解析命令行的命令和參數(shù),用于處理用戶輸入的指令;
  • Inquirer:通用的命令行用戶界面集合,用于和用戶進(jìn)行交互;
  • download-git-repo:下載git倉庫;
  • shellshell命令執(zhí)行;
  • ora:終端loading動(dòng)畫;
  • chalk:終端樣式;
  • log-symbols:給終端加上彩色符號(hào);
  • fs-extra:文件操作;

初始化

yarn init
yarn add commander inquirer download-git-repo handlebars shelljs ora chalk

此時(shí)目錄下就會(huì)生成好package.json文件,并且安裝好相關(guān)依賴。

cli.jpg

打開package.json增加bin字段,定義命令名和執(zhí)行文件

{
  "name": "lp-cli",
 ...
  "bin": {
    "lp-create-project": "bin/index.js"
  },
  "scripts": {
    "test": "node bin/index.js create abc"
  },
  ...
}

創(chuàng)建bin/index.js文件,并在文件開頭寫上#!/usr/bin/env node,這個(gè)代表這個(gè)文件是node環(huán)境下的腳本文件。

處理命令行

創(chuàng)建一個(gè)命令,輸入create即可,使用方法lp-create-project create [項(xiàng)目名]

const program = require('commander');

program.version(`lp-cli ${require('../package').version}`).usage('<command> [options]');

program
  .command('create [name]')
  .description('創(chuàng)建一個(gè)由lp-cli支持的初始化項(xiàng)目')
  .option('create <項(xiàng)目名>', '創(chuàng)建時(shí)輸入項(xiàng)目名')
  .action((name) => {
    if(!name){
      console.log(
        `\n  Usage: create [projectName]\n`
      );
      return
    }
    require('../lib/create')(name);
  });
program.parse(process.argv);

入口文件只做命令行處理,具體其它步驟抽離出來。

命令行交互

這部分就靠inquirer實(shí)現(xiàn),lib/question.js構(gòu)建問答系統(tǒng),收集交互過程中用戶提交選擇的信息。

const inquirer = require('inquirer');

module.exports = () => {
  return inquirer.prompt([
    {
      type: 'list',
      message: '請(qǐng)選擇項(xiàng)目類型',
      name: 'type',
      choices: ['vue', 'wxapp'],
    },
    {
      type: 'confirm',
      message: '是否初始化 git 倉庫',
      name: 'ifGitInit',
      default: true,
    },
    {
      type: 'confirm',
      message: '下載完成是否自動(dòng)安裝依賴包',
      name: 'ifInstall',
      default: true,
    },
    {
      type: 'list',
      message: '請(qǐng)選擇安裝方式',
      name: 'installWay',
      choices: ['yarn', 'npm'],
      when: (answers) => {
        return answers.ifInstall;
      },
    },
  ]);
};

功能實(shí)現(xiàn)

lib/generator.js生成器中根據(jù)用戶的輸入實(shí)現(xiàn)具體的功能。

  • 下載項(xiàng)目
const download = require('download-git-repo');
download(gitRepo[this.answers.type], this.answers.name, (err) => {
    if (err) {
        console.log(symbols.error, chalk.red('項(xiàng)目創(chuàng)建失敗'));
    } else {
        console.log(symbols.success, chalk.green('項(xiàng)目創(chuàng)建成功'));
    }
});
  • git初始化
const shell = require('shelljs');
shell.cd(this.answers.name).exec('git init', (err) => {
    if (err) {
        console.log('git倉庫創(chuàng)建失敗');
    } else {
        console.log(symbols.success, chalk.green('git 倉庫初始化成功'));
    }
});
  • 是否安裝依賴包
const shell = require('shelljs');
shell.cd(this.answers.name).exec(`${this.answers.installWay == 'yarn' ? 'yarn' : 'npm i'}`, (err) => {
        if (err) {
          console.log('依賴包安裝失敗');
        } else {
          console.log(symbols.success, chalk.green('依賴包安裝成功'));
        }
    });
}

視覺美化后的完整功能

// 下載模板
const download = require('download-git-repo');
// 命令行操作
const shell = require('shelljs');
// 顯示提示圖標(biāo)
const symbols = require('log-symbols');
// 字體加顏色
const chalk = require('chalk');
// 動(dòng)畫效果
const ora = require('ora');

const gitRepo = {
  vue: 'MonsterNO/vue-demp',
  wxapp: 'MonsterNO/pro_template',
};

module.exports = class Generator {
  constructor(answers) {
    this.answers = answers;
  }
  //下載
  downloadGit() {
    let spinner = ora('下載中...');
    return new Promise((resolve, reject) => {
      download(gitRepo[this.answers.type], this.answers.name, (err) => {
        if (err) {
          spinner.fail();
          console.log(symbols.error, chalk.red('項(xiàng)目創(chuàng)建失敗'));
          reject(err);
        } else {
          spinner.succeed();
          console.log(symbols.success, chalk.green('項(xiàng)目創(chuàng)建成功'));
          resolve();
        }
      });
    });
  }
  //git初始化
  gitInit() {
    if (this.answers.ifGitInit) {
      shell.cd(this.answers.name).exec('git init', (err) => {
        if (err) {
          console.log(symbols.error, chalk.red(err));
        } else {
          console.log(symbols.success, chalk.green('git 倉庫初始化成功'));
        }
      });
    }
  }
  // 安裝依賴包
  addPackage() {
    if (this.answers.ifInstall) {
      let spinner = ora('安裝中...');
      spinner.start();
      shell.cd(this.answers.name).exec(`${this.answers.installWay == 'yarn' ? 'yarn' : 'npm i'}`, (err) => {
        if (err) {
          spinner.fail();
          console.log(symbols.error, chalk.red(err));
        } else {
          spinner.succeed();
          console.log(symbols.success, chalk.green('依賴包安裝成功'));
        }
      });
    }
  }
};

調(diào)用生成器

lib/create.js中調(diào)用執(zhí)行

const Generator = require('./Generator');
module.exports = async (name) => {
  let answers = await question();
  answers.name = name
  let generator = new Generator(answers);
  await generator.downloadGit();
  generator.gitInit();
  generator.addPackage();
};
cli1.png

優(yōu)化

  • 項(xiàng)目文件夾重復(fù)處理

lib/create.js函數(shù)調(diào)用功能函數(shù)前校驗(yàn)項(xiàng)目文件夾是否存在可選擇清理文件夾或者不處理。

const fs = require('fs-extra')
const chalk = require('chalk');
const inquirer = require('inquirer');
module.exports = async (name) => {
  if(fs.existsSync(name)){
    let {action} = await inquirer.prompt([
      {
        name:'action',
        type:'list',
        message:`文件夾${name}已存在,請(qǐng)選擇下一步操作:`,
        choices:[
          {name:'clear',value:'clear'},
          {name:'cancel',value:false}
        ]
      }
    ])
    if(!action){
      return
    }else{
      console.log(`\nRemoving ${chalk.cyan(name)}...`)
      await fs.remove(name)
    }
  }
};
cli2.png
  • 考慮下載項(xiàng)目模板可能會(huì)出錯(cuò),加上異常捕獲
//異常退出程序 
try {
    await generator.downloadGit();
  } catch (err) {
    process.exit(1)
  }

到此這個(gè)腳手架的基本功能已經(jīng)可以正常使用了,同時(shí)可以繼續(xù)研究加入更多的功能,去優(yōu)化整體的一個(gè)功能。

發(fā)布

最后可以通過yarn publish 把腳手架發(fā)布到NPM上就可以使用了。

書洞筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容