在日常开发中,咱们会依据教训积淀出一些我的项目模板,在不同在我的项目中能够进行复用。如果是每次都是通过拷贝代码到新我的项目的话,这样会比拟麻烦,而且容易出错,此时咱们就会想能不能将一些模板集成到脚手架(相似vue-cli
, create-react-app
)中,这样咱们进行初始化创立就能应用了呢?
比方自己有以下两套开发模板
基于vue-cli4和vant搭建的挪动端开发模板 vue-cli4-vant
基于vue-cli4和ant-design-vue构建的后盾管理系统简略模板 vue-cli4-vant
心愿执行相似 aaa init bbb ccc
这样的命令疾速初始化一个我的项目,无需本人从零开始一步步配置,大大提高了开发效率。
为什么须要脚手架?
- 缩小重复性的工作,不再从零创立一个我的项目,或者复制粘贴另一个我的项目的代码 。
- 依据动静交互生成我的项目构造和配置文件,具备更高的灵活性和人性化定制的能力 。
- 有利于多人开发合作,防止了人工传递文件的繁琐。
- 能够集成多套开发模板,依据我的项目须要抉择适合的模板。
第三方库的反对
实现一个脚手架,通常须要以下工具,后续咱们将会一一介绍。
- commander: 命令行工具
- download-git-repo: 用来下载近程模板
- inquirer: 交互式命令行工具
- ora: 显示 loading 动画
- chalk: 批改控制台输入内容款式
- log-symbols: 显示出 √ 或 × 等的图标
- handlebars.js 用户提交的信息动静填充到文件中
构建步骤
- 新建一个文件夹,命名为
cm-cli
(我的脚手架命名),在该目录下执行npm init -y
进行初始化,此时就会生产一个package.json
文件。 - 装置第三方工具库
npm install chalk commander download-git-repo inquirer ora log-symbols
- 在根目录下新建一个 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
的成果了。
- 定义多个命令
咱们再 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
新增一个我的项目模板
- 通过命令行交互,让用户输出模板名称和模板的地址
- 将用户输出的模板信息新增写入到
template.json
文件中 - 打印出所有的我的项目模板
看一下代码
#!/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
删除一个我的项目模板,这个就好理解,步骤如下
- 通过命令行交互,让用户输出要删除的我的项目模板名称
- 删除用户输出的模板数据,而后再将更新的数据写入到
template.json
文件中 - 打印出所有的我的项目模板
代码如下
#!/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
初始化一个我的项目模板,这是最重要的一部分,步骤如下
- 通过命令行交互,让用户模板的名称和我的项目的名称
- 校验模板是否存在,项目名称是否填写
- 开始下载模板,显示加载图标
- 实现模板下载,暗藏加载图标
先来看一下代码
#!/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
公布流程
- 执行
npm login
登陆 npm 账号,如果没有账号的先注册一个 - 执行
npm publish
进行公布
公布到 npm 的脚手架名称就是 package.json 的 name 值,要留神的是公布名称不能反复。
公布完之后,咱们来验证一下。
- 执行
npm unlink
解绑一下全局命令 - 执行
npm install cm-vcli -g
全局装置脚手架 - 执行
cm -h
此时如果看到以下的成果,就阐明脚手架曾经公布并装置胜利了。