背景
虎妞同学,在工作中,遇到了重构旧运营系统的技术需求,旧系统都是前后端不分离的项目
因此对于前端需要一套整体的后台管理框架。
功能上,要满足登录,权限管理,菜单配置,用户管理,字典管理,角色管理等后台管理系统的常规功能,
技术上,要使用vue3,element-ui等主流框架。
很快虎妞同学,就确定了技术框架的选型,并创建了项目仓库,作为项目模版。
开发中遇到的问题
在具体业务中,虎妞在创建新项目时,每次手动去克隆项目模版,然后再修改相关参数。
比如env中的系统名称,package.json中的名字,描述和作者。
很快,同事们就发现这种方式的几个问题
- 手动修改配置容易出现漏改
- 每次克隆项目太过麻烦
- 模版项目的优化无法同步
解决方案
虎妞计划,建立一个简单的脚手架,打包成一个npm包,发布到公司私有库中,其他同事只要在全局安装了这个包,就只可以执行命令创建项目,然后输入需要配置的参数,就可以拉下来一个新的项目。
想到这里,思路清晰之后,虎妞说干就干。
首先在掘金上搜索了相关的文章,大同小异,但是需要接入公司的业务中,还需要修改一些内容。
相关依赖库
用到的三方依赖库有这些:
- chalk: 用于在控制台输出彩色的文本,使输出信息更加清晰易读。
- commander: 是一个命令行接口工具,可以轻松地创建命令行应用程序,并解析命令行参数。
- dotenv: 用于加载环境变量,从而避免将配置敏感信息硬编码到代码中。
- download-git-repo: 用于从 Git 仓库下载文件或整个仓库,支持多种协议(如 HTTP、SSH)和分支。
- fs-extra: 扩展了 Node.js 原生的文件系统模块(fs),提供了更多的方法,如复制、移动、删除等,方便文件的操作。
- inquirer: 提供了一个交互式命令行界面,可以方便地与用户进行交互,获取输入数据。
- ora: 用于在控制台显示进度条或者一些符号,表示正在进行某个耗时的操作,提高用户体验。
"chalk": "4.0.0",
"commander": "10.0.0",
"dotenv": "^16.1.4",
"download-git-repo": "3.0.2",
"fs-extra": "11.1.0",
"handlebars": "^4.7.7",
"inquirer": "8.0.0",
"ora": "6.1.2"
流程
1.新增help 指令和 create指令
- help 意思为查看帮助,
- create 为创建项目命令,接受一个参数,该参数为要新建的项目名字。
-
- -f 为强制覆盖命令
import { program } from 'commander'
program
.name("cf-cli")
.usage(`<command> [option]`)
.version(`cf-cli ${packageInfo.version}`);
program
.command("create <project-name>") // 增加创建指令
.description("创建新项目") // 添加描述信息
.option("-f, --force", "强制覆盖") // 强制覆盖
.action(async (projectName, cmd) => {
// 处理用户输入create 指令附加的参数
await create(projectName, cmd);
});
program.parse(process.argv);
2.当用户执行cf-cli create aaa 时,就会在开始当前目录下新建一个aaa 的项目。
3.优先判断当前目录下是否存在同名目录,
- 若存在执行第4条,
- 若不存在走第7条,请求项目模版。
此处使用fs.existsSync 方法判断
4.判断指令中是否使用了-f 参数
- 如果使用-f 或者--force 则直接删除重命目录
- 若没有使用强制覆盖命令,则继续执行第5条,走下一步判断
此处使用fs.remove 实现
5.询问当前用户是否要覆盖当前目录
- 若覆盖,则删除原有重名目录后,执行第6步。
- 若选择取消,则中断流程
此处主要使用Inquirer 库实现
代码语言:javascript复制import fs from "fs-extra"
import Inquirer from "inquirer"
const cwd = process.cwd();
const targetDirectory = path.join(cwd, projectName);
if (fs.existsSync(targetDirectory)) {
// 判断是否使用 --force 参数
if (options.force) {
// 删除重名目录(remove是个异步方法)
await fs.remove(targetDirectory);
} else {
let {isOverwrite} = await new Inquirer.prompt([
// 返回值为promise
{
name: "isOverwrite", // 与返回值对应
type: "list", // list 类型
message: "项目已经存在,请选择",
choices: [
{name: "覆盖", value: true},
{name: "取消", value: false},
],
},
]);
// 选择 Cancel
if (!isOverwrite) {
console.log("rn已取消");
return;
} else {
// 选择 Overwirte ,先删除掉原有重名目录
await fs.remove(targetDirectory);
console.log("rn已经删除原项目");
}
}
}
6.从gitlab 仓库拉取需要代码
此处使用了download-git-repo库
7.拉取成功后,询问用户,要求输入系统的中文标题。
此处主要使用Inquirer 库实现
8.获取到中文标题后,注入项目模版的env和package.json
修改env
这段代码定义了一个名为generateEnv的函数,它接受三个参数:
projectName表示项目名称,name表示要生成的.env文件的名称(例如.env.production),title表示应用的标题。
该函数的主要功能是读取指定的.env文件(路径由projectName和name组成),并将其中的VITE_APP_TITLE变量的值设置为传入的title,最后将修改后的内容覆盖原文件中的内容。
具体来说,它通过使用dotenv.parse解析.env文件的内容,然后将VITE_APP_TITLE的值替换为传入的title,接着将其余变量的键值对格式化为字符串,并写入到文件中。
代码语言:javascript复制generateEnv(projectName, '.env.production', '后台管理系统')
function generateEnv(projectName, name, title) {
const envFilePath = `${projectName}/${name}`;
const envConfig = dotenv.parse(fs.readFileSync(envFilePath));
envConfig.VITE_APP_TITLE = title;
const newEnvContent = Object.entries(envConfig)
.map(([key, value]) => `${key}=${value}`)
.join('n');
fs.writeFileSync(envFilePath, newEnvContent);
}
修改package.json
主要修改三个字段
- name 取创建项目时的名字
- description 取系统的中文名字
- author 取当前git 的用户名
-
- 这里使用了child_process库
import {execSync} from "child_process"
packageContent.author = execSync('git config user.name').toString().trim() || ''
9.打印成功的消息
这里使用了chalk 库来实现打印日志
代码语言:javascript复制export function Log(msg, color = 'blue') {
console.log(chalk[color](msg))
}
10.输出提示,结束流程。
代码语言:javascript复制Log(`rn${projectName}项目已成功创建`, 'white');
Log(`rncd ${projectName}`, 'green')
Log(`npm install`, 'green')
Log(`npm run dev`, 'green')
流程图示意
最后
在开发完这个脚手架之后,其实发现开发一个脚手架很简单,都是调用的三方库,难点在于怎么接入公司业务。对公司的统一模版如何管理和配置。
有脚手架的前提是有一个统一的项目模版。
现有的项目模版是一次性的,如果拉下来,就无法继续更新了,如果能够把整个项目模版打成一个安装包在项目中安装,这样我们唯一统一的项目模版,有新功能的更新,只需要改一次,其他平台更新依赖就可以了。