在理论开发过程中,咱们常常都会用到脚手架来构建前端工程项目,常见的支流框架都有本人的脚手架,vue-cli、create-react-app、angular-cli。在大型公司都会有外部定制化的脚手架开发工具,应用脚手架能够大幅晋升我的项目的构建速度,通过命令行的交互,抉择你所须要的配置与集成,可疾速实现初始我的项目的创立。

既然应用了这么多脚手架创立我的项目,为何不本人实现一套属于本人开发习惯的脚手架呢,本文将从0开始搭建一套脚手架开发工具。

什么是脚手架

脚手架是一类疾速造成工程化目录的工具(command-line-interface, 缩写:CLI),简略来说,脚手架就是帮你缩小「为重复性工作而做的重复性工作」的工具。

基于咱们日常应用过的脚手架得悉一个脚手架至多须要实现以下的性能点:

  • 有不同的命令执行操作,比方:
Commands:  create [options] <app-name>  add [options] <plugin> [pluginOptions]  invoke [options] <plugin> [pluginOptions]  inspect [options] [paths...]  serve  build  ui [options]  init [options] <template> <app-name>  config [options] [value]  outdated [options]  upgrade [options] [plugin-name]  migrate [options] [plugin-name]  info  help [command]
  • 能够通过命令行交互执行问答列表
? Please pick a preset:❯ Default ([Vue 3] babel, eslint)  Default ([Vue 2] babel, eslint)  Manually select features
  • 最终依据用户的问答后果生成对应的文件

必备的知识库

在欠缺构建脚手架前,须要引入一些脚手架构建中必须用到的工具库。

  • commander 能够自定义一些命令行指令,在输出自定义的命令行的时候,会去执行相应的操作
  • inquirer 能够在命令行询问用户问题,并且能够记录用户答复抉择的后果
  • fs-extra 是fs的一个扩大,提供了十分多的便当API,并且继承了fs所有办法和为fs办法增加了promise的反对。
  • chalk 能够丑化终端的输入
  • figlet 能够在终端输入logo
  • ora 控制台的loading动画
  • download-git-repo 下载近程模板

实现过程

创立脚手架

新建一个文件夹,执行 npm init 生成package.json文件。

npm init -y

创立bin文件夹,bin文件夹寄存次要的命令程序入口文件,并在文件夹上面新建index.js文件,目录构造如下:

fe   ├─ bin│  ├─ index.js          └─ package.json  

在package.json指定命令的入口文件为刚刚新建的index.js,bin上面的fe为程序的疾速启动命令。

{  "name": "fe",  "version": "1.0.0",  "main": "index.js",  "bin": {    "fe": "./bin/index.js"  },  ...}

批改入口文件index.js为如下内容,文件以#!结尾代表这个文件被当做一个执行文件来执行,能够当做脚本运行。前面的/usr/bin/env node代表这个文件用node执行,node基于用户装置根目录下的环境变量中查找:

#! /usr/bin/env nodeconsole.log('hello fe cli')

最初将以后命令链接到全局,即可测试是否失常

npm link

此时在命令行中输出刚刚的疾速启动命令fe,输入了打印的日志。阐明执行了index.js中的代码,接下来就开始正式搭建脚手架的性能。

这里咱们能够额定扩大一下输入的款式,当然这个不是必要的,平时装置依赖包的时候常常会看到不同色彩的输入和LOGO,次要是用了chalkfiglet,示例代码如下:

program  .on('--help', () => {    console.log('\r\n' + chalk.white.bgBlueBright.bold(figlet.textSync('nanChengFE', {      font: 'Standard',      horizontalLayout: 'default',      verticalLayout: 'default',      width: 80,      whitespaceBreak: true    })));    console.log(`\r\nRun ${chalk.cyan(`fe <command> --help`)} for detailed usage of given command\r\n`)  })

最终输入的款式如下,具体的其余LOGO款式和字体色彩能够看官网文档。

脚手架功能完善

基于commander执行自定义命令指令,具体的应用能够看相干文档,以下实现一个简略的create命令,传入参数并打印输出日志。

#! /usr/bin/env nodeconst { Command } = require('commander');const program = new Command();program  .name('fe cli')  .description('这里是形容文案')  .version('1.0.0');program.command('create <name>')  .description('创立一个新工程')  .action((name) => {    console.log('[ 工程名称 ] >', name)  });program.parse(process.argv);

通过command定义create命令,并要求必传参数name,再通过action回调获取到传入的参数,后续即可通过用户输出的名称创立我的项目。

接下来进入创立文件的过程,在创立文件的时候须要校验当前目录下是否曾经存在,如果存在是否须要执行笼罩的动作。

const path = require('path')const fs = require('fs-extra')// 以后命令行执行的目录const cwd  = process.cwd();// 须要创立的目录const targetPath  = path.join(cwd, name)// 目录是否存在if (fs.existsSync(targetPath)) {  // 强制创立  if (options.force) {  } else {    // 询问用户是否须要强制创立  }} else {  // 目录不存在失常创立}

以上代码能够看出options.force命令参数能够间接进入到强制创立的过程。如果还有其余的想法想通过不同的指令做不同的解决,能够参考强制创立的逻辑进行其余的自定义。

下面提到了询问用户是否须要强制创立,这须要通过命令行与用户进行交互,获取到用户抉择的信息,inquirer这个包能够在命令行询问用户问题并拿到后果进行后续的逻辑交互。

let { action } = await inquirer.prompt([{  name: 'action',  type: 'list',  message: '目录已存在,请抉择:',  choices: [{    name: '笼罩',    value: 'overwrite'  }, {    name: '勾销',    value: false  }]}])if (!action) {  return;} else if (action === 'overwrite') {  // 移除已存在的目录  await fs.remove(targetPath)}

接下来持续询问要抉择哪个模版,这个github有提供相干的接口,也能够本人保护模版配置的相干接口,外围就是让用户抉择须要的模版,比方模版里有vue,react,小程序等不同场景或框架模版,用户抉择模版后获取到对应的远端模版地址进行下载,这里会用到两个新的npm包,ora 能够在控制台输入loading动画,download-git-repo 执行下载近程模板。示例代码如下:

const spinner = ora('远端仓库开始下载...');spinner.start();// 本地寄存地址const tmp = path.join(  process.cwd(),  'templates',  '工程目录名称',);// 已存在门路执行删除if (fs.existsSync(tmp)) {  await fs.remove(tmp)}// 下载远端模版download(  '远端仓库地址',  tmp,  {    clone: true,  },  (err) => {    if (err) {      spinner.fail('[ 远端仓库下载失败 ]')      logger.console.error();    }    spinner.succeed('[ 远端仓库下载胜利 ]')  })

操作过程如下:

如果只是做一个简略版本的脚手架下载模版到这里根本就差不多了,抉择对应的框架就下载对应的模版。然而理论开发过程中往往还不够,不同的框架中还会不同的配置,比方应用vue框架时,是否须要主动退出vuex,vue-router等等。所以在不同的模版中咱们还要进行下一步的用户问询配置,基于用户答复的后果创立更合乎的工程文件。

这一段能够参考Vue-cli的源码,在不同的模板我的项目中减少相干的问询配置,比方在Vue模版中新增了以下prompts配置:

  "name": {    "type": "string",    "required": true,    "message": "填写项目名称"  },   "description": {    "type": "string",    "required": false,    "message": "填写我的项目形容",    "default": "ZZZ前端vue我的项目"  },  ...

在执行完结下载文件后继续执行我的项目问询配置列表,成果如下:

最初依据获取到的用户答复后果进行工程文件的生成,比方将获取到的工程名称间接填充到模版内容中,依据用户的抉择是否须要减少vuex的应用等。这块次要会用到以下几个依赖。

  • Metalsmith 动态网站(博客,我的项目)的生成器
  • handlerbars 模板编译器,通过template和json,输入一个html
  • consolidate 模板引擎整合库

在vue_cli源码中注册了2个渲染器,相似于vue中的 v-if v-else的条件渲染,这个咱们能够依据理论须要扩大其余的条件渲染。

// register handlebars helperHandlebars.registerHelper('if_eq', function (a, b, opts) {  return a === b    ? opts.fn(this)    : opts.inverse(this)})Handlebars.registerHelper('unless_eq', function (a, b, opts) {  return a === b    ? opts.inverse(this)    : opts.fn(this)})

在模版文件中应用对应的标识,联合模板编译器即可动静生成内容,具体的构建细节有趣味的大家能够看看源码或者其余文章具体的解析。

{{#if_eq userSelectVuex 'vuex'}}import Vuex from 'vuex'Vue.use(Vuex){{/if_eq}}

列举这些是想阐明如果要革新一个脚手架生成的模版内容咱们间接能够找到对应的配置进行减少批改,在用户问询处减少须要的场景字段,获取到对应的值进行模版内容的动静解决。

总结

到这里咱们实现了一个简略的开发脚手架,有自定义的快捷命令,能够进行模板抉择,能够依据不同的模板进行下一步的配置抉择,最终生成工程文件。这只是一个简略的实现,看vue脚手架的命令就能够看出还有很多性能点能够进一步欠缺,有趣味的同学能够深入研究。

到此本文就完结了,看完本文如果感觉有用,记得点个赞反对,珍藏起来说不定哪天就用上啦~

专一前端开发,分享前端相干技术干货,公众号:南城大前端(ID: nanchengfe)