从零搭建一个前端cli脚手架并发布到npm

在日常开发中,我们会根据经验沉淀出一些项目模板,在不同在项目中可以进行复用。如果是每次都是通过拷贝代码到新项目的话,这样会比较麻烦,而且容易出错,此时我们就会想能不能将一些模板集成到脚手架(类似vue-cli, create-react-app)中,这样我们进行初始化创建就能使用了呢?

比如本人有以下两套开发模板

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

希望执行类似 aaa init bbb ccc 这样的命令快速初始化一个项目,无需自己从零开始一步步配置,大大提高了开发效率。

脚手架源码地址 点击这里

安装使用

npm install cm-vcli -g

cm -h

为什么需要脚手架?

  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 node
console.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 node
const program = require('commander')

program.usage('')

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,就会看到以下的效果

从零搭建一个前端cli脚手架并发布到npm_第1张图片

此时我们再改一下 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 node

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

let 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 ,并输入项目模板名称和地址,就能看到以下效果了

从零搭建一个前端cli脚手架并发布到npm_第2张图片

cm detele

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

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

代码如下

#!/usr/bin/env node

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

let 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 ,输入要删除的模板,就能看到以下效果了

从零搭建一个前端cli脚手架并发布到npm_第3张图片

cm list

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

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

showTable(templateList)

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

从零搭建一个前端cli脚手架并发布到npm_第4张图片

cm init

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

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

先来看一下代码

#!/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(' [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]

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 文件夹,就是新拉取的项目模板。

从零搭建一个前端cli脚手架并发布到npm_第5张图片

至此,一个前端脚手架就正式完成了。下面我们把它发布到 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

此时如果看到以下的效果,就说明脚手架已经发布并安装成功了。

从零搭建一个前端cli脚手架并发布到npm_第6张图片

你可能感兴趣的:(从零搭建一个前端cli脚手架并发布到npm)