详解前端脚手架开发排坑全指南【前端提效必须上干货】

咱们业务中能够经过Vue-cli脚手架快速生成vue项目,一样咱们也能够开发一款cli脚手架用于快速生成咱们平常提炼出来的业务基础模型/架构。本文将详细讲解脚手架如何开发,所涉及到的技术细节和坑以及各类第三方包的讲解,确保即便是小白同窗也能够照着作出来本身的cli。html

装逼大法!提高逼格!!升职加薪!!!赢娶白富美!!!!你还在等什么?快快开始吧~~~前端

感受像作梦同样
感受像作梦同样

言归正传,首先思考一下咱们的脚手架要帮助咱们作什么事情?好比,这里咱们就实现一个vta-cli,经过在终端运行一个vta create my-app就能够初始化咱们日出提炼出来的一套Vue+Ts+ElementUi的RBAC系统的基础项目架构。有了这个目标,咱们就能够拆解要实现的步骤了:vue

  • 支持终端命令vta
  • vta create my-app命令运行后,检查当前文件名的存在与否状况
  • 拉取们git上的模板项目到本地
  • 拷贝当前下载的资源到咱们目标地址
  • 更新package.json等文件内容(好比name、author、version字段更改等)
  • 在项目中初始化Git来管理项目
  • 自动安装当前项目所须要的依赖
  • 运行app

👇下面咱们就一步一步讲解具体实现过程,跟上队别掉队哈~~~node

✨✨初始化项目基础架构

  • 首先建立文件夹,手动点击建立也行,终端运行命令也行:
# 终端建立项目根文件夹并进入到根文件夹
mkdir vta-cli && cd vta-cli  # 建立bin和src文件夹 mkdir bin src  # bin下建立init.js做为脚本的入口文件 cd bin && touch init.js  # 并在init.js中键入以下内容: #!/usr/bin/env node console.log('Hello,my bin!')  # 初始化npm的包管理文件, 根目录下执行 # 该命令会询问你不少配置参数,若是不想询问直接在后面加-y参数便可 npm init 复制代码

项目基本的目录文件夹出来了,说下具体的目录做用,bin文件夹用于存放咱们的命令入口文件,init.js做为入口文件(命名随你),src做为咱们真正实现脚本命令逻辑的地方:linux

vta-cli项目目录文件夹
vta-cli项目目录文件夹
  • 紧接着咱们须要配置package.json文件,在里面添加咱们的脚本命令。打开咱们的package.json文件,在里面添加bin字段:
{
 "bin": {  "vta": "bin/init.js"  }, } 复制代码

这就是咱们定义了vta这个能够在终端运行的脚本命令,即运行vta这个命令的时候,程序会去运行咱们配置的bin/init.js这个脚本文件。其实,根据npm的机制,当install一个包的时候,会自动去查询其定义的bin命令,并把他添加到node_modules/.bin文件中去,做为shell的命令能够去执行。所以当你的包安装到局部的项目中,那么其bin中的命令就是局部可运行的,安装到全局中则变成了全局能够运行的命令。ios

说明一下,并非必定要是js文件,其实在linux系统中一切皆文件,是没有后缀名的规定的,至少为了让“人”好识别而已。git

重点强调一下:init.js文件的第一行,必定是第一行,咱们添加了#!/usr/bin/env node代码,是指定了咱们脚本的运行环境,和自定在咱们运行vta命令的时候添加了node命令做为前缀,即实际运行的是node vtagithub

  • 接下来,为了方便咱们测试,咱们须要将这个包发不到本地的全局环境。咱们能够经过以下命令:
# 终端运行命令(需在当前项目根目录下)
npm link 复制代码

注意,npm link是当咱们当前包link到本地的全局中,就比如如咱们安装依赖时使用了-g参数把一些包装到了全局环境同样,是用来方便咱们本地开发时测试的,他可让咱们开发的时候自动热更新。若是不清楚npm link的小伙伴,能够去npm官网查查npm link的用法再继续往下学习。vue-cli

可是,我想说的时候,不少小伙伴在这块可能会踩坑:typescript

  • 首先,最好把你的npm的镜像源改成npm自己的镜像源(若是你指定了为淘宝等其余的话);特别是你须要发布npm仓库的时候会失败。
  • 其次,必定要在package.json的配置中把node_modules等无关的文件夹去掉(或者指定咱们须要的),也能够经过.gitignore等配置文件忽略掉也能够,或者.npmrc等。在哪里设置均可以,由于npm配置取值是有一套前后顺序的规则,有兴趣的话能够移步npm文档查阅。这里演示一下如何在package.json文件的配置:
{
 "files": [  "./bin",  "./src"  ], } 复制代码

咱们经过在package.json文件中指定files文件夹目录,即告诉npm咱们实际应该包含的真正文件有哪些,好比咱们只须要bin和src文件夹,一些默认的文件像package.json啊,其余的一些基础配置文件啊,即便你不添加,也会被默认包含进来的。这也是当咱们把这个包发布到npm所须要配置的,也就是须要哪些文件发布到npm仓库上。

注意,也能够经过排除的字段,exclude。可是,不少时候指定咱们须要哪些文件,可能更为方便哦!再强调一遍,node_modules必定要排除掉,否则npm link会巨慢并且会失败的几率大,当心踩坑~~

说的彷佛颇有道理表情包
说的彷佛颇有道理表情包
  • 测试命令
# 终端运行
vta  # 那么脚本执行后,便会看到终端的输出 # 说明脚本执行成功了 复制代码

再次强调,init.js首行必定要添加沙棒,以下:

#!/usr/bin/env node
console.log('运行测试') 复制代码

❤️❤️命令行界面的解决方案

commander.js是nodejs命令行界面的一个完整解决方案。能够帮助咱们定义各类命令行命令/参数等等。好比咱们想定义create命令啊,或者-v做为版本号查询的参数等等。那就先看下怎么使用吧:

  • 安装
cnpm install commander -S
复制代码
  • 引入使用
// 在init.js中引入
const { Command } = require('commander'); // 导入当前根目录下的package.json文件, // 为了获取对应的字段值,好比版本version const package = require('../package'); // 初始化 const program = new Command(); 复制代码
  • 定义版本命令和help命令的说明信息
// 
// 如此, program  .version(package.version, '-v, --version', 'display version for vta-cli')  .usage('<command> [options]'); //  复制代码

经过调用version方法,定义命令行命令版本的功能,咱们即可以在命令行输入vta -v获得当前的版本信息。

调用usage方法,是定义的咱们的辅助命令(help)的提示的文案标题,相似于定义table的表头的感受,以下图,当咱们输入vta -h时,就是定义的蓝色框框内展现的部分:

版本信息演示代码
版本信息演示代码

注意,这里version方法的第三个参数,是咱们定义的说明内容,如上图的红色部分。help默认也是这个值

  • 定义命令行参数
