Skip to content

脚手架设计思路与制作过程

背景

前端的同学在搭建一个新项目的时候,往往要经历一个很蛋疼的过程:

  1. 使用 vite / Vue CLI / create-vue 等工具来初始化工程
  2. 安装一堆插件,比如:vue-routerpiniaelement-plusaxios 等等
  3. axios二次封装(请求拦截等)
  4. 集成售楼登录
  5. 布局设计(顶部栏、侧边菜单栏等)
  6. ……

然而,以上工作都是机械式的工作,但是每次都要重复这个过程,不仅毫无意义,而且耗费大量的时间。

那么,有没有一种方式,能够把这些工作都统一起来?比如提供一个工具,我使用这个工具创建项目的时候,生成的工程代码里就把以上的工作全做好了,而我只需要关注业务本身的开发即可,做到开箱即用。

答案当然是有的,我们也可以制作一个类似于 Vue CLI 这样的脚手架工具,我们通过它使用命令行就可以生成开箱即用的初始代码,我们把它命名为 @b-flash/cli

设计思路

我们先通过以下这张流程图理解一下 @b-flash/cli 的工作流程。

脚手架设计

不难看出,整个流程非常简单:

  1. 终端输入 b-flash create <项目名称>
  2. 选择项目类型( 中后台 / H5 )
  3. 根据选择的项目类型从不同的 git 仓库拉取代码模板
  4. 项目创建成功

整体工作就可以分成 两个部分

  1. 准备好代码模板,放到 git 仓库上
  2. 编写 @b-flash/cli 的流程代码

确定好工作内容后,我们就可以开展工作了。

代码模板

TIP

我们以中后台模板为例子,以下提到的模板都指中后台模板

代码模板实际上就是使用 vite 创建项目,把开始提到的蛋疼的过程全都集成到里面去,然后推送到代码仓库。最终 模板 的目录结构长这样

b-flash-admin
├── .env ———————————————————————————————— 所有环境通用配置文件
├── .env.prod ——————————————————————————— 生产环境配置文件
├── .env.sit ———————————————————————————— 测试环境配置文件
├── .eslintignore ——————————————————————— eslint 忽略文件
├── .eslintrc.cjs ——————————————————————— eslint 配置文件
├── .gitignore —————————————————————————— git 忽略文件
├── .husky —————————————————————————————— 代码提交前置脚本
├── .prettierrc.cjs ————————————————————— prettier 配置文件
├── .prettierignore ————————————————————— prettier 忽略文件
├── .vscode ————————————————————————————— vscode 配置文件
├── README.md ——————————————————————————— 必读!!!
├── index.html —————————————————————————— html 入口文件
├── package-lock.json ——————————————————— package-lock.json
├── package.json ———————————————————————— package.json
├── public —————————————————————————————— 不用经过打包的文件放这里
├── src ————————————————————————————————— 源代码文件夹
│ ├── api ——————————————————————————————— 接口调用写这里
│ │ ├── menu.js ————————————————————————— 菜单相关接口
│ │ └── user.js ————————————————————————— 用户相关接口
│ ├── app.vue ——————————————————————————— app.vue
│ ├── components ———————————————————————— 公共组件放这里
│ ├── layout ———————————————————————————— 布局组件
│ │ ├── b-aside.vue ————————————————————— 左侧栏组件
│ │ ├── b-header.vue ———————————————————— 顶部栏组件
│ │ ├── b-menu.vue —————————————————————— 左侧栏递归菜单组件
│ │ └── b-tabs.vue —————————————————————— 多标签页实现组件
│ ├── main.js ——————————————————————————— 应用入口文件
│ ├── pages ————————————————————————————— 所有页面放这里
│ │ ├── module1 ————————————————————————— 模块 1(示例)
│ │ │ ├── index.vue ————————————————————— 模块 1 的路由嵌套公共组件(示例)
│ │ │ ├── page1.vue ————————————————————— 模块 1 的页面 1(示例)
│ │ │ └── page2.vue ————————————————————— 模块 1 的页面 2(示例)
│ │ ├── module2 ————————————————————————— 模块 2(示例)
│ │ │ ├── index.vue ————————————————————— 模块 2 的路由嵌套公共组件(示例)
│ │ │ ├── page1.vue ————————————————————— 模块 2 的页面 1(示例)
│ │ │ └── page2.vue ————————————————————— 模块 2 的页面 2(示例)
│ ├── router ———————————————————————————— 路由写这里
│ │ └── index.js ———————————————————————— 路由文件
│ ├── store ————————————————————————————— 状态存储写这里
│ │ ├── tabs.js ————————————————————————— 标签页的状态存储
│ │ └── user.js ————————————————————————— 用户相关的状态储存
│ ├── style.less ———————————————————————— 全局样式定义写这里
│ ├── utils ————————————————————————————— 工具类库写这里
│ │ ├── request.js —————————————————————— 统一请求拦截文件
│ │ └── url-helper.js ——————————————————— url 处理相关的工具库
└── vite.config.js —————————————————————— vite 配置文件
b-flash-admin
├── .env ———————————————————————————————— 所有环境通用配置文件
├── .env.prod ——————————————————————————— 生产环境配置文件
├── .env.sit ———————————————————————————— 测试环境配置文件
├── .eslintignore ——————————————————————— eslint 忽略文件
├── .eslintrc.cjs ——————————————————————— eslint 配置文件
├── .gitignore —————————————————————————— git 忽略文件
├── .husky —————————————————————————————— 代码提交前置脚本
├── .prettierrc.cjs ————————————————————— prettier 配置文件
├── .prettierignore ————————————————————— prettier 忽略文件
├── .vscode ————————————————————————————— vscode 配置文件
├── README.md ——————————————————————————— 必读!!!
├── index.html —————————————————————————— html 入口文件
├── package-lock.json ——————————————————— package-lock.json
├── package.json ———————————————————————— package.json
├── public —————————————————————————————— 不用经过打包的文件放这里
├── src ————————————————————————————————— 源代码文件夹
│ ├── api ——————————————————————————————— 接口调用写这里
│ │ ├── menu.js ————————————————————————— 菜单相关接口
│ │ └── user.js ————————————————————————— 用户相关接口
│ ├── app.vue ——————————————————————————— app.vue
│ ├── components ———————————————————————— 公共组件放这里
│ ├── layout ———————————————————————————— 布局组件
│ │ ├── b-aside.vue ————————————————————— 左侧栏组件
│ │ ├── b-header.vue ———————————————————— 顶部栏组件
│ │ ├── b-menu.vue —————————————————————— 左侧栏递归菜单组件
│ │ └── b-tabs.vue —————————————————————— 多标签页实现组件
│ ├── main.js ——————————————————————————— 应用入口文件
│ ├── pages ————————————————————————————— 所有页面放这里
│ │ ├── module1 ————————————————————————— 模块 1(示例)
│ │ │ ├── index.vue ————————————————————— 模块 1 的路由嵌套公共组件(示例)
│ │ │ ├── page1.vue ————————————————————— 模块 1 的页面 1(示例)
│ │ │ └── page2.vue ————————————————————— 模块 1 的页面 2(示例)
│ │ ├── module2 ————————————————————————— 模块 2(示例)
│ │ │ ├── index.vue ————————————————————— 模块 2 的路由嵌套公共组件(示例)
│ │ │ ├── page1.vue ————————————————————— 模块 2 的页面 1(示例)
│ │ │ └── page2.vue ————————————————————— 模块 2 的页面 2(示例)
│ ├── router ———————————————————————————— 路由写这里
│ │ └── index.js ———————————————————————— 路由文件
│ ├── store ————————————————————————————— 状态存储写这里
│ │ ├── tabs.js ————————————————————————— 标签页的状态存储
│ │ └── user.js ————————————————————————— 用户相关的状态储存
│ ├── style.less ———————————————————————— 全局样式定义写这里
│ ├── utils ————————————————————————————— 工具类库写这里
│ │ ├── request.js —————————————————————— 统一请求拦截文件
│ │ └── url-helper.js ——————————————————— url 处理相关的工具库
└── vite.config.js —————————————————————— vite 配置文件

脚手架

代码模板准备好之后,剩下的就是制作脚手架工具了。

脚手架工具目录结构:

b-flash-admin
├── bin ———————————————————————————————— 脚本目录
│ ├── index.js ————————————————————————— 程序入口
│ └── create.js ———————————————————————— create 命令入口
├── .gitignore ————————————————————————— 上传到git时要忽略的文件
├── README ————————————————————————————— 必读
└── package.json ——————————————————————— package.json
b-flash-admin
├── bin ———————————————————————————————— 脚本目录
│ ├── index.js ————————————————————————— 程序入口
│ └── create.js ———————————————————————— create 命令入口
├── .gitignore ————————————————————————— 上传到git时要忽略的文件
├── README ————————————————————————————— 必读
└── package.json ——————————————————————— package.json

index.js

js
#!/usr/bin/env node

import { createRequire } from 'module'
import { Command } from 'commander'
import create from './create.js'
import chalk from 'chalk'
const program = new Command()
const require = createRequire(import.meta.url)
const { version } = require('../package.json')

program
    .name('b-flash')
    .description(
        chalk.green(`
        ██████╗       ███████╗██╗      █████╗ ███████╗██╗  ██╗
        ██╔══██╗      ██╔════╝██║     ██╔══██╗██╔════╝██║  ██║
        ██████╔╝█████╗█████╗  ██║     ███████║███████╗███████║
        ██╔══██╗╚════╝██╔══╝  ██║     ██╔══██║╚════██║██╔══██║
        ██████╔╝      ██║     ███████╗██║  ██║███████║██║  ██║
        ╚═════╝       ╚═╝     ╚══════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝
                                                              
        `)
    )
    .version(version, '-v, --version', '输出版本号')
    .helpOption('-h, --help', '输出帮助信息')

// 定义使用方法
program.usage('<command>')

program
    .command('create <name>')
    .description('创建项目')
    .action((name) => {
        create(name)
    })
    .helpOption('-h, --help', '创建项目')

program
    .command('help [command]')
    .description('显示帮助信息')
    .action((cmd) => {
        switch (cmd) {
            case 'create':
                console.log('碧桂园营销前端专用,创建项目工程起始代码')
                break
            default:
                program.help()
        }
    })

// 错误命令时的显示帮助
program.parse(process.argv)

if (!program.args.length) {
    program.help()
}
#!/usr/bin/env node

import { createRequire } from 'module'
import { Command } from 'commander'
import create from './create.js'
import chalk from 'chalk'
const program = new Command()
const require = createRequire(import.meta.url)
const { version } = require('../package.json')

program
    .name('b-flash')
    .description(
        chalk.green(`
        ██████╗       ███████╗██╗      █████╗ ███████╗██╗  ██╗
        ██╔══██╗      ██╔════╝██║     ██╔══██╗██╔════╝██║  ██║
        ██████╔╝█████╗█████╗  ██║     ███████║███████╗███████║
        ██╔══██╗╚════╝██╔══╝  ██║     ██╔══██║╚════██║██╔══██║
        ██████╔╝      ██║     ███████╗██║  ██║███████║██║  ██║
        ╚═════╝       ╚═╝     ╚══════╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝
                                                              
        `)
    )
    .version(version, '-v, --version', '输出版本号')
    .helpOption('-h, --help', '输出帮助信息')

// 定义使用方法
program.usage('<command>')

program
    .command('create <name>')
    .description('创建项目')
    .action((name) => {
        create(name)
    })
    .helpOption('-h, --help', '创建项目')

program
    .command('help [command]')
    .description('显示帮助信息')
    .action((cmd) => {
        switch (cmd) {
            case 'create':
                console.log('碧桂园营销前端专用,创建项目工程起始代码')
                break
            default:
                program.help()
        }
    })

