时间:2023-2-24 作者:悬浮的青春 分类: javascript
如何实现一个cli:输入一个git仓库的url(即.pb文件的url),就能自动创建好.js
代码文件。
最近做一个全新的h5元宇宙项目(社交类型的游戏),其特点就是接口特别多,前后端通讯特别频繁。想要高效率进行联调和高效率通讯,就不能使用http接口文档的方式。
方案:前后端通过封装websocket进行双向通讯,接口定义采用Protocol Buffers
(简称PB协议)进行定义。
优点:websocket 具有高性能的特点,Protocol Buffers
又非常适合自动化生成代码。
那么通过这样一套机制,就满足了前后端通讯协作的要求。【题外话:除了单纯web开发,很多都是这样进行前后端协作,尤其是客户端开发】
第一步:后端编写并发布PB协议到git仓库。
第二步:前端将这些PB协议复制到自己的前端项目中。
第三步: 根据这些协议,再利用protobufjs
工具,生成.js
或.d.ts
文件,供自己使用。
痛点1:需要后端主动通知前端有接口协议变动
痛点2:pb文件一下子非常多,可能有几十个文件更改,需要挨个复制到前端项目中
痛点3:protobufjs
工具不太会用,有学习成本。
能不能实现一个cli:输入一个git仓库的链接,就能自动创建好.js
文件?
答案是肯定的:
所谓实现方案,就是如何实现一个这样的cli:
1. 编写一份满足要求的模板配置,
2. 通过cli命令,将这个配置文件复制到目标项目工程,
3. 通过cli命令,读取配置文件后从git仓库里获取pb文件。
4. 通过cli命令,根据pb文件创建目标文件(如.js文件)。
由于git提供了一个工具@gitbeaker/node
,可以自动拉取文件到另一个仓库。
又因为 protobufjs-cli
支持将pb文件生成为.js
或.d.ts
等类型的文件。
那么我们就能想到一份配置类似如下:
const theConfig = {
gitLab: { // @gitbeaker/node 需要的配置
host: 'https://git.xxxxxx.com', // 你的git仓库
token: '你的git的token',
projectId: '项目id',
protoNames: [
'xxx/xxxx.proto', // 仓库中需要转换文件
'xxx/xxxx.proto',
],
targetPath: 'protos', // 在前端项目中的工作目录
branch: 'master' // 仓库分支
},
protobufjs: { // 这些是需要生成的js文件的格式定义
wrap: "commonjs",
"keep_case": true,
es6: false,
},
needChangeToDTS: true, // 是否生成.d.ts
needChangeToJS: true, // 是否生成.js
needChangeToJSON: true, // 是否生成json
}
完整代码看 github: pb-2-js
以下前提知识非常重要,为怎么构建自己的命令行工具,本文省略:
前提知识1:通过npm的bin命令,可以创建自己的命令行工具。类似于vue-cli的命令行工具。详细可以查看https://docs.npmjs.com/cli/v9/configuring-npm/package-json#bin
前提知识2:通过commander或inquider等工具,就可以实现对话方式的命令行。类似于vue-cli询问你需要创建一个什么类型的的vue工程。
以下代码是cli询问是否进行初始化后,将配置复制到项目文件的代码
// 创建配置文件 pbconfigName 是配置文件的名称
const generateFile = (a, pbconfigName) => {
// template 是第一步生成的配置,通过fs 写入到项目文件中
fs.writeFileSync(`./${pbconfigName}`, JSON.stringify(template, null, 4))
}
完整代码看 github: pb-2-js
从git仓库里读取文件,就是使用@gitbeaker/node
工具,再根据配置文件,自动将git仓库的pb文件,复制到本地项目。
import fs from 'fs'
import shelljs from 'shelljs'
import template from "./template.js";
import {Gitlab} from "@gitbeaker/node";
// 读取配置文件,下载对应pb文件
const readConfigCreatePb = async (pbConfigName) => {
const config = JSON.parse(fs.readFileSync(`./${pbConfigName}`).toString())
const gitLabConf = config.gitLab
const api = new Gitlab(gitLabConf)
const {projectId, protoNames, targetPath, branch} = gitLabConf
for (let protoName of protoNames) {
const data = await api.RepositoryFiles.show(projectId, protoName, branch)
let content = Buffer.from(data.content, 'base64').toString('utf8')
const targetFile = `${targetPath}/${protoName}`
const fList = targetFile.split('/')
fList.splice(fList.length-1, 1)
if (!fs.existsSync(fList.join('/'))) { // 创建文件夹
fs.mkdirSync(fList.join('/'),{ recursive: true })
}
fs.writeFileSync(`${targetPath}/${protoName}`, content)
}
}
完整代码看 github: pb-2-js
本地得到从pb文件之后,需要将pb文件转换为目标文件(.js/.d.ts文件)。但是protobufjs-cli
提供的也是cli命令,所以我们需要在代码里通过js调用cli命令,以生成目标文件。 那么就需要我们构建xx.sh
可执行文件,然后再执行它即可。
// 根据pb文件,生成对应js/ts文件
const readPbCreateJs = async (pbConfigName) => {
const config = JSON.parse(fs.readFileSync(`./${pbConfigName}`).toString())
const {gitLab, needChangeToDTS, needChangeToJS, needChangeToJSON, protobufjs} = config
const {protoNames, targetPath} = gitLab
let commend = ''
// 根据配置的pb文件数量,创建命令
for (let protoName of protoNames) {
const targetFile = `${targetPath}/${protoName}`
const fList = targetFile.split('/')
fList.splice(fList.length-1, 1)
const filePath = fList.join('/')
commend += (`pbjs -t static-module -w ${protobufjs.wrap} ${protobufjs.keep_case ? '--keep-case': ''} ${protobufjs.es6 ? '--es6' : ''} -o ${targetFile.replace('.proto', '.js')} ${targetFile}\n`)
if (needChangeToDTS) {
commend += (`pbts -o ${targetFile.replace('.proto', '.d.ts')} ${targetFile.replace('.proto', '.js')}\n`)
}
if (needChangeToJSON) {
commend += (`pbjs -t json ${targetFile} -o ${targetFile.replace('.proto', '.json')}\n`)
}
}
// 将执行命令,写入gen.sh文件
fs.writeFileSync(`${targetPath}/gen.sh`, commend)
// 执行命令
shelljs.exec(`sh ${targetPath}/gen.sh`)
// 如果不需要js文件,再删除
if (!needChangeToJS) {
for (let protoName of protoNames) {
const targetFile = `${targetPath}/${protoName}`
const fList = targetFile.split('/')
fList.splice(fList.length-1, 1)
const filePath = fList.join('/')
const dir = fs.readdirSync(filePath)
for (let file of dir) {
if (file.endsWith('.js')) {
fs.unlinkSync(`${filePath}/${file}`)
}
}
}
}
}
完整代码看 github: pb-2-js
这是初版cli,为什么初版就发出来分享呢,因为我认为初版才是解决核心问题的,后面逻辑复杂了,再分享出来,就不好理解了。
类似的自动生成API工具已经有了,比如yapi、swaggar。但是均基于http的接口文档,本文是基于Protocol Buffers的。