自定义模板
使用 Handlebars 创建自定义代码生成模板
当预定义模板不满足需求时,创建完全自定义的 Handlebars 模板。
基本用法
自定义模板通过插件的 getTemplate hook 返回模板路径。也可以在 plugins 中使用直接返回模板路径的插件函数:
import { defineConfig } from "wormajs";
import { functional } from "wormajs/plugin";
export default defineConfig({
generator: [
{
plugins: [
functional({
path: "./my-template",
config: { customOption: "value" },
}),
],
},
],
});目录结构与文件规则
基本结构
模板目录中的 .handlebars 文件(除 partials/ 外)都作为目标模板渲染,生成代码的文件结构与模板保持一致。
{tag} 动态文件名
在文件名中使用 {tag} 占位符,生成时按 OpenAPI 的 tag 遍历生成多个文件:
my-template/
├── index.ts.handlebars # → index.ts
└── {tag}.ts.handlebars # → user.ts, article.ts, order.ts ...在 {tag} 模板中可访问 currentTag 对象(类型为 ApiDoc),通过 currentTag.tagName 访问当前 tag 名称,通过 currentTag.apis 访问当前 tag 下的接口列表。
{api} 动态文件名
文件名可指定为 {api}(例如 {api}.ts),生成时遍历 allApis 数组生成多个文件,将 {api} 替换为对应接口名称。在 {api} 模板中可访问 currentApi 对象(类型为 Api)。
my-template/
├── index.ts.handlebars # → index.ts
└── {api}.ts.handlebars # → getUser.ts, createUser.ts, deleteUser.ts ...模板数据中 currentTag 和 currentApi 仅在对应动态文件名的上下文中有效。
# 不覆盖前缀
文件名前加 #,表示首次生成后不再覆盖:
my-template/
├── #index.ts.handlebars # 首次创建,后续不覆盖(生成后命名为 index.ts)
└── index.ts.handlebars # 每次都覆盖模块类型区分
按模块类型分子目录,适配不同项目配置:
my-template/
├── typescript/ # 生成 .ts 文件
│ ├── index.ts.handlebars
│ └── types.ts.handlebars
├── module/ # 生成 .mjs 文件
│ ├── index.mjs.handlebars
│ └── types.d.ts.handlebars
└── common/ # 生成 .cjs 文件
├── index.cjs.handlebars
└── types.d.cts.handlebarsTemplateData.type 取值:
typescript— 使用typescript/目录module— 使用module/目录commonjs— 使用common/目录auto— 自动检测
模板可以只提供部分类型目录。缺少的类型会抛出明确错误:
Template "ky" does not support module type "commonjs". Supported types: typescript, module不区分模块类型时,模板路径下的所有 handlebars 文件都作为目标模板渲染。
Handlebars 语法
{{! 注释 }}
{{! 变量输出 }}
{{keyName}}
{{{unescapedHtml}}}
{{! 条件 }}
{{#if pathParameters}} ... {{/if}}
{{#or pathParameters queryParameters}} ... {{/or}}
{{! 循环 }}
{{#each tagedApis}}
{{#apis}}
export const
{{{name}}}
= () => {};
{{/apis}}
{{/each}}
{{! 比较 }}
{{#if (eq type "typescript")}} ... {{/if}}预定义 Helper
worma 在模板渲染前自动注册了一批常用 Handlebars helper,可在模板中直接使用:
| Helper | 签名 | 说明 |
|---|---|---|
isType | (value, type, options) | 判断 value 是否为指定类型(如 'array'、'object'),配合 {{#isType}} 块级使用 |
and | (...args) | 所有参数均为 truthy 时返回块内容,配合 {{#and}} 使用 |
or | (...args) | 任一参数为 truthy 时返回块内容,配合 {{#or}} 使用 |
eq | (a, b) | 判断 a === b,配合 {{#if (eq a b)}} 使用 |
not | (a, b) | 判断 a !== b,配合 {{#if (not a b)}} 使用 |
join | (...args) | 拼接所有参数为字符串 |
raw | (text) | 输出原始 HTML,不转义特殊字符 |
stripStarPrefix | (text) | 去除每行开头的 * 前缀,常用于清理 JSDoc 注释 |
示例:
{{! 类型判断 }}
{{#isType pathParameters "object"}}
// 路径参数是对象类型
{{else}}
// 路径参数是其他类型
{{/isType}}
{{! 多条件组合 }}
{{#and hasQueryParams (eq method "GET")}}
// 有查询参数且为 GET 请求
{{/and}}
{{! 去除 * 前缀 }}
{{{stripStarPrefix queryParametersComment}}}也可以通过插件的 onHandlebarsCreated hook 注册额外的自定义 helper:
{
name: 'my-helpers',
onHandlebarsCreated({ hbs }) {
hbs.registerHelper('uppercase', (str) => str.toUpperCase());
},
}模板数据结构
模板渲染时可以访问以下数据。TemplateData 继承自 OpenAPI 文档标准结构,并附加了 worma 自动生成的辅助字段。
顶层字段
| 字段 | 类型 | 说明 |
|---|---|---|
openapi | string | OpenAPI 规范版本号 |
baseUrl | string | API 基础 URL,从 OpenAPI 的 servers[0].url 提取 |
type | string | 模块类型:'typescript' | 'module' | 'commonjs' |
title | string | OpenAPI 文档的 info.title |
version | string | OpenAPI 文档的 info.version |
description | string | OpenAPI 文档的 info.description |
contact | object | OpenAPI 文档的 info.contact |
framework | string | 框架标记:vue | react | svelte | solid-js | nuxt |
defaultKey | boolean | 是否为默认配置 |
components | string[] | Schema/Component 定义 |
componentNames | string[] | 所有生成的 component schema 名称 |
allApis | Api[] | 所有 API 的扁平数组,按 tag 分组前的原始顺序 |
tagedApis | ApiDoc[] | 按 OpenAPI tag 分组的 API 列表 |
config | Record<string, any> | 由模板配置传入的自定义参数 |
继承自 OpenAPI 的字段
TemplateData 展开了整个 OpenAPI 文档对象,因此模板中也可直接访问:
| 字段 | 说明 |
|---|---|
openapi | OpenAPI 规范版本号 |
info.title | 文档标题 |
info.version | 文档版本 |
info.description | 文档描述 |
info.contact | 联系人信息 |
servers | 服务器列表 |
paths | API 路径定义 |
components | 组件定义(schemas、responses 等) |
tags | 标签列表 |
生成的 API 分组数据
tagedApis: ApiDoc[]
按 OpenAPI tag 分组的 API 列表。每个 ApiDoc 结构如下:
interface ApiDoc {
/** 该分组的 tag 名称(原 tag 字段更名为 tagName) */
tagName: string;
/** 该 tag 下的所有 API */
apis: Api[];
}
interface Api {
/** 所属 tag */
tag: string;
/** 函数名(operationId) */
name: string;
/** HTTP 方法(大写),如 GET、POST */
method: string;
/** API 摘要 / 描述 */
summary: string;
/** URL 路径 */
path: string;
/** 路径参数类型字符串 */
pathParameters: string;
/** 查询参数类型字符串 */
queryParameters: string;
/** 路径参数 JSDoc 注释(可选) */
pathParametersComment?: string;
/** 查询参数 JSDoc 注释(可选) */
queryParametersComment?: string;
/** 响应体 JSDoc 注释(可选) */
responseComment?: string;
/** 请求体 JSDoc 注释(可选) */
requestBodyComment?: string;
/** 响应体 TypeScript 类型字符串 */
response: string;
/** 请求体 TypeScript 类型字符串(可选) */
requestBody?: string;
/** API 调用代码示例 */
callingCode?: string;
}模板中遍历写法示例:
{{#each tagedApis}}
// tag:
{{tagName}}
{{#each apis}}
export function
{{{name}}}(params:
{{{pathParameters}}}) { return request('{{method}}', '{{path}}'); }
{{/each}}
{{/each}}allApis: Api[]
所有 API 的扁平数组,按 tag 分组前的原始顺序排列。
Partials
模板根目录下 partials/ 文件夹自动注册为 partial,通过文件名引入:
my-template/
├── partials/
│ └── comment.handlebars # {{> comment}}
├── index.ts.handlebars
└── {tag}.ts.handlebars{{> comment}} <!-- 引入 partials/comment.handlebars -->完整示例
my-template/
├── partials/
│ └── license.handlebars
├── typescript/
│ ├── index.ts.handlebars
│ ├── {tag}.ts.handlebars
│ └── #config.ts.handlebars
└── module/
├── index.mjs.handlebars
└── {tag}.mjs.handlebars