// 错误命令时的显示帮助
program.parse(process.argv)

if (!program.args.length) {
    program.help()
}

create.js

js
import { exec } from 'child_process'
import { select, spinner } from '@clack/prompts'
import chalk from 'chalk'

const s = spinner()

const removeGit = (name) => {
    let cmd = `cd ${name}`
    switch (process.platform) {
        case 'darwin':
            cmd += '&& rm -rf .git'
            break
        case 'win32':
            cmd += ' && rd /s /q .git'
            break
    }
    exec(cmd, (e) => {
        if (e) {
            s.stop(
                chalk.yellow('项目创建成功,但.git文件夹未清除,建议您手动清除')
            )
            console.log(chalk.yellow(e))
            return
        }
        s.stop(chalk.green('项目创建成功,运行以下命令启动项目'))
        console.log(
            chalk.green(`
    cd ${name}
    npm install
    npm run dev
        `)
        )
    })
}

const create = async (name) => {
    const projectType = await select({
        message: chalk.green('你想创建什么类型的项目?'),
        options: [
            {
                value: 'admin',
                label: '中后台',
            },
            {
                value: 'h5',
                label: '移动端',
            },
        ],
    })

    s.start('正在创建项目')

    switch (projectType) {
        case 'admin':
            exec(
                `git clone https://git.bgy.com.cn/bu00241/b-flash/b-flash-admin.git ${name}`,
                (err) => {
                    if (err) {
                        s.stop(chalk.red('项目创建失败'))
                        console.error(err)
                        return
                    }
                    removeGit(name)
                }
            )
            break
        case 'h5':
            exec(
                `git clone https://git.bgy.com.cn/bu00241/b-flash/b-flash-h5.git ${name}`,
                (err) => {
                    if (err) {
                        s.stop(chalk.red('项目创建失败'))
                        console.error(err)
                        return
                    }
                    removeGit(name)
                }
            )
            break
    }
}

export default create
import { exec } from 'child_process'
import { select, spinner } from '@clack/prompts'
import chalk from 'chalk'

const s = spinner()

const removeGit = (name) => {
    let cmd = `cd ${name}`
    switch (process.platform) {
        case 'darwin':
            cmd += '&& rm -rf .git'
            break
        case 'win32':
            cmd += ' && rd /s /q .git'
            break
    }
    exec(cmd, (e) => {
        if (e) {
            s.stop(
                chalk.yellow('项目创建成功,但.git文件夹未清除,建议您手动清除')
            )
            console.log(chalk.yellow(e))
            return
        }
        s.stop(chalk.green('项目创建成功,运行以下命令启动项目'))
        console.log(
            chalk.green(`
    cd ${name}
    npm install
    npm run dev
        `)
        )
    })
}

const create = async (name) => {
    const projectType = await select({
        message: chalk.green('你想创建什么类型的项目?'),
        options: [
            {
                value: 'admin',
                label: '中后台',
            },
            {
                value: 'h5',
                label: '移动端',
            },
        ],
    })

    s.start('正在创建项目')

    switch (projectType) {
        case 'admin':
            exec(
                `git clone https://git.bgy.com.cn/bu00241/b-flash/b-flash-admin.git ${name}`,
                (err) => {
                    if (err) {
                        s.stop(chalk.red('项目创建失败'))
                        console.error(err)
                        return
                    }
                    removeGit(name)
                }
            )
            break
        case 'h5':
            exec(
                `git clone https://git.bgy.com.cn/bu00241/b-flash/b-flash-h5.git ${name}`,
                (err) => {
                    if (err) {
                        s.stop(chalk.red('项目创建失败'))
                        console.error(err)
                        return
                    }
                    removeGit(name)
                }
            )
            break
    }
}

export default create

代码很少,主要是定义了 create 命令,选择 中后台 还是 H5 应用,然后根据选择从 gitlab 下载对应的模板。以及定义帮助命令和版本号等辅助功能。

重点是使用了 commander 这个模块,超级强大的命令行工具库。

上次更新于: