在日常开发中,咱们会依据教训积淀出一些我的项目模板,在不同在我的项目中能够进行复用。如果是每次都是通过拷贝代码到新我的项目的话,这样会比拟麻烦,而且容易出错,此时咱们就会想能不能将一些模板集成到脚手架(相似vue-cli, create-react-app)中,这样咱们进行初始化创立就能应用了呢?

比方自己有以下两套开发模板

基于vue-cli4和vant搭建的挪动端开发模板 vue-cli4-vant
基于vue-cli4和ant-design-vue构建的后盾管理系统简略模板 vue-cli4-vant

心愿执行相似 aaa init bbb ccc 这样的命令疾速初始化一个我的项目,无需本人从零开始一步步配置,大大提高了开发效率。

为什么须要脚手架?

  1. 缩小重复性的工作,不再从零创立一个我的项目,或者复制粘贴另一个我的项目的代码 。
  2. 依据动静交互生成我的项目构造和配置文件,具备更高的灵活性和人性化定制的能力 。
  3. 有利于多人开发合作,防止了人工传递文件的繁琐。
  4. 能够集成多套开发模板,依据我的项目须要抉择适合的模板。

第三方库的反对

实现一个脚手架,通常须要以下工具,后续咱们将会一一介绍。

  • commander: 命令行工具
  • download-git-repo: 用来下载近程模板
  • inquirer: 交互式命令行工具
  • ora: 显示 loading 动画
  • chalk: 批改控制台输入内容款式
  • log-symbols: 显示出 √ 或 × 等的图标
  • handlebars.js 用户提交的信息动静填充到文件中

构建步骤

  1. 新建一个文件夹,命名为 cm-cli(我的脚手架命名),在该目录下执行 npm init -y 进行初始化,此时就会生产一个 package.json 文件。
  2. 装置第三方工具库
npm install chalk commander download-git-repo inquirer ora log-symbols
  1. 在根目录下新建一个 bin 文件夹,并在 bin 目录下新建一个无后缀名的 cm 文件,并写上:
#!/usr/bin/env nodeconsole.log('hello world')

这个文件就是整个脚手架的入口文件,咱们用 node ./bin/cm 运行一下,在控制台就会打印出 hello world。

当然,每次输出node ./bin/cm 这个命令有点麻烦,咱们能够在 package.json 进行命令配置

"bin": {  "cm": "bin/cm"}

此时咱们执行 npm link将命令挂载到全局,而后再输出 cm 就能够达到方才node ./bin/cm 的成果了。

  1. 定义多个命令

咱们再 bin 上面的 cm 文件夹来定义多个命令,此时就用到 commander 了。首先咱们来看一下 commander 的用法

  • usage(): 设置 usage 值
  • command(): 定义一个命令名字
  • description(): 设置 description 值
  • option(): 定义参数,须要设置“关键字”和“形容”,关键字包含“简写”和“全写”两局部,以”,”,”|”,”空格”做分隔。
  • parse(): 解析命令行参数 argv
  • action(): 注册一个 callback 函数
  • version() : 终端输入版本号

依据日常开发须要,咱们创立以下几个脚手架命令

  • add 新增一个我的项目模板
  • delete 删除一个我的项目模板
  • list 列举所以我的项目模板
  • init 初始化一个我的项目模板

咱们先来编写一下 cm 文件

#!/usr/bin/env nodeconst program = require('commander')program.usage('<command>')program.version(require('../package').version)program  .command('add')  .description('add a new template')  .action(() => {    require('../commands/add')  })program  .command('delete')  .description('delete a template')  .action(() => {    require('../commands/delete')  })program  .command('list')  .description('List the templateList')  .action(() => {    require('../commands/list')  })program  .command('init')  .description('init a project')  .action(() => {    require('../commands/init')  })program.parse(process.argv)

而后执行一下 cm -h,就会看到以下的成果

此时咱们再改一下 package.json 的配置

"bin": {  "cm-add": "bin/cm-add",  "cm-delete": "bin/cm-delete",  "cm-list": "bin/cm-list",  "cm-init": "bin/cm-init"}

而后执行 npm unlink 解绑全局命令,再执行 npm link 从新把命令绑定到全局,这样就能够间接应用 cm add 等命令了。

编写指令

在这里会用到 inquirer 进行命令行交互,咱们先来看下 inquirer 的用法,它有以下参数能够配置

  • type:示意发问的类型,包含:input, confirm, list, rawlist, expand, checkbox, password, editor;
  • name: 存储以后问题答复的变量;
  • message:问题的形容;
  • default:默认值;
  • choices:列表选项,在某些 type 下可用,并且蕴含一个分隔符(separator);
  • validate:对用户的答复进行校验;
  • filter:对用户的答复进行过滤解决,返回解决后的值;
  • when:依据后面问题的答复,判断以后问题是否须要被答复;
  • prefix:批改 message 默认前缀;
  • suffix:批改 message 默认后缀。

语法结构如下:

const inquirer = require('inquirer')const question = [  // 具体交互内容]inquirer.prompt(question).then((answers) => {  console.log(answers) // 返回的后果})

cm add

新增一个我的项目模板

  1. 通过命令行交互,让用户输出模板名称和模板的地址
  2. 将用户输出的模板信息新增写入到template.json文件中
  3. 打印出所有的我的项目模板

看一下代码

#!/usr/bin/env nodeconst inquirer = require('inquirer')const fs = require('fs')const templateList = require(`${__dirname}/../template`)const { showTable } = require(`${__dirname}/../util/showTable`)const symbols = require('log-symbols')const chalk = require('chalk')chalk.level = 1let question = [  {    name: 'name',    type: 'input',    message: '请输出模板名称',    validate(val) {      if (!val) {        return 'Name is required!'      } else if (templateList[val]) {        return 'Template has already existed!'      } else {        return true      }    }  },  {    name: 'url',    type: 'input',    message: '请输出模板地址',    validate(val) {      if (val === '') return 'The url is required!'      return true    }  }]inquirer.prompt(question).then((answers) => {  let { name, url } = answers  templateList[name] = url.replace(/[\u0000-\u0019]/g, '') // 过滤 unicode 字符  fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(templateList), 'utf-8', (err) => {    if (err) console.log(chalk.red(symbols.error), chalk.red(err))    console.log('\n')    console.log(chalk.green(symbols.success), chalk.green('Add a template successfully!\n'))    console.log(chalk.green('The latest templateList is: \n'))    showTable(templateList)  })})

在这里还用到以下两个第三方库,原来丑化互相成果:

  • chalk:用来批改控制台输入内容款式的,比方色彩
  • log-symbols: 显示出 √ 或 × 等的图标

此时,执行 cm add ,并输出我的项目模板名称和地址,就能看到以下成果了

cm detele

删除一个我的项目模板,这个就好理解,步骤如下

  1. 通过命令行交互,让用户输出要删除的我的项目模板名称
  2. 删除用户输出的模板数据,而后再将更新的数据写入到template.json文件中
  3. 打印出所有的我的项目模板

代码如下

#!/usr/bin/env nodeconst inquirer = require('inquirer')const fs = require('fs')const templateList = require(`${__dirname}/../template`)const { showTable } = require(`${__dirname}/../util/showTable`)const symbols = require('log-symbols')const chalk = require('chalk')chalk.level = 1let question = [  {    name: 'name',    message: '请输出要删除的模板名称',    validate(val) {      if (!val) {        return 'Name is required!'      } else if (!templateList[val]) {        return 'Template does not exist!'      } else {        return true      }    }  }]inquirer.prompt(question).then((answers) => {  let { name } = answers  delete templateList[name]  fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(templateList), 'utf-8', (err) => {    if (err) console.log(chalk.red(symbols.error), chalk.red(err))    console.log('\n')    console.log(chalk.green(symbols.success), chalk.green('Deleted successfully!\n'))    console.log(chalk.green('The latest templateList is: \n'))    showTable(templateList)  })})

此时,咱们执行一下cm delete ,输出要删除的模板,就能看到以下成果了

cm list

列举所有的我的项目模板,这个就更简略了,间接上代码

#!/usr/bin/env nodeconst { showTable } = require(`${__dirname}/../util/showTable`)const templateList = require(`${__dirname}/../template`)showTable(templateList)

此时,咱们执行一下cm list ,输出要删除的模板,就能看到以下成果了

cm init

初始化一个我的项目模板,这是最重要的一部分,步骤如下

  1. 通过命令行交互,让用户模板的名称和我的项目的名称
  2. 校验模板是否存在,项目名称是否填写
  3. 开始下载模板,显示加载图标
  4. 实现模板下载,暗藏加载图标

先来看一下代码

#!/usr/bin/env nodeconst program = require('commander')const ora = require('ora')const download = require('download-git-repo')const templateList = require(`${__dirname}/../template`)const symbols = require('log-symbols')const chalk = require('chalk')chalk.level = 1program.usage('<template-name> [project-name]')program.parse(process.argv)// 当没有输出参数的时候给个提醒if (program.args.length < 1) return program.help()// 第一个参数是 webpack,第二个参数是 project-namelet templateName = program.args[0]let projectName = program.args[1]if (!templateList[templateName]) {  console.log(chalk.red('\n Template does not exit! \n '))  return}if (!projectName) {  console.log(chalk.red('\n Project should not be empty! \n '))  return}let url = templateList[templateName]console.log(url)console.log(chalk.green('\n Start generating... \n'))// 呈现加载图标const spinner = ora('Downloading...')spinner.start()download(`direct:${url}`, `./${projectName}`, { clone: true }, (err) => {  if (err) {    spinner.fail()    console.log(chalk.red(symbols.error), chalk.red(`Generation failed. ${err}`))    return  }  // 完结加载图标  spinner.succeed()  console.log(chalk.green(symbols.success), chalk.green('Generation completed!'))  console.log('\n To get started')  console.log(`\n    cd ${projectName} \n`)})

这里用到 download-git-repo 下载近程模板,它的应用办法如下

const download = require('download-git-repo')download(repository, destination, options, callback)
  • repository 是近程仓库地址
  • destination 是寄存下载的文件门路,也能够间接写文件名,默认就是当前目录
  • options 是一些选项,比方 { clone:boolean } 示意用 http download 还是 git clone 的模式下载。
  • callback 是回调函数

此时,咱们执行一下cm init app demo ,就能看到以下成果了,根目录下就多了一个 demo 文件夹,就是新拉取的我的项目模板。

至此,一个前端脚手架就正式实现了。上面咱们把它公布到 npm 上。

公布 npm

公布流程

  1. 执行 npm login 登陆 npm 账号,如果没有账号的先注册一个
  2. 执行 npm publish 进行公布

公布到 npm 的脚手架名称就是 package.json 的 name 值,要留神的是公布名称不能反复。

公布完之后,咱们来验证一下。

  1. 执行 npm unlink 解绑一下全局命令
  2. 执行 npm install cm-vcli -g 全局装置脚手架
  3. 执行 cm -h

此时如果看到以下的成果,就阐明脚手架曾经公布并装置胜利了。