在日常开发中,咱们会依据教训积淀出一些我的项目模板,在不同在我的项目中能够进行复用。如果是每次都是通过拷贝代码到新我的项目的话,这样会比拟麻烦,而且容易出错,此时咱们就会想能不能将一些模板集成到脚手架(相似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 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
的成果了。
- 定义多个命令
咱们再 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
新增一个我的项目模板
- 通过命令行交互,让用户输出模板名称和模板的地址
- 将用户输出的模板信息新增写入到
template.json
文件中 - 打印出所有的我的项目模板
看一下代码
#!/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
删除一个我的项目模板,这个就好理解,步骤如下
- 通过命令行交互,让用户输出要删除的我的项目模板名称
- 删除用户输出的模板数据,而后再将更新的数据写入到
template.json
文件中 - 打印出所有的我的项目模板
代码如下
#!/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
初始化一个我的项目模板,这是最重要的一部分,步骤如下
- 通过命令行交互,让用户模板的名称和我的项目的名称
- 校验模板是否存在,项目名称是否填写
- 开始下载模板,显示加载图标
- 实现模板下载,暗藏加载图标
先来看一下代码
#!/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
公布流程
- 执行
npm login
登陆 npm 账号,如果没有账号的先注册一个 - 执行
npm publish
进行公布
公布到 npm 的脚手架名称就是 package.json 的 name 值,要留神的是公布名称不能反复。
公布完之后,咱们来验证一下。
- 执行
npm unlink
解绑一下全局命令 - 执行
npm install cm-vcli -g
全局装置脚手架 - 执行
cm -h
此时如果看到以下的成果,就阐明脚手架曾经公布并装置胜利了。