/**  * 定义vta的参数  */ program  .option('-y, --yes', 'run default action')  .option('-f, --force', 'force all the question');  /**  * 能够经过判断,当用户输入了对应的这些参数时,  * 咱们能够作一些操做:  */ if (program.force) {  // do something.. } 复制代码

经过option方法,定义咱们的命令行参数,比如vta -f,等同于vta --force。注意,第一个参数是定义命令行参数,包含一个短的名称(1个字符)和一个长的名称,不能多了。第二个参数,就是定义的说明内容。注意,判断部分的代码,只能使用长的名称,不能判断短的,例如program.f

  • 建立一个子命令

建立子命令是重要的一部分,好比咱们使用vue create my-app建立项目时, create就是vue命令的子命令,my-app是命令参数。这里咱们也定义一个子命令:

/**  * 调用command方法,建立一个create命令,  * 同时create命令后面必须跟一个命令参数  * 若是你在终端运行vta create不加名称,则会报错提示用户  */ program.command('create <name>')  // 定义该命令的描述  .description('create a vta template project')  // 为该命令指定一些参数  // 最后咱们均可以解析到这些参数,而后根据参数实现对应逻辑  .option('-f, --force', '忽略文件夹检查,若是已存在则直接覆盖')  /**  * 最后定义咱们的实现逻辑  * source表示当前定义的name参数  * destination则是终端的cmd对象,能够从中解析到咱们须要的内容  */  .action((source, destination) => {  /**  * 好比咱们这里把实现逻辑放在了另外一个文件中去实现,  * 方便代码解耦,  * 由于destination参数比较杂乱,其实仍是在此处先解析该参数对应再传入使用吧  * 能够定义一个解析的工具函数  */  new CreateCommand(source, destination)  }); 复制代码

如图,看下destination对象究竟是什么?仍是满多的内容。咱们须要关注的就是红色框框的这部分,这里就是咱们定义的该命令的全部参数的列表,咱们变量该列表,取图中蓝色的部分的值,解决--后面的部分,而后做为key到整个cmd对象中取匹配,其值就是用户输入的参数的值。

cmd对象展现
cmd对象展现

好比,可能会定义一个解析的工具函数:

/**  * parseCmdParams  * @description 解析用户输入的参数  * @param {} cmd Cammander.action解析出的cmd对象  * @returns { Object } 返回一个用户参数的键值对象  */ exports.parseCmdParams = (cmd) => {  if (!cmd) return {}  const resOps = {}  cmd.options.forEach(option => {  const key = option.long.replace(/^--/, '');  if (cmd[key] && !isFunction(cmd[key])) {  resOps[key] = cmd[key]  }  })  return resOps } 复制代码

上述的解析方法实现方式和咱们vue-cli的差很少。

  • 完成解析
/**  * 切记parse方法的调用,必定要program.parse()方式,  * 而不是直接在上面的链式调用以后直接xxx.parse()调用,  * 否则就会做为当前command的parse去处理了,从而help命令等都与你的预期不符合了  */ try {  program.parse(process.argv); } catch (error) {  console.log('err: ', error) } 复制代码

最后必定要解析,不解析是拿不到对应参数program.parse(process.argv),也就是不会执行对应的命令等行为的。切记!切记!切记!!!更详细的命令请查询commander文档

🌞对目标路径进行检查

从上面的步骤咱们能够看出,咱们已经定义好了vta create <name>的命令了,即当咱们运行vta create my-app命令的时候,就会初始化咱们定义的CreateCommand类了。下面咱们看看入如何实现这个逻辑:咱们首先建立src/command/CreateCommand.js这个文件来实现咱们的逻辑:

/**  * class 项目建立命令  *  * @description  * @param {} source 用户提供的文件夹名称  * @param {} destination 用户输入的create命令的参数  */ class Creator {  constructor(source, destination, ops = {}) {  this.source = source  this.cmdParams = parseCmdParams(destination)  this.RepoMaps = Object.assign({  repo: RepoPath, // 配置文件中放置的远程地址常量  temp: path.join(__dirname, '../../__temp__'),  target: this.genTargetPath(this.source)  }, ops);  this.gitUser = {};  this.spinner = ora();  this.init();  }   // 其余实例方法  // ... }  // 最终导出这个class module.exports = Creator; 复制代码

咱们看下这个构造函数咱们用来作了什么事情,首先就是把实例化时传进来的参数赋值给this对象,供后面其余实例方法中去使用。而后定义了RepoMaps属性设置咱们的一些基础参数,像项目模板的地址repo、咱们本地cli项目内部临时存放的项目模板的地址temp、和最终咱们须要把项目安装到的目标地址taregt。由于项目最终会安装到终端运行的地址下的位置,而你的脚手架包是被安装在其余地址的。

而后定义了gitUser用于存放用户的git信息,后面会经过自动执行命令获取相关的信息,而后最后咱们会把信息塞到package.json文件中。

this.spinner = ora();就是实例化一个菊花图,当咱们在执行命令的时候能够调用this.spinner方法进行菊花转呀转!

下面咱们来实现这个init初始化的方法吧:

// 初始化函数
async init() {  try {  // 检查目标路径文件是否正确  await this.checkFolderExist();  // 拉取git上的vue+ts+ele的项目模板  // 存放在临时文件夹中  await this.downloadRepo();  // 把下载下来的资源文件,拷贝到目标文件夹  await this.copyRepoFiles();  // 根据用户git信息等,修改项目模板中package.json的一些信息  await this.updatePkgFile();  // 对咱们的项目进行git初始化  await this.initGit();  // 最后安装依赖、启动项目等!  await this.runApp();  } catch (error) {  console.log('')  log.error(error);  exit(1)  } finally {  this.spinner.stop();  } } 复制代码

从上面代码注释能够看到,咱们的init方法,就是把一系列操做一次调用执行便可。最后先看一下配置文件吧:

exports.InquirerConfig = {
 // 文件夹已存在的名称的询问参数  folderExist: [{  type: 'list',  name: 'recover',  message: '当前文件夹已存在,请选择操做:',  choices: [  { name: '建立一个新的文件夹', value: 'newFolder' },  { name: '覆盖', value: 'cover' },  { name: '退出', value: 'exit' },  ]  }],  // 重命名的询问参数  rename: [{  name: 'inputNewName',  type: 'input',  message: '请输入新的项目名称: '  }] }  // 远程Repo地址 // 你们开发阶段,若是没有本身的项目,能够先调用个人这个地址练习 // 也能够随便一个地址练习均可以 exports.RepoPath = 'github:chinaBerg/vue-typescript-admin' 复制代码

后面咱们将看看这一系列方法该如何实现?

🌞终端的菊花图工具

首先介绍一下咱们的小菊花吧!咱们在执行各类操做的时候,好比拉模板数据等等,都是会有必定等待实际的,那么这个等待过程,咱们能够在终端有个小菊花转转转,这样 会给用户更好的体验,让用户知道当前脚本在执行加载,如图(最左侧有个小菊花在转转转~~~):

转动的ora菊花图
转动的ora菊花图

ora就是这一款终端使用的菊花图工具,下面看看如何使用吧!

  • 安装
cnpm install ora -S
复制代码
  • 使用
const ora = require('ora');
 // ora参数建立spinner文字内容 // 也能够传递一个对象,设置spinner的周期、颜色等 // 调用start方法启动,最终返回一个实例 const spinner = ora('Loading start')  // 开启菊花转转 spinner.start();  // 中止 spinner.stop()  // 设置文案,后者菊花的color spinner.text = '正在安装项目依赖文件,请稍后...'; spinner.color = 'green';  // 显示转成功的状态 spinner.succeed('package.json更新完成'); 复制代码

注意,文案的颜色,仍是得靠chalk辅助。后面会介绍chalk。上个图片演示一下实际的运用:

更多详细的用户请查阅ora文档

🌛五彩斑斓的控制台

chalk是一款可让咱们的控制台打印出各类颜色/背景的内容的工具,由此咱们能够鲜明的区分各类提示内容,以下图(就问你骚不骚???):

chalk效果图
chalk效果图
  • 安装
# 终端运行
cnpm i chalk -S 复制代码
  • 使用
const chalk = require('chalk');
 // 好比,这里定义一个log对象 exports.log = {  warning(msg = '') {  console.warning(chalk.yellow(`${msg}`));  },  error(msg = '') {  console.error(chalk.red(`${msg}`));  },  success(msg = '') {  console.log(chalk.green(`${msg}`));  } } 复制代码

好比,上面咱们封装了最简单的log方法,用于打印各类类型的信息时展现带颜色的内容。还有一点,咱们说一下上面提到了的如何配合ora使用吧:

const chalk = require('chalk');
const ora = require('ora');  const spinner = ora('Loading start')  // 开启菊花转转 spinner.start(chalk.yellow('打印一个yellow色的文字')); 复制代码

用法比较简单,很少说了,更多用法仍是查阅文档吧!

✨fs-extra文件操做

在详细说明各个步骤实现的方式以前,咱们先说一下在cli中使用的文件操做的库。node自己有fs操做,那么咱们为何还要引入fs-extra库呢?是由于他彻底能够用来取代fs的库,省去了mkdirp``rimraf``ncp等库等安装引入。用于拷贝、读取、删除等文件操做,并且提供了更多的功能等等。

  • 安装
cnpm install fs-extra
复制代码

具体的api的方法,请查阅文档fs-extra,后面讲解各个步骤具体实现的时候也会说起到。

✨检查文件夹是否合法

到了咱们运行vta create my-app的时候了,这时候咱们就要考虑了,若是当前位置已经存在了同名的文件夹,那么咱们确定是不能直接覆盖的,而是要给用户选择,好比覆盖、从新建立一个新的文件夹、退出,以下图:

而后根据用户的不一样选择做出对于的操做。下面咱们看这个文件夹检查的具体实现:

checkFolderExist() {
 return new Promise(async (resolve, reject) => {  const { target } = this.RepoMaps  // 若是create附加了--force或-f参数,则直接执行覆盖操做  if (this.cmdParams.force) {  await fs.removeSync(target)  return resolve()  }  try {  // 不然进行文件夹检查  const isTarget = await fs.pathExistsSync(target)  if (!isTarget) return resolve()   const { recover } = await inquirer.prompt(InquirerConfig.folderExist);  if (recover === 'cover') {  await fs.removeSync(target);  return resolve();  } else if (recover === 'newFolder') {  const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);  this.source = inputNewName;  this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);  return resolve();  } else {  exit(1);  }  } catch (error) {  log.error(`[vta]Error:${error}`)  exit(1);  }  })  } 复制代码

具体讲解:

  1. 咱们定义了这个方法,返回的是一个Promise对象。
  2. 咱们判断用户在输入vta create my-app的时候有没有在后面加-f的参数,若是添加了参数则是告诉咱们忽略检查直接日后走,就是默认覆盖的操做。经过调用fs.removeSync(target);方法进行移除须要覆盖的文件;
  3. 不然的话,咱们则须要进行文件夹检查的实现逻辑了。经过await fs.pathExistsSync(target)逻辑进行判断当前文件夹名称是否已经存在,若是不存在则resolve告诉程序执行文件夹检查成功以后的程序。
  4. 若是同名的则给用户提示,让用户选择操做。下面将讲解如何在命令行进行交互。

❤️命令行交互

说到命令行交互,就要提到一个比较程序的库inquirer,这是一个用于node环境下进行命令行交互的库,支持单选、多选、用户输入、confirm询问等等操做。

  • 安装
cnpm i inquirer -S
复制代码
  • 使用
const inquirer = require('inquirer');
 // 定义询问的参数 // type表示询问的类型,是单选、多选、确认等等 // name能够理解为当前交互的标识符,其值为交互的结果 const InquirerConfig = {  // 文件夹已存在的名称的询问参数  folderExist: [{  type: 'list',  name: 'recover',  message: '当前文件夹已存在,请选择操做:',  choices: [  { name: '覆盖', value: 'cover' },  { name: '建立一个新的文件夹', value: 'newFolder' },  { name: '退出', value: 'exit' },  ]  }],  // 重命名的询问参数  rename: [{  name: 'inputNewName',  type: 'input',  message: '请输入新的项目名称: '  }] }  // 使用 // 经过当前标识符获取交互的结果 // 好比,以下是一个单选的演示 const { recover } = await inquirer.prompt(InquirerConfig.folderExist);  // 若是用户选中的是“覆盖”选项 if (recover === 'cover') {  await fs.removeSync(target);  return resolve(); // 若是用户选中的是“建立新文件夹”选中 } else if (recover === 'newFolder') {  // 再次建立一个用户输入的交互操做  // 让用户输入新的文件夹名称  const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);  this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);  return resolve(); // 若是用户选的是“退出”选项 } else {  exit(1); } 复制代码
  1. 若是用户选择了覆盖,咱们就移除文件夹而后reolve
  2. 若是用户选择了建立新的文件夹,那么咱们就再给出一个用于输入的终端,让用户输入新的文件夹名称。在用户输入完成后,咱们把target的目标地址更新掉。
  3. 若是用户选择退出,咱们则调用process.exit方法进行退出当前node程序便可。

❤️拉取git等远程仓库代码

在进行了文件夹监测完成以后,就应该是要下载咱们在git上的项目资源了。下载资源咱们是经过download-git-repo这个库来实现的。

  • 安装
cnpm install download-git-repo -S
复制代码
  • 使用
const path = require('path');
const downloadRepo = require('download-git-repo');   // 下载repo资源  downloadRepo() {  // 菊花转起来~  this.spinner.start('正在拉取项目模板...');  const { repo, temp } = this.RepoMaps  return new Promise(async (resolve, reject) => {  // 若是本地临时文件夹存在,则先删除临时文件夹  await fs.removeSync(temp);  /**  * 第一个参数为远程仓库地址,注意是类型:做者/库  * 第二个参数为下载到的本地地址,  * 后面还能够继续加一个配置参数对象,最后一个是回调函数,  */  download(repo, temp, async err => {  if (err) return reject(err);  // 菊花变成对勾  this.spinner.succeed('模版下载成功');  return resolve()  })  })  } 复制代码

主要逻辑就是把资源下载到咱们当前的临时文件夹位置,若是临时文件夹已经存在了那么就先删除临时文件夹。

👍把资源拷贝到目标地址,并移除无关文件

上面经过git上资源的下载,咱们是下载到了cli目录内的临时文件内,那么咱们还须要把资源移动到咱们指定的位置,而且删除没必要要的资源。因此咱们这边会在utlis里面封装一个公共函数,用于资源的拷贝:

  • 拷贝函数的封装
/**  * copyFiles 拷贝下载的repo资源  * @param { string } tempPath 待拷贝的资源路径(绝对路径)  * @param { string } targetPath 资源放置路径(绝对路径)  * @param { Array<string> } excludes 须要排除的资源名称(会自动移除其全部子文件)  */ exports.copyFiles = async (tempPath, targetPath, excludes = []) => {  const removeFiles = ['./git', './changelogs']  // 资源拷贝  await fs.copySync(tempPath, targetPath)   // 删除额外的资源文件  if (excludes && excludes.length) {  await Promise.all(excludes.map(file => async () =>  await fs.removeSync(path.resolve(targetPath, file))  ));  } } 复制代码
  • 调用
// 拷贝repo资源
async copyRepoFiles() {  const { temp, target } = this.RepoMaps  await copyFiles(temp, target, ['./git', './changelogs']); } 复制代码

这里,咱们移除了项目中自己含有的./git./changelogs等文件,由于这些是该git项目须要的内容,而咱们实际是不须要的。

👍自动更新package.json文件

经过上面的操做,咱们已经把资源拷贝到咱们的目标地址了。那么咱们还想自动把package.json中的name、version、author等字段更新成咱们须要的,应该怎么作呢?

/**  * updatePkgFile  * @description 更新package.json文件  */ async updatePkgFile() {  // 菊花转起来!  this.spinner.start('正在更新package.json...');  // 获取当前的项目内的package.json文件的据对路径  const pkgPath = path.resolve(this.RepoMaps.target, 'package.json');  // 定义须要移除的字段  // 这些字段自己只是git项目配置的内容,而咱们业务项目是不须要的  const unnecessaryKey = ['keywords', 'license', 'files']  // 调用方法获取用户的git信息  const { name = '', email = '' } = await getGitUser();   // 读取package.json文件内容  const jsonData = fs.readJsonSync(pkgPath);  // 移除不须要的字段  unnecessaryKey.forEach(key => delete jsonData[key]);  // 合并咱们须要的信息  Object.assign(jsonData, {  // 以初始化的项目名称做为name  name: this.source,  // author字段更新成咱们git上的name  author: name && email ? `${name} ${email}` : '',  // 设置非私有  provide: true,  // 默认设置版本号1.0.0  version: "1.0.0"  });  // 将更新后的package.json数据写入到package.json文件中去  await fs.writeJsonSync(pkgPath, jsonData, { spaces: '\t' });  // 中止菊花  this.spinner.succeed('package.json更新完成!'); } 复制代码

这一块,上面代码注释已经写的很是清晰了,看一遍应该就晓得过程逻辑了吧!!!至于其中获取用户git信息的逻辑,后面立刻会讲解到!!!

🌟获取Git信息

如今咱们看下如何获取git信息的,咱们定义了一个公共的方法getGitUser:

/**  * getGitUser  * @description 获取git用户信息  */ exports.getGitUser = () => {  return new Promise(async (resolve) => {  const user = {}  try {  const [name] = await runCmd('git config user.name')  const [email] = await runCmd('git config user.email')  // 移除结尾的换行符  if (name) user.name = name.replace(/\n/g, '');  if (email) user.email = `<${email || ''}>`.replace(/\n/g, '')  } catch (error) {  log.error('获取用户Git信息失败')  reject(error)  } finally {  resolve(user)  }  }); } 复制代码

咱们都知道,在终端想查看用户的git信息,那么只须要键入git config user.name便可,git config user.email能够获取用户的邮箱。那么咱们一样的在脚本中也执行这样的命令不就能够获取到了吗?

那么剩下的就是如何在终端执行shell命令呢?

✨✨node脚本中,执行指定的shell命令

node是经过开启一个子进程来执行脚本命令的,child_process说明是node提供的一个开启子进程的方法。因而咱们能够封装一个方法用于执行子进程:

// node的child_process能够开启一个进程执行任务
const childProcess = require('child_process');   /**  * runCmd  * @description 运行cmd命令  * @param { string } 待运行的cmd命令  */ const runCmd = (cmd) => {  return new Promise((resolve, reject) => {  childProcess.exec(cmd, (err, ...arg) => {  if (err) return reject(err)  return resolve(...arg)  })  }) } 复制代码

因此上述获取git详情的操做其实就是调用的这个方法,让node开启一个子进程去运行咱们的git命令,而后将结果返回出来。

❤️初始化git文件

// 初始化git文件
 async initGit() {  // 菊花转起来  this.spinner.start('正在初始化Git管理项目...');  // 调用子进程,运行cd xxx的命令进入到咱们目标文件目录  await runCmd(`cd ${this.RepoMaps.target}`);   // 调用process.chdir方法,把node进程的执行位置变动到目标目录  // 这步很重要,否则会执行失败(由于执行位置不对)  process.chdir(this.RepoMaps.target);   // 调用子进程执行git init命令,辅助咱们进行git初始化  await runCmd(`git init`);  // 菊花停下来  this.spinner.succeed('Git初始化完成!'); } 复制代码

这一块也是调用的咱们封装的方法执行git命令而已。可是必定要注意、process.chdir(this.RepoMaps.target);变动进程的执行位置,若是变动目录失败会抛出异常(例如,若是指定的 directory 不存在)。这步操做很是重要,切记!!切记!!!详细能够查阅process.chdir说明

🌟安装依赖

最后咱们就须要自动暗转项目依赖了。本质也是调用子进程执行npm命令就能够了。这里咱们直接指定了使用淘宝的镜像源,小伙伴们也能够扩展,根据用户的选择指定npm、yarn和其余镜像源等等,尽情发挥吧!!!

// 安装依赖
 async runApp() {  try {  this.spinner.start('正在安装项目依赖文件,请稍后...');  await runCmd(`npm install --registry=https://registry.npm.taobao.org`);  await runCmd(`git add . && git commit -m"init: 初始化项目基本框架"`);  this.spinner.succeed('依赖安装完成!');   console.log('请运行以下命令启动项目吧:\n');  log.success(` cd ${this.source}`);  log.success(` npm run serve`);  } catch (error) {  console.log('项目安装失败,请运行以下命令手动安装:\n');  log.success(` cd ${this.source}`);  log.success(` npm run install`);  }  } 复制代码

最后👍👍👍

vta-cli脚手架git源码地址,有兴趣的小伙伴能够查阅代码实现。也可使用vta-cli快速初始化Vue+Ts+ElementUi的RBAC后台管理系统的基础架构。安装vta-cli的方法:

# 安装cli
npm i vta-cli -g  # 初始化项目 vta create my-app 复制代码

vue-typescript-admin项目模板将会很快完善起来!!!也欢迎小伙伴们一块儿贡献代码哦~~

关于cli开发的讲解,到这就基本结束了!!!上面涵盖了常见的技术实现方案和注意细节,项目能够无痛上手的~~~有兴趣的小伙伴们能够照着封装本身的cli,把业务通用的场景解决方案抽离处理,提高本身的开发效率吧!最后,我是大家的老朋友愣锤,欢迎👏👏点赞👍👍收藏💗💗哦~~~

点赞👍、收藏👋、分享防走丢哦!!!须要的时候能够拿出来对着开发~~~

❤️❤️❤️更新留白

此处将留做后续更多和脚手架开发相关的优秀库的展现地址,后续会继续更新~~~

👍👍👍做者其余文章推荐

本文使用 mdnice 排版

相关文章
相关标签/搜索