基础工具总览
搭建脚手架的目的是为了快速的搭建项目的基本结构并给团队提供一致的规范,让团队成员按照约定进行编码。目前日常工作中常用的脚手架有vue-cli、create-react-app、vite 等等,都是通过简单的初始化命令,完成内容的快速构建,但这些都仅仅提供了最基础的功能,本文将从基础工具的角度阐述怎样搭建一套服务于业务的脚手架。
在了解脚手架之前,我们先谈谈在实际生产中搭建一个脚手架或者阅读其他脚手架源码的时候需要了解的一些工具库:
| 工具名 | 备注 |
|---|---|
| commander.js | 完整的 node.js 命令行解决方案。 |
| chalk | 终端样式美化 |
| Inquirer.js | 常用交互式命令行用户界面的集合 |
| ora | 终端控制loading样式 |
| download-git-repo | 下载远程模版 |
| fs-extra | 系统fs的扩展 |
| ejs | 嵌入式javascript模板 |
| semver | npm语义版本控制器 |
| boxen | 在终端中创建框 |
commander.js 命令行指令
初始化一个简单的cli项目
{
"name": "@thrusterx/cli",
"version": "0.0.1-beta.0",
"description": "thrusterx frontend team",
"main": "dist/scripts/bin/index.js",
"author": "thrusterX",
"license": "ISC",
"bin": {
"t7x": "dist/scripts/bin/index.js"
},
"files": [
"dist"
],
"dependencies": {
"commander": "^8.3.0",
}
}
目录结构:
thrusterx-cli
├─ scripts
│ ├─ bin
│ ├─ └─ index.ts
├─ package-lock.json
└─ package.json
引入commander
#!/usr/bin/env node
import { Command } from 'commander'
import pkg from '../../package.json'
const program = new Command()
program.name('t7x').usage('<command> [options]')
program.version(`@thrusterx/cli ${pkg.version}`).usage('<command> [options]')
program
.command('init <app-name>')
.description('init a new project')
.option('-d, --default', 'skip init project options')
.option('-c --all-config', 'create project with all config')
.option('-a, --all-plugins', 'create project with all plugins')
.option('-u, --uninstalled', 'skip install denpendencies')
.action((name: string, options: InitCliOptions) => {
console.log('do something')
})
program.parse()
npm link 链接到全局
- 执行 npm link 将应用 @thrusterx/cli 链接到全局
- 完成之后,在命令行中执行 t7x
$ t7x -h
Usage: t7x <command> [options]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
init [options] <app-name> init a new project
用chalk美化你的控制台
program
.command('init <app-name>')
.description('init a new project')
.option('-d, --default', 'skip init project options')
.option('-c --all-config', 'create project with all config')
.option('-a, --all-plugins', 'create project with all plugins')
.option('-u, --uninstalled', 'skip install denpendencies')
.action((name: string, options: InitCliOptions) => {
// 颜色
console.log('project name is ' + chalk.yellow(name))
console.log('project name is ' + chalk.green(name))
// 背景色
console.log('project name is ' + chalk.bgBlue(name))
// 文本样式
console.log('project name is ' + chalk.bold(name))
// RGB颜色&十六进制
console.log('project name is ' + chalk.rgb(255, 191, 0).underline(name))
console.log('project name is ' + chalk.hex('#3C3C3C').bold(name))
console.log('project name is ' + chalk.bgHex('#FF0000').bold(name))
})
program.parse(process.argv)
inquirer 让脚手架更灵活
inquirer 可以让你的脚手架通过简单的终端交互变得更灵活,官方文档
import { QuestionCollection } from 'inquirer'
export const promptList: Array<QuestionCollection> = [
{
type: 'input',
message: 'Version:',
name: 'version',
default: '0.0.1',
},
{
type: 'input',
message: 'Author:',
name: 'author',
default: 'thruster-x',
},
{
type: 'input',
message: 'Description:',
name: 'description',
default: 'thruster-x project',
},
{
type: 'list',
message: 'Please set the project template',
name: 'layoutTemp',
choices: ['baseLayout', 'emptyLayout', 'none'],
filter: (val: string) => {
return val
},
},
]
async function checkOptions() {
await inquirer
.prompt(promptList)
.then((answers: any) => {
console.log(answers)
Object.assign(defaultOptions, answers)
})
.catch((error: any) => {
return error
})
}
ora 命令行 loading 动效
配置策略下载模板
关于下载模板通常都是通过 download-git-repo 拉取远程仓库模板,参考 create-vite 仓库提供了不同项目的模板,然后通过 ejs 将配置的参数写入到文件中
download('flippidippi/download-git-repo-fixture', 'test/tmp', function (err) {
console.log(err ? 'Error' : 'Success')
})
const template: string = fs.readFileSync(filePath, 'utf-8')
const outputCode: string = ejs.compile(template, {})(config)
fs.writeFileSync(filePath, outputCode)
子进程安装依赖
import { exec } from 'child_process'
exec(`cd ${config.name} && git init && npm install`, (err: any) => {
console.log(err ? 'error' : 'success')
})
脚手架版本升级校验
脚手架版本控制遵循 semantic versioning 2.0.0,当仓库有新的版本发布时,你执行本地的脚手架命令,控制台会输出升级提示
const nodeModules = resolve('../../../package.json')
const { latest, beta } = await getCliVersion()
fs.readFile(nodeModules, (err, data: any) => {
if (semver.lt(version, latest)) {
console.log(boxen('xxxxxx'))
} else {
....
}
})
当新版本可用时:
当beta版本发布后: