前端怎么实现一个”由接口文档自动生成代码“的cli?

时间:2023-2-24    作者:悬浮的青春    分类:


本文主要内容

如何实现一个cli:输入一个git仓库的url(即.pb文件的url),就能自动创建好.js代码文件。

一、背景:

最近做一个全新的h5元宇宙项目(社交类型的游戏),其特点就是接口特别多,前后端通讯特别频繁。想要高效率进行联调和高效率通讯,就不能使用http接口文档的方式。

1. 前后端怎么沟通协作?

方案:前后端通过封装websocket进行双向通讯,接口定义采用Protocol Buffers(简称PB协议)进行定义。

优点:websocket 具有高性能的特点,Protocol Buffers又非常适合自动化生成代码。

那么通过这样一套机制,就满足了前后端通讯协作的要求。【题外话:除了单纯web开发,很多都是这样进行前后端协作,尤其是客户端开发】

2. 怎么自动化生成代码?

传统做法:

第一步:后端编写并发布PB协议到git仓库。
第二步:前端将这些PB协议复制到自己的前端项目中。
第三步: 根据这些协议,再利用protobufjs工具,生成.js.d.ts文件,供自己使用。

痛点:

痛点1:需要后端主动通知前端有接口协议变动
痛点2:pb文件一下子非常多,可能有几十个文件更改,需要挨个复制到前端项目中
痛点3:protobufjs工具不太会用,有学习成本。

改进思路:

能不能实现一个cli:输入一个git仓库的链接,就能自动创建好.js文件?
答案是肯定的:

    1. 我们可以自动拉取git仓库的pb文件到代码中;
    1. 再根据拉取的文件自动生成目标代码。

二、正文:实现方案

 所谓实现方案,就是如何实现一个这样的cli:  
 1. 编写一份满足要求的模板配置,
 2. 通过cli命令,将这个配置文件复制到目标项目工程,
 3. 通过cli命令,读取配置文件后从git仓库里获取pb文件。
 4. 通过cli命令,根据pb文件创建目标文件(如.js文件)。

第一步:编写模板配置

  • 生成一份配置,需要包含这些内容:
    1. git仓库的信息,如git地址,token,pb文件路径
    1. 需要创建的目标文件信息,如文件路径、文件格式

由于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

第二步: cli编写:将配置复制到项目文件

以下前提知识非常重要,为怎么构建自己的命令行工具,本文省略:

前提知识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

第三步: cli编写:读取配置文件后从git仓库里获取pb文件

从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

第四步:通过cli命令,根据pb文件创建目标文件(如.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的。

WRITTEN BY

avatar