用 template 生成其他语言协议
在前一章节 生成其他语言协议 里,我们展示了如何解析 protocol
协议来生成目标语言的代码。在这一节,我们将演示如何通过 handlebars 语法 来实现生成其他语言代码的方法。
本节的演示代码位于项目 niuhedemo 中
开启 template 功能
在 niuhe/.config.json5
文件里,我们可以通过配置 templates
选项来开启模板渲染功能。下面是 templates
配置项的声明:
interface TplConfig {
/** 模板类型, 可选为 message, enum, route */
type: 'message' | 'enum' | 'route'
modes: string[]
/** template 文件地址 */
template: string
/** 存放的目标地址 */
output: string
}
// 入口文件定义的信息
interface EntryInfo {
...
/** 是否显示烟花效果 */
fire?: boolean
/** template 渲染 */
templates?: TplConfig[]
}
生成代码
我们通过在 niuhe/.config.json5
中添加配置,来演示生成 typescript
脚本所需的类型、常量和 API 请求这三部分代码。其他语言的示例可以根据对应语言的特性,结合 handlebar
语法进行修改。
templates:[{
type: 'message', // 可选值有 message, route, enum 三个, 同 protocol 协议相同
template: './templates/ts_message.tpl', // 模板文件地址
output: './output/message.d.ts', // 输出文件地址
modes: ["api"], // 生成的 mode, url 第一部分
},{
type: 'route',
template: './templates/ts_route.tpl',
output: './output/route.ts',
modes: ["api"],
},{
type: 'enum',
template: './templates/ts_enum.tpl',
output: './output/enum.d.ts',
modes: ["api"],
}]
生成类型定义代码
./templates/ts_message.tpl 代码如下:
// Generated by niuhe.idl
// 此文件由 niuhe.idl 自动生成, 请勿手动修改
interface Rsp<T> {
result: number
data: T
message: string
}
declare namespace {{capitalize mod}} {
{{#each items}}
{{#if desc}}
/** {{desc}} */
{{/if}}
interface {{name}} {
{{#each fields}}
{{#if desc}}
/** {{desc}} */
{{/if}}
{{#if (isArray label)}}
{{ json }}: {{ convertType type }}[], <!-- 处理数组 -->
{{else}}
{{ json }}: {{ convertType type }},
{{/if}}
{{/each}}
}
{{/each}}
}
生成的 typescript
代码如下:
// Generated by niuhe.idl
// 此文件由 niuhe.idl 自动生成, 请勿手动修改
interface Rsp<T> {
result: number
data: T
message: string
}
declare namespace Demo {
/** 分页查询Config信息 */
interface ConfigPageReq {
/** 页码 */
page: number,
/** 每页数量 */
size: number,
/** 配置名称 */
name: string,
/** 配置值 */
value: number,
}
/** 系统配置表 */
interface ConfigItem {
/** id */
id: number,
/** 配置名称 */
name: string,
/** 配置值 */
value: number,
/** 创建时间 */
create_at: string,
/** 更新时间 */
update_at: string,
}
/** 分页查询Config信息 */
interface ConfigPageRsp {
/** 总数 */
total: number,
/** Config信息 */
items: ConfigItem[],
}
/** 请求Config信息 */
interface ConfigFormReq {
id: number,
}
/** 批量删除Config信息 */
interface ConfigDeleteReq {
/** 记录id列表 */
ids: number[],
}
/** Config无数据返回 */
interface ConfigNoneRsp {
}
/** 测试请求 */
interface HelloReq {
/** 用户名 */
name: string,
}
/** 测试响应 */
interface HelloRsp {
/** 问候语 */
greeting: string,
}
/** 空请求 */
interface NoneReq {
}
/** 空响应 */
interface NoneRsp {
}
/** RPC测试用例请求参数 */
interface XxxYyyReqMsg {
/** 用户名 */
name: string,
/** 密码 */
password: string,
}
/** RPC测试用例返回参数 */
interface XxxYyyRspMsg {
/** 用户open_id */
open_id: string,
/** 账户信息 */
account_info: string,
}
}
这里只展示了类型代码的生成,由于篇幅限制,常量和 API 代码请访问 示例项目查看。
添加模板代码助手
在插件里,我们添加了 capitalize
、isArray
和 cameCase
这三个代码助手。在上一小节的演示中,用到了 capitalize
和 isArray
,它们的定义如下:
// 首字母大写 Helper
handlebars.registerHelper('capitalize', (str: string): string => {
if (!str) {
return str;
}
if (str.length === 1) {
return str.toUpperCase();
}
return str.charAt(0).toUpperCase() + str.slice(1);
});
// 数组 Helper
handlebars.registerHelper('isArray', (label: string): boolean => {
return label === 'repeated';
});
// 驼峰命名 Helper
handlebars.registerHelper('cameCase', (text: string): string => {
return camelCase(text);
});
添加自定义代码助手
当通过模板功能成功生成一次代码后, 会创建 niuhe/.template.tpl.ts
文件(这是自定义代码助手文件入口, 也可手动创建)。其默认内容如下:
// templates 注入 例子。 不能使用 js 高级语法。
// 详细例子参考 https://handlebarsjs.com/zh/
handlebars.registerHelper('funName', function(args){
return args;
});
为了在 template
里更简洁、灵活地编写模板代码,我们可以根据需求添加更多代码助手。
在上面的演示代码中,使用到了自定义的 convertType
和 isInteger
两个代码助手,下面是演示代码中完整的 niuhe/.templates.tpl.js 内容:
// templates 注入 例子。 不能使用 js 高级语法。
// 插件官方说明文档: http://niuhe.zuxing.net/chapter4/section6.html
// handlebars详细例子参考 https://handlebarsjs.com/zh/
handlebars.registerHelper('funName', function(args){
return args;
});
handlebars.registerHelper('convertType', function (type) {
const typeMap = {
'string': 'string',
'stringenum': 'string',
'number': 'number',
'float': 'number',
'double': 'number',
'int': 'number',
'long': 'number',
'enum': 'number',
'integer': 'number',
'boolean': 'boolean',
'object': 'any',
'array': 'any[]',
'map': 'any',
};
return typeMap[type] || type;
});
handlebars.registerHelper('isInteger', function (type) {
return type === 'integer';
});
高效调试
在正确配置 niuhe/.config.json5
中的 templates
项后, 插件会在调用 handlebars
从模板文件编译目标文件前, 打印传入的数据内容(console.log({config, data})
)。其中 data
是待编译的具体数据结构和数值。 我们可以通过 帮助
-> 切换开发人员工具
-> 控制台
页面查看打印的数据信息。
完整的数据结构定义
在编写 *.tpl 文件时, 数据项(message
), 常量(enum
)和请求方法(route
) 定义结构如下。参考下面的定义,有助于我们高效、快速地编写模板代码。
declare namespace Tpl {
/**
* 字段定义属性, enum 比 message 多一个 value 字段
*/
interface TplField {
name: string;
/** 字段描述 */
desc?: string;
/** 类型 专属字段 */
type?: string;
/** json 名 */
json: string;
/** 引用的结构 */
ref?: TplMessage;
/** 标签, required, optional, repeated, mapping */
label: string;
/** 字段版本 */
version?: string;
/** 是否废弃 */
deprecated?: boolean
/** enum 专属字段 */
value?: string;
}
interface TplMessage {
mode: string
/* 类名 */
name: string;
/** 类描述 */
desc?: string;
/** 字段列表 */
fields: TplField[];
/** 是否废弃 */
deprecated?: boolean;
/** enum 专属字段, 为整数常量和字符串常量 */
type?: 'integer' | 'string';
}
interface TplRoute {
/** 如: api, url 第一段内容 */
mode: string
/** 路由方法, post, get, put, option... */
method: string;
/** api 路径 */
url: string;
/* 路由名 */
name: string;
/** 描述 */
desc?: string;
/** 返回结构 */
rsp?: TplMessage;
/** 请求结构 */
req?: TplMessage;
/** RPC定义返回的错误代码列表 */
codes: TplField[]
}
interface Template {
/** 数据项定义 */
messages: TplMessage[]
/** 常量定义 */
enums: TplMessage[]
/** 请求方法定义 */
routes: TplRoute[]
}
}