乐趣区

关于前端:从零搭建一个前端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 这样的命令疾速初始化一个我的项目,无需本人从零开始一步步配置,大大提高了开发效率。

为什么须要脚手架?

  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('<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 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,并输出我的项目模板名称和地址,就能看到以下成果了

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

cm list

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

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

showTable(templateList)

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

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('<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]

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

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

退出移动版