搭建一个前端脚手架cli并发布

201 阅读3分钟

前言

在日常开发中,随着业务业务增多需要新建多个项目,会沉淀出一些项目模板。 如果每次都是通过拷贝到新项目比较麻烦,为了在不同的项目中可以进行复用,可以将一些模板集成到脚手架中, 这样进行项目初始化时就能直接使用了。

为什么需要脚手架

  1. 减少重复性的工作,不再从零创建一个项目,或者复制粘贴另一个项目的代码 。
  2. 根据动态交互生成项目结构和配置文件,具备更高的灵活性和人性化定制的能力 。
  3. 有利于多人开发协作,避免了人工传递文件的繁琐。
  4. 可以集成多套开发模板,根据项目需要选择合适的模板。

创建脚手架流程

1 创建文件夹,执行npm init -y

新建一个文件夹,命名为my-cli,在该目录下执行npm init -y进行初始化,此时就会产生一个packages.json文件

2 安装第三方库

npm install chalk commander download-git-repo inquirer ora log-symbols

实现一个脚手架,常用工具 commander: 命令行工具 download-git-repo: 用来下载远程模板 inquirer: 交互式命令行工具 ora: 显示 loading 动画 chalk: 修改控制台输出内容样式 log-symbols: 显示出 √ 或 × 等的图标 handlebars.js 用户提交的信息动态填充到文件中

3 在根目录下新建一个bin文件夹,并在bin目录下新建一个无后缀名的cm文件

3.1 cm 文件内容

#!/usr/bin/env node
console.log('hello world')

这是脚手架的入口文件,用node ./bin/cm运行一下,控制台输出 hello world

3.2 每次输入node ./bin/cm 这个命令有点麻烦,我们可以在 package.json 进行命令配置

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

此时执行 npm link将命令挂载到全局,然后再输入 cm 就可以到达刚才node ./bin/cm 的效果了。

4 创建脚手架命令

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

4.1 编写cm文件

#!/usr/bin/env node
const 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,就会看到以下的效果

4.2 改一下 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安装版本过高,rquirer识别不到,存在的问题,可以降低inquirer版本试试,inquirer@6.2.2

5 建立模板

在根目录下新建一个commands文件夹,后续文件都在这个文件夹下创建

5.1 新建一个项目模板 add.js

#!/usr/bin/env node

const inquirer = require('inquirer')
const fs = require('fs')
const templateList = require(`${__dirname}/../template`)
const { showTable } = require(`${__dirname}/../utils/showTable`)
const symbols = require('log-symbols')
const chalk = require('chalk')
chalk.level = 1

let question = [
  {
    name: 'name',
    type: 'input',
    message: '请输入模板名称',
    validate(val) {
      if (!val) {
        return 'Name is required!'
      } 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 ,并输入项目模板名称和地址,就能看到以下效果了

5.2 新建一个文件delete.js

#!/usr/bin/env node

const inquirer = require('inquirer')
const fs = require('fs')
const templateList = require(`${__dirname}/../template`)
const { showTable } = require(`${__dirname}/../utils/showTable`)
const symbols = require('log-symbols')
const chalk = require('chalk')
chalk.level = 1

let question = [
  {
    name: 'name',
    message: '请输入要删除的模板名称',
    validate(val) {
      if (!val) {
        return 'Name is required!'
      } 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 ,输入要删除的模板,就能看到以下效果了

5.3 新建一个文件list.js

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

showTable(templateList)

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

5.4 新建一个文件init.js

初始化一个项目模板,这是最重要的一部分

#!/usr/bin/env node

const 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 = 1

program.usage('<template-name> [project-name]')
program.parse(process.argv)
// 当没有输入参数的时候给个提示
if (program.args.length < 1) return program.help()

// 第一个参数是 webpack,第二个参数是 project-name
let templateName = program.args[0]
let projectName = program.args[1]
console.log(templateList, projectName, templateList[projectName])


let url = templateList[projectName] || templateList.templeteUrl
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`)
})

参考文章zhuanlan.zhihu.com/p/413036370