前言
在日常开发中,随着业务业务增多需要新建多个项目,会沉淀出一些项目模板。 如果每次都是通过拷贝到新项目比较麻烦,为了在不同的项目中可以进行复用,可以将一些模板集成到脚手架中, 这样进行项目初始化时就能直接使用了。
为什么需要脚手架
- 减少重复性的工作,不再从零创建一个项目,或者复制粘贴另一个项目的代码 。
- 根据动态交互生成项目结构和配置文件,具备更高的灵活性和人性化定制的能力 。
- 有利于多人开发协作,避免了人工传递文件的繁琐。
- 可以集成多套开发模板,根据项目需要选择合适的模板。
创建脚手架流程
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`)
})