We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pb 协议转换成 ts 的 interface 其实现在有一个很方便的库,https://github.com/protobufjs/protobuf.js/ 可以将 protobuf 转换成 ast,然后遍历树就可以生成自己想要的格式。
但是,虽然文章说的是要对 pb 进行转换,但是并不是每一个团队都是用 pb,使用特定的库有点限制。所以本文是使用编译的方式来处理 protobuf 协议,可以参考里面具体对 pb 的处理方式,对其他协议或者是类协议对项目进行处理。
https://github.com/Means88/tsbuf.git
tsbuf是很早的一个库了,使用 peggy 对 pb 进行编译,生成 ast,然后对 ast 进行处理,生成 interface 的一个早期的库,此文章就是对这个库的魔改。
Peggy是一个简单的JavaScript解析器生成器,可以产生快速的解析器,并有出色的错误报告。你可以用它来处理复杂的数据或计算机语言,并轻松建立转化器、解释器、编译器和其他工具。
一个 pb 协议的格式如下
syntax = "proto3"; package net.test.money.api.family; import "common.proto"; service Family { rpc GetFamilyLvConfs(GetFamilyLvConfsReq) returns (GetFamilyLvConfsRes) {} } enum ECode { CODE_OK = 0; CODE_NOT_ENOUGH_PIECE = 21000; CODE_EXCHANGE_ITEM_NONE = 21001; CODE_EXCHANGE_LIMITED = 21002; } message FamilyLvConf { int32 lv = 1; int64 score = 2; string icon = 3; repeated string big_bg_colors = 5; string upgrade_icon = 6; string minicard_bg = 7; string base_color = 8; string name = 9; string long_icon = 10; } message GetFamilyLvConfsReq { int64 sequence = 1; // 请求序列号,可填纳秒级时间戳 } message GetFamilyLvConfsRes { common.Result result = 1; //结果 repeated FamilyLvConf confs = 2; //配置列表 }
使用 Peggy 对上面的文件进行编译,跟正则不同,Peggy 的编写有一定的格式,这里只列出对 message 的处理
// 声明 MessageName 定义 MessageName = Ident // 声明 Message ,这里有点类似正则,匹配 message GetFamilyLvConfsReq // 内部变量 name 就会被赋值 GetFamilyLvConfsReq // body 会转换成 一个数组的格式 Message = "message" _ name:MessageName __ body:MessageBody { return { type: 'Message', name, body, } } MessageBody = "{" __ "}" { return [] } / "{" body:(__ (Field / Enum / Message / Option / Oneof / MapField / Reversed / EmptyStatement) __)* "}" { return body ? body.map(i => i[1]) : []; }
上面的代码经过编译会生成如下的 ast
{ "type": "Message", "name": { "type": "Identifier", "name": "GetFamilyLvConfsReq" }, "body": [ { "type": "Field", "repeated": false, "typeName": { "type": "KeywordType", "typeName": "int64" }, "name": { "type": "Identifier", "name": "sequence" }, "value": { "type": "IntegerLiteral", "value": 1 }, "options": null } ] },
关于 peggyjs 的语法规则,可以参考 https://peggyjs.org/documentation.html。
每种类型的遍历定义了三个钩子,enter,in,exit 分别对应遍历进入,内部遍历生成,退出。
对于 message ,定义如下的钩子函数
Message: { enter: (path: Path): void => { const message = path.node as Message; const interfaceTree: InterfaceTree = { node: { name: message.name.name, fields: [], }, children: [], }; if (this.interfaceScopeStack.length === 0) { this.rootInterfaces.push(interfaceTree); } else { const iscope = this.interfaceScopeStack.slice(-1)[0] || null; iscope.children.push(interfaceTree); } this.interfaceScopeStack.push(interfaceTree); }, exit: (path: Path): void => { this.interfaceScopeStack.pop(); }, },
深度遍历函数 walk ,对每个节点进行处理,然后执行对应的钩子函数
private readonly walk = (node: BaseNode): void => { const lastNode = this.currentNode; this.currentNode = node; const currentPath = new Path(node, lastNode, this.context); console.log('currentPath', currentPath) // getActions 函数会通过 visitor[node.type] 来匹配对应子visitor for (const visitor of this.visitors) { const actions = Generator.getActions(node, visitor); actions.enter(currentPath, this.walk); } for (const visitor of this.visitors) { const actions = Generator.getActions(node, visitor); actions.in(currentPath, this.walk); } for (const visitor of this.visitors) { const actions = Generator.getActions(node, visitor); actions.exit(currentPath, this.walk); } this.currentNode = lastNode; };
执行完成遍历函数,整个 pb 文件会被转换成
{ services: [ { name: 'Family', methods: [Array] } ], imports: [ { type: 'ImportStatement', path: [Object], public: false }, ], package: { type: 'Package', identifier: { type: 'FullIdentifier', name: 'net.test.money.api.family' } }, scopeStack: ScopeStack { stack: [] }, enums: [ { type: 'Enum', name: [Object], body: [Array] }, ], interfaces: [ { node: [Object], children: [] } ] }
然后我们就用这个 result 来生成我们自己要的格式了
还是以 interfaces 为例子,可以来看看怎么处理
export const generateInterface = (mode: GenerateMode) => (i: InterfaceTree): string => ` ${mode === GenerateMode.Global ? '' : 'export '}interface ${i.node.name} { ${i.node.fields .map((f: InterfaceTreeField) => { if (f.type === 'normal') { return `${f.name}${f.optional ? '?' : ''}: ${getType(f, i)};`; } if (f.type === 'map') { return `${f.name}${f.optional ? '?' : ''}: { [key: string]: ${getType(f, i)}, };`; } return ''; }) .join('')} } ${ i.children.length <= 0 ? '' : `${mode === GenerateMode.Global ? 'declare' : 'export'} namespace ${i.node.name} { ${i.children.map(j => generateInterface(GenerateMode.Module)(j)).join('\n')} }` }`;
对 interface 进行一次遍历,生成 ts 字符串,然后对字符串进行导出就可以了,最后导出的形式
export interface FamilyLvConf { lv: number; score: number; icon: string; big_bg_colors: string[]; upgrade_icon: string; minicard_bg: string; base_color: string; name: string; long_icon: string; } export interface GetFamilyLvConfsReq { sequence: number; } export interface GetFamilyLcConfsRes { result: common.Result; confs: FamilyLvConf[]; } export interface GetFamilyBaseInfoReq { sequence: number; uid: number; }
对于 service 的导出,我们要对 methods 进行挨个导出,如上面的 pb 文件,我们要导出的格式为 export const GetFamilyLvConfs = (req: moneyapifamily.GetFamilyLvConfsReq) => requestFamily<moneyapifamily.GetFamilyLcConfsRes>("GetFamilyLvConfs", req);
export const GetFamilyLvConfs = (req: moneyapifamily.GetFamilyLvConfsReq) => requestFamily<moneyapifamily.GetFamilyLcConfsRes>("GetFamilyLvConfs", req);
核心处理函数就是遍历整个 service 对象,转换为 ts 字符串
const serviceFile = ` import * as ${basename} from '../interface/${basename}' import axios from 'axios'; function baseRequest(packageName: string, serviceName: string) { return function methodReq<T>(method: string, req: any): Promise<T> { return axios.post('/api', { packageName: packageName, serviceName: serviceName, method: method, data: req, }); }; } ${result.services.map((item: any) => { const serviceName = item.name; const reqString = `const request${serviceName} = baseRequest('${packageName}', '${serviceName}')`; return ( reqString + '\n\n' + item.methods .filter((child: any) => child.type === 'rpc' || child.type === 'comment') .map((child: RpcField) => { const name = child.name; const req = child.argTypeName.identifier.name; // 获取 req interface 名称 const res = child.returnTypeName.identifier.name; // 获取 res interface 名称 return `export const ${name} = (req: ${basename}.${req}) => request${serviceName}<${basename}.${res}>('${name}', req);`; }) .join('\n\n') ); })} `;
最后生成的 service api 如下
import * as moneyapifamily from "../interface/moneyapifamily"; import axios from "axios"; function baseRequest(packageName: string, serviceName: string) { return function methodReq<T>(method: string, req: any): Promise<T> { return axios.post("/user", { packageName: packageName, serviceName: serviceName, method: method, data: req }); }; } const requestFamily = baseRequest("net.test.money.api.family", "Family"); export const GetFamilyLvConfs = (req: moneyapifamily.GetFamilyLvConfsReq) => requestFamily<moneyapifamily.GetFamilyLcConfsRes>("GetFamilyLvConfs", req);
The text was updated successfully, but these errors were encountered:
No branches or pull requests
pb 协议转换成 ts 的 interface 其实现在有一个很方便的库,https://github.com/protobufjs/protobuf.js/
可以将 protobuf 转换成 ast,然后遍历树就可以生成自己想要的格式。
但是,虽然文章说的是要对 pb 进行转换,但是并不是每一个团队都是用 pb,使用特定的库有点限制。所以本文是使用编译的方式来处理 protobuf 协议,可以参考里面具体对 pb 的处理方式,对其他协议或者是类协议对项目进行处理。
https://github.com/Means88/tsbuf.git
tsbuf是很早的一个库了,使用 peggy 对 pb 进行编译,生成 ast,然后对 ast 进行处理,生成 interface 的一个早期的库,此文章就是对这个库的魔改。
peggy 编译
Peggy是一个简单的JavaScript解析器生成器,可以产生快速的解析器,并有出色的错误报告。你可以用它来处理复杂的数据或计算机语言,并轻松建立转化器、解释器、编译器和其他工具。
一个 pb 协议的格式如下
使用 Peggy 对上面的文件进行编译,跟正则不同,Peggy 的编写有一定的格式,这里只列出对 message 的处理
上面的代码经过编译会生成如下的 ast
关于 peggyjs 的语法规则,可以参考 https://peggyjs.org/documentation.html。
ast 深度遍历
每种类型的遍历定义了三个钩子,enter,in,exit 分别对应遍历进入,内部遍历生成,退出。
对于 message ,定义如下的钩子函数
深度遍历函数 walk ,对每个节点进行处理,然后执行对应的钩子函数
interfaces output
执行完成遍历函数,整个 pb 文件会被转换成
然后我们就用这个 result 来生成我们自己要的格式了
还是以 interfaces 为例子,可以来看看怎么处理
对 interface 进行一次遍历,生成 ts 字符串,然后对字符串进行导出就可以了,最后导出的形式
service api output
对于 service 的导出,我们要对 methods 进行挨个导出,如上面的 pb 文件,我们要导出的格式为
export const GetFamilyLvConfs = (req: moneyapifamily.GetFamilyLvConfsReq) => requestFamily<moneyapifamily.GetFamilyLcConfsRes>("GetFamilyLvConfs", req);
核心处理函数就是遍历整个 service 对象,转换为 ts 字符串
最后生成的 service api 如下
The text was updated successfully, but these errors were encountered: