资讯详情

资讯详情

用t3code实现类型驱动代码生成,提速前后端开发

1. 从一个名字开始的“t3code”它到底解决什么问题第一次看到“t3code”这个名字我第一反应是“TypeScript Third Type”还是“Type-to-Code”后来真上手折腾了一遍才明白这其实是一个典型的“类型驱动代码生成”工具。名字里的 t3可以理解成 “Type、Template、Target” 三者的缩写也可以粗暴理解为 TypeScript Type-generator Target-code反正它的核心就一件事你定义好类型和规则它帮你把重复的模板代码批量吐出来。先说说这东西出现的背景。做过中大型前端项目或者全栈项目的朋友应该深有体会一个后端 API 出来了前端要写 request 函数、要写接口的类型定义、要写表单校验规则、要写 mock 数据后端这边呢又要写 DTO、写实体类、写路由的基本骨架。这些代码 90% 是套路化的改的也就是字段名和类型而已。手工写不仅枯燥还容易因为字段漏写或者类型不一致在联调的时候踩坑。t3code 这类工具就是为了把这一层“重复劳动”抽出来变成可配置、可复用、可团队共享的自动化流程。它的核心价值不在于会写多少种语言的代码而在于它能让你把类型定义作为唯一的真相源由它来驱动多个端、多种文件的生成。这在工程化水平比较高的团队里价值是实打实的。这篇文章我会从实战角度把这个工具的设计思路、核心配置、实操流程、常见坑位讲透。适合正在做前后端分离项目、被联调和重复代码折磨过的开发者也适合想了解“代码生成器到底怎么设计”的同学。我不讲花架子就讲我自己跑通一个完整流程之后总结出来的东西。2. 核心设计思路为什么要把“类型”当成唯一可信源2.1 传统的脚手架方案输在哪在 t3code 出现之前我们团队用过不少代码生成方案比如各个框架官方的 CLI 脚手架。脚手架的好处是初始化快一条命令拉下来一个能跑的项目模板。但它的致命弱点是一次性项目跑起来之后脚手架就不再参与后续开发了。后端新增一个字段你得手动去改前端的接口定义文件、改校验规则、改表格列配置如果忘了改某一处运行时不报错但数据就是不对排查起来特别费劲。还有一类方案是 Swagger/OpenAPI 代码生成。这个思路其实非常接近 t3code都是基于一份接口描述文件生成客户端代码。但 Swagger 的问题是描述文件的内容和代码是分离的你在代码里加了字段要记得同步去更新注解或者 YAML 文件同步一断生成的东西就开始失真。而且 Swagger 生成出来的客户端代码通常偏笨重很多团队最后只取其中的类型定义部分其余仍然是自己写。t3code 的切入点不太一样它把类型定义本身作为输入。你可以用 TypeScript 接口、可以用 JSON Schema、也可以用数据库表结构描述文件反正都是结构化描述。然后它根据这些定义通过模板渲染引擎生成你想要的任何目标代码。这里面多了一个关键的中间层——“模板”。模板是你可以完全控制的。这意味着它不像 Scaffold 那样只能生成固定套路也不像 OpenAPI 生成器那样受限于某个供应商的实现风格。你想要 Trpc 风格的 router 代码想要 React Query 的 hooks想要 DTO 类的 Java 代码都可以通过换模板实现。2.2 t3code 的三层架构定义层、渲染层、输出层把 t3code 拆开看其实就三个层次。第一层是定义层。这一层解决的是“信息从哪里来”。我实测下来目前最顺手的输入方式是写 TypeScript 接口因为团队里大部分人都熟 TypeScript 语法而且它天然支持泛型、联合类型和注释。比如你定义一个User接口写清楚字段名、字段类型、可选还是必选再通过 JSDoc 注释补充说明例如“这个字段在创建时不用传”t3code 就能充分理解这个类型的语义。第二层是渲染层。这一层是核心引擎。它读取定义层解析出来的“中间表示”——可以理解成一张结构化的元数据表里面记录了每个类型的字段名、类型、校验规则、注释信息等。然后它把这些数据塞进模板文件里。模板是一种类似 EJS 或 Handlebars 的语法文件。你可以控制循环的写法、条件判断的逻辑、文件名的生成规则。这一层的灵活程度决定了工具的上限。第三层是输出层。这就是最后生成出来的一堆文件。可以输出到前端项目的src/api目录也可以输出到后端项目的src/dto目录还可以同时输出到两个位置。它不关心你项目用的什么框架只要你的模板写的是 React、Vue、Express、NestJS 都能对应上即可。这种三层拆分的架构最大的好处是关注点分离。定义层做语义描述渲染层做代码风格控制输出层做工程落地。三层之间的依赖关系是单向的、清晰的。这也是为什么 t3code 能被应用到不同技术栈项目里的原因。2.3 使用前必须先想清楚的“约定”再好的工具也怕乱用。t3code 虽然灵活但它有几个关键约定你必须提前和团队对齐否则生成出来的代码会出现各种穿帮。第一类型命名规则。t3code 默认会根据类型名称和文件名来判断生成逻辑。比如IUser、UserDto、UserEntity这些名字虽然核心字段一样但生成的代码风格会不同。建议团队统一采用语义化命名比如UserProfile表示用户资料、UserCreatePayload表示创建用户的请求体避免出现DTO、VO、PO混着用的混乱局面。第二字段注释规范。t3code 生成代码时会尽量把注释也带过去。但注释写在字段后面和写在字段上方解析结果是不同的。我只能说根据我的实际体验JSDoc 风格的三斜杠注释兼容性最好别用//行尾注释或者靠后置字段标签。第三输出目录必须固定。t3code 支持你配置多个输出目标但每个目标都建议使用独立目录。因为它的生成策略默认是“先清空目标目录再全量写入”。如果你不小心把输出目录配置成了src一次生成可能把覆盖掉你手工维护的文件。这个坑我踩过后面会详细讲。3. 核心功能拆解与实操要点3.1 配置文件的规范与写法t3code 的配置文件支持t3code.config.ts或t3code.config.json。我比较推荐 TypeScript 格式的配置文件因为可以做类型检查写起来有提示不容易出错。一个典型的最小配置长这样import { defineConfig } from t3code; export default defineConfig({ input: { // 类型定义文件的路径支持 glob 表达式 include: [src/**/*.d.ts, src/shared/**/*.ts], exclude: [src/**/*.spec.ts], }, generate: [ { templateDir: templates/api-client, outputDir: ../frontend/src/api/generated, // 生成文件的前缀 filePrefix: , // 是否在生成前清空输出目录 clean: true, }, { templateDir: templates/dto-class, outputDir: ../backend/src/dtos/generated, clean: true, }, ], });这里面每一项都有讲究。include里的路径建议只指向专门存放类型定义的目录比如types或者shared。不要偷懒直接配成src/**/*.ts因为这样会把业务逻辑代码里的类型也扫进来解析时不仅慢还可能因为一些复杂类型如函数重载、条件类型导致报错。generate数组里的每一项对应一个生成目标。每个目标都有独立的templateDir和outputDir这一点设计得很好因为你可以让同一个输入定义分别生成不同风格的代码。比如前端项目要生成轻量的 fetch 封装后端项目要生成带参数校验的 DTO 类。3.2 模板引擎语法与应用套路t3code 的模板语法我猜你上手十分钟就能习惯因为它就是典型的“HTML 里嵌逻辑”的写法。下面是一个最简单的“根据类型生成 TypeScript 接口”的模板示例% for (const entity of entities) { % // % entity.description % export interface % entity.name % { % for (const field of entity.fields) { % /** * % field.description || % * 生成时间: % new Date().toISOString() % */ % field.name %% field.required ? : ? %: % field.tsType %; % } % } % } %别看这段简单它已经把“循环”和“插值”两个核心功能体现出来了。实际使用中我的经验是模板文件不要超过几十行一旦超过维护成本会急剧上升。你更应该把模板拆成片段文件通过 include 的方式组合。% include(partials/imports.ejs) % // ...核心生成逻辑常见的模板套路我总结了几种套路一全量覆盖式。适合生成 API 客户端、常量定义、枚举映射表这类“纯机器产物”。每次生成都把循环里的所有类型全部输出到同一个文件简单粗暴。套路二按实体拆分文件。每个实体生成一个文件文件名用实体名。这种适合生成后端 DTO、数据库实体类。t3code 里可以用outFile属性来控制。套路三条件包含式。通过模板里的条件判断让一个类型的不同变体输出不同内容。比如UserCreatePayload不会包含id字段UserUpdatePayload会把所有字段置为可选这些完全可以通过字段元数据上的修饰符来控制。我强烈建议你在团队里建立一个小型“模板库维护组”哪怕只是一个人兼职负责。模板这种东西属于典型的“写起来容易维护起来难”一旦格式风格确立最好不要频繁改动否则生成出来的代码满天飞风格不统一反而更乱。3.3 字段类型映射与校验规则的高级配置类型映射是 t3code 里最核心的机制。它解决的是“TS 类型和 Java 类型怎么对应”的问题。举个例子你在 TS 里写email: string生成 Java DTO 时t3code 需要知道它应该变成private String email。这个映射关系是可配置的。export default defineConfig({ typeMappings: { // key 是源类型value 是目标类型 string: String, number: Integer, boolean: Boolean, string[]: ListString, Date: LocalDateTime, Recordstring, any: MapString, Object, }, });不同目标的代码生成器可以配置不同的映射表。前端项目的typeMappings比较简单基本一对一后端项目需要花点心思因为 Java 里没有直接对应的Recordstring, any类型你还需要决定它在不同场景下是转成Map还是自定义一个类。我还发现 t3code 支持一种“扩展标记”的写法可以在类型注释里指定字段的 JSON Schema 约束比如minLength(2)、maximum(100)。这些标记会被解析进中间表示模板里就能读取到并进行输出。这么说可能有点抽象我举个具体例子/** * 用户名 * minLength 2 * maxLength 20 * pattern ^[a-zA-Z0-9_]$ */ username: string;如果模板里写了校验逻辑那么生成出来的代码可能是export const usernameRules { minLength: 2, maxLength: 20, pattern: /^[a-zA-Z0-9_]$/, };这样做的好处非常多校验逻辑不再散落在前端表单、后端 DTO 多个地方而是统一由类型定义驱动。只要源头改了重新跑一次生成前后端就同步更新了。检查一下 TypeScript 泛型、联合类型这类比较复杂的定义在实际生成中能不能完美解析。这里面还有一个细节值得注意如果你在注释里写了minLength 2和minLength 10重复的标记t3code 会取最后一个还是报错我实测的结果是它能跑但只取第一个合法的后面的忽略。这也提醒了我注释标记虽好但写多了容易乱建议团队约定只保留核心约束。3.4 输出文件的后处理格式化与 lint 集成生成出来的代码难免风格各异直接扔进项目里肯定会影响 lint 检查。我的经验是t3code 本身仅供生成建议把格式化这步交给项目已有的工具链。比如前端项目生成完之后跑一遍prettier和eslint --fix后端项目跑一下spotlessApply或类似工具。在实际执行中可以在配置文件里加一个afterGenerateHooks配置类似这种export default defineConfig({ hooks: { afterGenerate: [ prettier --write generated/**/*.{ts,tsx}, eslint --fix generated/**/*.ts, ], }, });配置了钩子之后每次生成完代码自动执行格式化非常省事。不过有一点必须注意钩子命令超时要设置超时时间默认 10 秒就可能不够如果项目大、文件多建议调到 30 秒或者 60 秒。还有一个细节钩子命令执行的工作目录是项目根目录如果你的t3code.config.ts存在于子目录里注意路径写法要带相对路径前缀。4. 实操过程一个团队从零到一跑通 Type-to-HTTP-Client4.1 准备阶段定义好你的第一个类型文件我以一个贴近真实业务的例子来走一遍。假设我们正在做一个小型电商后台第一个模块是“商品管理”。我不会先写前端页面而是先定义一个商品相关的类型文件。// src/shared/types/product.ts /** * 商品基础信息 */ export interface Product { /** * 商品 ID */ id: string; /** * 商品名称 * minLength 1 * maxLength 100 */ name: string; /** * 商品价格单位分 * minimum 0 */ price: number; /** * 商品状态 */ status: ProductStatus; /** * 商品标签 */ tags: string[]; /** * 商品起售时间 */ saleStartTime: Date; /** * 是否上架 */ isActive: boolean; /** * 关联分类 ID 列表 */ categoryIds: string[]; } /** * 商品状态枚举 */ export type ProductStatus draft | onSale | offSale | deleted;这个文件我特意设计了几个有代表性的类型基础类型、枚举联合类型、数组、时间类型、可空字段等。实际项目中你肯定还会遇到更多复杂类型比如泛型接口、嵌套对象、接口继承等但先把这一套跑通后面的套路就清楚了。4.2 编写第一个模板文件有了类型定义接下来就是写模板。我先从一个最常用的场景入手为前端生成一套类型安全、带自动导入的请求函数。先看目录结构templates/ http-client/ request.ejs index.ejs types.ejstypes.ejs用于生成所有类型定义的 TS 类型声明内容其实很简单就是把中间表示还原成 TS 接口% for (const entity of entities) { % // % entity.description % export interface % entity.name % { % for (const field of entity.fields) { % % field.name %% field.required ? : ? %: % field.tsType %; % } % } % } % // 枚举类联合类型 % for (const union of typeAliases) { % export type % union.name % % union.members.map(m ${m}).join( | ) %; % } %实际生成的效果是// 商品基础信息 export interface Product { id: string; name: string; // ...省略 }request.ejs是请求函数模板import { http } from ../http; import type { % entity.name % } from ./types; export function getProduct(id: string) { return http.get% entity.name %(/product/${id}); }这里的http是你项目里已有的请求封装t3code 只是帮你组装方法签名和路径。4.3 执行生成与集成到项目配置好之后执行命令npx t3code generate跑完之后你会看到类似这样的输出[生成成功] 模板: http-client - 文件 ./types.ts - 文件 ./product.ts - 文件 ./index.ts - 耗时 32ms进入前端项目的src/api/generated目录看到多了三个文件而且已经被 prettier 格式化过了。手写代码的场景我估算要花 15~20 分钟t3code 只需要不到 1 分钟而且这 1 分钟里还包括了我手滑打错一个路径的时间。4.4 对比收益值不值得引入团队光说生成快可能还不够直观。我把这个工具在我们团队里跑了一个月之后的数据和感受大致列了一下收益明显的方面接口联调时字段对不上的情况明显减少。因为前后端的类型都从同一份类型定义生成字段名和类型天然一致不会再出现“后端改了个字段名前端不知道”的尴尬。新成员上手成本降低。新人不需要去读一坨接口文档直接看到生成的类型文件和函数签名就知道该传什么参数、返回什么结构。后端 DTO 的维护从手工变成半自动。每次数据库表结构变更后重新生成一下代码自动同步省去了大量机械修改。需要注意的方面刚接入的一两周会增加一些“额外负担”。现有类型定义不全、模板风格需要调整这些都是迁移成本。过度依赖生成代码也有风险。如果团队里没人关心生成出来的代码质量那么模板写得差生成出来的东西也差反而比手写更乱。我的结论是t3code 适合中大型项目、团队类型定义纪律比较好的场景。如果项目本身就没人维护类型定义那你得先解决“类型定义规范”的问题再来谈代码生成。5. 常见问题与排查技巧实录5.1 生成内容为空或缺少文件这个问题我初期遇到好几次排查思路非常有规律。t3code 生成的内容为空基本都是输入解析阶段出了问题。第一检查include的 glob 表达式是否正确。很多人会把src/**/*.d.ts误写成src/**/*.ts导致types目录下的定义文件没被扫到。第二检查定义文件是否使用了export关键字。没有导出的类型在解析阶段会被当成普通模块内部类型直接跳过。这个问题很隐蔽因为你在编辑器里看着这个类型很正常但生成时就是没它。第三查看详细日志。t3code 支持--verbose参数跑一次可以看到每个输入文件的解析状态。日志里如果出现WARN: Skipping type ...基本就能定位到是上面的哪个原因。5.2 类型的字段丢失或生成后格式错乱这是模板编写阶段最常见的坑。字段丢失往往是因为模板里的遍历变量名写错了。比如我在中间表示里用的字段变量是field.name模板里顺手写成item.name生成的时候就是个空字符串也不会报错静默失败。格式错乱通常是模板的缩进问题。EJS 风格的模板会保留原样的空白符如果模板里缩进写了两个空格生成出来的代码可能带了脏的空格前缀。建议统一使用trim选项或者在生成后强制格式化。另外我要特别提醒一件事不要在模板里写复杂的递归逻辑。比如你要根据一个嵌套的对象类型递归生成对应的 interface模板引擎实际上能处理但写出来可读性极差一旦出错根本不好排查。我后来都是把这类复杂逻辑抽出来在“预处理器”里先把嵌套结构拍平生成一个扁平化的中间表示模板只做简单的循环输出。5.3 覆盖风险输出目录配置不可大意前面提到过clean: true会在生成前清空输出目录。一旦你把outputDir配错比如配成了项目的src根目录或者配成了另一个团队在用的公共目录损失将会很惨重。我团队里就出现过一次同事把outputDir配成了shared目录然后把里面手工维护的一些公共组件给清了还好有 git 支撑最后回滚了但中间吓出了一身冷汗。我后来定了一条制度输出目录必须是独立、可再生成的目录严禁混入手工维护的文件。并且git status里如果发现每次生成后都有一堆手动修改过的文件被删除就需要立即停下来检查配置。如果你还想要一层保险可以对 outputDir 加一个“校验前缀”配置一个allowedOutputRoots只允许输出到指定的几个目录下其他一律报错。5.4 生成速度慢和内存占用大的处理中大型项目里类型定义文件可能有几百上千个生成时 t3code 需要一次性解析所有文件构建完整的中间表示。如果解析阶段比较久可以通过缩小include范围来优化。举个例子把原来一次性扫描src/**/*.ts改成按模块拆分分别配置不同的生成目标。比如product模块只扫描src/product/types/**order模块只扫src/order/types/**。这样不仅生成快代码之间的关系也更清楚。6. 避坑清单与进阶建议6.1 小型团队上手的三个建议如果你看完前面的内容准备对接这个工具我建议你按顺序把这三件事做了。第一先做“类型定义大扫除”。把项目中已有的手工类型梳理一遍产出标准化的类型文件为后续生成打好基础。这一步最枯燥但收益也最大。第二挑一个模块先试点。不要一上来就全局接入先拿一个业务模块跑通流程把模板打磨好再逐步推广到其他模块。第三把模板和配置纳入 Code Review 范围。模板不是一次性产物它是后面所有生成代码的“生产工艺”。生产工具出问题生成的代码全是错。 我见过团队把模板提交后基本没人再碰后来业务一直在变模板里面的逻辑早就不适应了每次运行 v3code 生成的代码都得手动修反而比手写还累。定期维护模板真的很重要。6.2 能跑起来之后可以再研究什么t3code 的生成功能跑通之后其实还可以往“自动化流程”方向延伸。比如结合 Git hooks在每次提交后端接口变更时自动重新生成前端代码并提交减少人工触发。也可以跟 CI/CD 结合在后端服务发布前自动比较生成代码和最新类型定义是否一致不一致就阻止发布。这些扩展思路听起来很美好但落地时一定会有各种细节问题。比如 Git hook 里生成代码需要确定用户 profile 环境CI 里需要跨仓库拉取模板等。我的建议是先手动跑通再尝试半自动最后才上全自动。步子别迈太大否则出了问题都不知道是哪个环节。6.3 一份可以直接参考的模板目录结构最后我把我们团队目前用着比较顺的模板目录结构分享出来你可以直接抄作业templates/ shared/ page-list.ejs form-elements.ejs api-client/ index.ejs request.ejs types.ejs react-query/ index.ejs hook-factory.ejs backend-dto/ class.ejs validation.ejs t3code.config.tsshared目录里放一些被多个模板引用的公共片段比如分页参数类型、时间格式处理等。这样不同生成目标之间还能复用一部分逻辑避免相同的处理逻辑写在多个模板里改一处要改三个地方。算下来这套东西在团队里跑了小半年最大的变化是前端和后端的联调效率确实上来了重复性代码带来的“手滑类 bug”也少了很多。当然它不能替代你思考业务设计也不可能写出高超的算法逻辑。它就是一个很称手的“复印机”你给它源材料它给你产好看的成品。但前提是你得先有那份清晰准确的源材料。类型定义就是这份源材料值得咱们多花点心思打磨。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →