ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

t3code:模型驱动的T3 Stack代码生成器,解决CRUD重复劳动

t3code:模型驱动的T3 Stack代码生成器,解决CRUD重复劳动 一直用 T3 Stack 做全栈项目的人应该都有这种体验Next.js 加 TypeScript 加 Tailwind CSS 加 tRPC 这套组合的上手体验确实顺滑但一旦业务开始变多路由、输入校验、RPC handler、前端调用 hook每一层都是大量重复劳动。t3code 就是为解决这个痛点出现的——它不是传统意义上跑一次就扔的脚手架而是一个能跟着项目长期迭代的代码生成器。你只要维护一份描述业务模型的 schema它就能把 tRPC 路由、Zod 校验、Prisma 查询、前端类型的整套链路补齐。这篇文章我打算把 t3code 的核心机制、完整使用流程、以及我在这几个项目中踩过的坑一起聊透给正在用 T3 Stack、或者打算尝试这类模型驱动开发思路的读者一个真实的参考。1. 从手搓CRUD到一键生成t3code想解决的真实痛点1.1 T3 Stack看着很美写起来却全是重复劳动T3 Stack 最吸引人的地方在于端到端类型安全。tRPC 把服务端 router 的类型直接暴露给前端调用一个 API 就像调用本地函数一样参数和返回值都有完整类型提示。但这里有个微妙的问题类型安全解决的是前后端类型不一致的隐患它并没有解决每次新增一个业务模块你要在不同文件里写四遍相似代码的体力活。拿一个最普通的后台菜单模块举例。我得先打开schema.prisma定义 MenuModel加上关联关系然后去api/routers/menu.ts写 CRUD 的 tRPC router再在api/schemas/menu.ts里用 Zod 定义输入校验接着回组件里写 useQuery、useMutation 的调用最后还要手动维护 router 的根注册表。这还只是单个模块如果涉及分页、筛选、排序、软删除代码量直接翻倍。最难受的不是写这些代码本身而是它们之间的隐式关联。Prisma 字段改名了Zod schema 层可能漏改tRPC procedure 增加了一个 query 参数前端调用地方的类型也会跟着报错——虽然报错也算提醒但一个几百个文件的项目里这种联动错误往往是分散的、延迟出现的修起来特别考验耐心。1.2 t3code的定位不是脚手架是维护型代码生成器我第一次用 t3code 时本来以为它和市面上那些create-t3-app或代码片段插件差不多生成完 CRUD 模板就算完事。实际跑完一个模块后我才发现它真正下功夫的地方在增量维护。普通的脚手架工具是一次性生成生成完之后你修改文件、维护代码它就不管你了。t3code 的做法不太一样它在生成的文件里保留特定标记区域marker当你修改了 schema 描述再次执行生成命令时它只更新标记区域内的代码页面里手写的业务逻辑会原样保留。这就意味着你可以把 t3code 当作一个常态化的开发辅助工具而不是只在项目初始化时用一次。所以它适合的人群也比较明确一是业务有大量标准化 CRUD 场景的团队二是想把类型安全做到极致、希望所有前后端契约都从一个 schema 自动推导出来的项目三是团队里有规范强迫症、希望路由命名和参数结构保持一致的同学。反过来如果你的项目以复杂定制查询为主、表单结构千奇百怪那 t3code 能帮你省的时间会打折扣但仍可以作为基础链路的加速器。2. 核心机制拆解模板引擎、类型推演与RPC代码生成2.1 模板引擎选型与渲染流程t3code 的底层结构并不神秘核心就三件事读入 schema、渲染模板、写出文件。但真正决定它好用与否的是每一步的细节设计。模板引擎方面t3code 没有自研语法而是直接复用 Nunjucks 作为渲染引擎。这个选型我觉得很务实Nunjucks 的模板语法对后端转前端的开发者很友好支持宏定义、import 复用、过滤器和异步环境而且容错率不错——某个变量没传时默认输出空字符串不至于直接把整个生成流程打崩。渲染流程上t3code 分成了两个阶段。第一阶段是模型编译把用户写的 schema 文件解析成统一的内部模型ModelDefinition里面包含字段列表、类型映射、校验规则、关联关系、是否软删除、是否包含分页等元信息。第二阶段是模板渲染把内部模型塞进 Nunjucks 模板按需输出不同的文件类型。有一点值得注意t3code 里的模板不只是把 schema 翻译成代码它还会根据模型属性做条件渲染。比如某个模型标记了softDelete生成的 Prisma 查询和 tRPC router 就会自动带上 deletedAt 过滤逻辑某个字段标记了unique生成的输入校验里会多一个查重函数。这种描述式生成比传统脚手架里死板的模板要灵活得多。2.2 类型安全的双向推演从Zod Schema到前端调用tRPC 的类型安全链路是 t3code 的核心卖点这里我得展开多说几句。用 t3code 生成 RPC 模块时你在 schema 描述里定义的字段类型会同时影响三条代码线Prisma model 的数据库类型、Zod 输入校验的类型、前端引用的返回类型。t3code 内部维护了一张类型映射表比如 schema 里的string字段如果再声明了email规则就会映射为 Zod 的z.string().email()如果是int加min:0就映射成z.number().int().min(0)。关键在输入反向推演这一步。很多代码生成器只做正向推导从 schema 生成 Prisma model然后生成 tRPC router。但 t3code 会反过来从上一步生成的 Prisma model 再推导一份对应的 Zod input schema并且把 create、update、list 三种场景的输入类分开。比如update场景的输入会允许字段可选list场景会单独生成 page、pageSize、sortBy、sortOrder 这些查询参数。这样前端引用的类型和后端校验的类型严格来自同一份元数据不会出现Postgres 里能存、Zod 校验却拦住了的错位。2.3 增量生成的算法逻辑如何不覆盖手写代码增量生成是 t3code 和其他脚手架拉开差距的地方。简单说每个生成目标文件里都有类似这样的标记结构// t3code:component:begin export const menuRouter createRouter() .query(list, { ... }) .mutation(create, { ... }) // t3code:component:end当你再次执行t3code generate时工具会扫描文件、定位标记区域只重写标记区域内的内容标记区域之外的代码一律不动。实现上它其实就是先用正则把每个标记区域的位置记下来再用模板结果替换对应区间。但这个做法有个前提你要在第一次生成后保持标记不被删除。我在实际项目中见过有同学觉得标记注释碍眼手动清掉的之后每次跑 t3code 都直接在文件末尾追加重复的 router 定义导致类型冲突。后来我们团队在 Code Review 规范里加了一条t3code生成的注释区域等同锁定区域不允许手改需要自定义逻辑就在标记外扩展。这算是用工具的人必须接受的纪律。3. 从零跑通一个完整菜单模块的生成过程实录3.1 初始化项目与t3code配置说再多机制不如跑一遍实例。假设我们现在要在已有的 T3 Stack 项目里加一个菜单管理模块项目结构是标准的 Next.js App Router tRPC Prisma。先安装并初始化 t3codepnpm add -D t3code npx t3code init执行 init 之后t3code 会在项目根目录生成t3code.config.ts和schema目录。配置文件负责告诉工具Prisma schema 往哪里输出、tRPC router 目录在哪、前端 hooks 的输出路径是什么、类型文件名怎么命名。我的建议是配置里路径相关选项不要写绝对路径全部用相对项目根目录的路径。原因后面会讲t3code 在解析路径别名时对绝对路径的处理容易遇到坑相对路径要稳得多。初始化完成后在schema/menu.ts里描述菜单模型export default defineModel({ name: Menu, tableName: menu, fields: { name: { type: string, required: true, max: 50 }, code: { type: string, required: true, unique: true }, path: { type: string, required: true }, order: { type: int, default: 0 }, parentId: { type: string, optional: true }, status: { type: enum, values: [enabled, disabled], default: enabled } }, softDelete: true, pagination: true, includes: [children] });这份描述的语义很直白一个菜单有名称、唯一编码、路由路径、排序、父级 ID、状态支持软删除、分页和子节点关联查询。3.2 编写Schema描述并生成API链路schema 写好后执行npx t3code generatet3code 会做几件事。首先把模型同步到prisma/schema.prisma生成对应的 Menu model随后在src/server/api/routers/下生成menu.ts与根注册表更新接着在src/server/api/schemas/下生成 Zod 输入最后在前端src/hooks/下生成useMenu.ts和类型声明文件。生成完的 tRPC router 大致长这样// t3code:component:begin export const menuRouter createRouter() .query(list, { input: menuListInput, resolve: async ({ ctx, input }) { const where buildWhereFromInput(input); const [list, total] await ctx.prisma.$transaction([ ctx.prisma.menu.findMany({ ... }), ctx.prisma.menu.count({ where }) ]); return { list, total }; } }) .mutation(create, { input: menuCreateInput, resolve: async ({ ctx, input }) { return ctx.prisma.menu.create({ data: input }); } }) // ... update / remove / softDelete // t3code:component:end这里能清楚看到查询的排序、分页、软删除过滤条件都是按 schema 描述自动展开的手写的话至少得十几行样板。3.3 前端调用与类型提示验证生成的前端 hook 是针对 React 封装的。以useMenu.ts为例它会导出useMenuList、useMenuCreate、useMenuUpdate等自定义 hook内部直接封装 tRPC 的 query/mutationexport function useMenuList(params: MenuListParams) { return trpc.menu.list.useQuery(params, { keepPreviousData: true }); } export function useMenuCreate() { const utils trpc.useUtils(); return trpc.menu.create.useMutation({ onSuccess: () utils.menu.list.invalidate(), }); }最直观的验证方式是打开页面组件输入useMenuList如果代码提示里能自动带出{ page, pageSize, sortBy, sortOrder, keyword }这些参数类型并且返回值能感知到list和total说明这条类型链路是通的。我一般还会顺手在prisma studio里确认表结构字段名是否与预期一致避免默认走 Prisma 属性名和数据库列名的驼峰映射问题。到这里一个完整的 CRUD 后端链路和基础前端调用就结束了。从写 schema 到跑通接口走的都是声明式描述 自动生成的路子不用手动在四五个文件之间来回跳。4. 使用中踩过的坑三类最容易翻车的场景4.1 覆盖策略导致的开发分支丢失t3code 最需要小心的就是旧项目接入时的手工改造。前面提到增量生成依赖标记区域但如果你的项目不是从零开始用 t3code而是中途接入第一次 generate 时会发现 t3code 默认不覆盖已有文件而是生成.t3gen后缀的新文件。这样虽然安全但也带来麻烦你需要手动把生成内容合并进旧文件还要补上标记注释否则下次生成又会变成重复的新文件。我的处理方式是接入老项目时先跑一次生成然后逐个文件手工合并合并完成后确认标记注释已经写入再跑第二次 generate 验证没有重复代码。这个流程比较繁琐但对已有线上代码库来说安全第一值得花半小时换后面几个月的稳定。另外要留意.prisma 模型的同步冲突。如果团队里有人同时手工改了schema.prisma里的 Menu 模型又有人跑了 t3code generate工具默认会用生成的描述覆盖同名模型导致手工修改丢失。后来我给团队定了个规矩一切与模型结构相关的改动都要回到schema/menu.tsPrisma schema 只当产物看不直接改。刚开始大家觉得多一层间接操作很反直觉但运行半年之后几乎所有成员都认为这个约束反而让结构变更变得可审计。4.2 路径别名映射的差异与路由失效第二个坑和 Next.js 的路径别名有关。T3 Stack 项目里tsconfig.json一般定义了/*指向src/*t3code 生成的代码默认也会用/server/api/...这类别名导入。听起来没问题但 t3code 解析别名时用的是自己的配置项pathAlias它和tsconfig.json的 paths 不是一回事。我遇到过这么个情况配置文件里写了别名但忘了在t3code.config.ts里声明 tRPC router 根路径的别名前缀。结果生成的路由文件内部 import router 时用了/server/api/trpc而实际生成的文件里对外导出的路径变成相对路径导致编译期不报错、运行时 404。折腾了两个小时最后发现是别名设置不一致。建议把 t3code 的路径相关配置和tsconfig.json做成一份共享源或者至少在 README 里写清楚项目里改了路径别名必须同步改 t3code 的 pathAlias。用工具的人最忌讳的就是配置默认值存活在潜意识里。4.3 生成代码与Lint规则的冲突第三类坑是生成代码过不了 ESLint。t3code 生成的代码特点是模板感强变量命名规整但偶尔会出现空行过多、单引号双引号混用、缩进不一致的情况。这不是 bug而是模板文件没有跟着项目的 Lint 配置走。我在新项目里接 t3code 后第一件事就是把生成流程后面挂了一步自动格式化npx t3code generate eslint --fix src/server/api/routers/menu.ts src/hooks/useMenu.ts更省事的做法是在t3code.config.ts里开启formatOnGenerate让工具在写出文件后自动调用 prettier。有几个版本这个开关还不太稳定所以我仍然习惯在 package.json 里写一条组合命令。如果生成完的代码被 ESLint 标红不要急着关规则先看看是不是模板变量里带了不该有的空白字符。还有一个容易被忽略的是 unused import 问题。当某个模型没有配置关联查询时生成的 router 里不会用include但模板默认引用了 Prisma 的Prisma类型结果一编译就报 unused。t3code 后来加了 lint 清理步骤但升级版本后仍建议跑一次全量tsc --noEmit验证。5. 二次定制与团队协作把t3code变成团队的基础设施5.1 自定义模板与变量注入内置模板能满足 80% 的 CRUD 场景但每个团队多少有点自己的范式。比如我们团队约定所有 list 接口都必须返回summary汇总字段t3code 默认模板里没有这个逻辑就得自己改模板。t3code 的自定义入口是把模板目录指向项目内某个目录比如templates/然后复制一份内置的router.njk和hook.njk进去改。模板变量名可以通过在 schema 描述里补充自定义字段实现比如export default defineModel({ name: Menu, // ... t3: { customSummary: true, }, });只要内部模型里有t3对象Nunjucks 模板里就能通过model.t3.customSummary做条件判断。这种自定义方式比修改生成后的文件要干净得多因为生成后的文件每次都会被覆盖。5.2 与CI/CD、ESLint、prettier的工作流整合团队协作时t3code 最好加入 CI 检查防止有人手工改了生成产物却没有同步 schema。我们做法是加一个 CI 任务npx t3code generate --check--check模式不会真正写文件只比对如果执行生成是否有文件发生变化有变化就返回非零退出码。这样哪个 MR 里出现了schema 改了但生成产物没提交的情况流水线会直接拦截审查者就不用肉眼 diff 那么痛苦了。prettier 和 ESLint 的整合也建议做成一条命令链{ scripts: { gen: t3code generate, gen:fmt: npm run gen prettier --write . eslint --fix src/ } }不要只写在 README 里直接把命令放进package.json更实际新成员跑npm run gen:fmt就守住了规范。5.3 团队规范化的实际收益与注意事项我们团队用了大半年之后最大的变化不是代码写得少了而是代码审查的时间明显下降。因为所有 CRUD 模块的路由命名、校验规则、错误处理模式都高度一致审查者不需要每次重新理解一套新的写法。新人上手项目时也不用从零学业务模块的代码结构直接看一份 schema 描述就大概明白模块有哪些字段、哪些规则。但注意t3code 解决的是重复不是复杂。如果一个模块的逻辑集中在自定义查询和业务规则上那生成产物的占比会下降最终还是要靠人写。所以团队约定俗成的用法是先用 t3code 生成骨架再在标记区域外补充自定义逻辑尽量不动生成区。这样既保住了规范又留了扩展空间。6. 几个使用心得性能、版本升级与长期维护最后分享几个我在长期使用中的观察这些属于文档里不会细写的部分。第一性能方面。t3code 在大型项目里生成速度主要受模板渲染和 Prisma schema 同步影响。模型数量几十个时基本是毫秒级但一旦 Prisma schema 文件很大每次读写整个文件再做字段级 diff 会明显变慢。如果项目模型超过一百个可以考虑拆成多个 schema 描述文件t3code 支持多文件输入内部会做合并执行体验会好不少。第二版本升级不要冲动。我经历过一次 t3code 大版本升级它调整了内部模型的字段命名规范导致旧模板里的变量名全部失效生成的代码结构变了。升级前务必看 release notes并且先在 feature 分支跑一次--check确认影响面再合并。第三如果要跨项目复用一套模板可以把模板和 schema 描述单独放到一个私有 npm 包这样每个项目只需要依赖这个包加 t3code就能保持全团队的生成产物风格统一。根据我个人的使用体会t3code 这类模型驱动的代码生成工具最大的价值不是帮你省那几分钟的敲代码时间而是让团队在类型安全这件事上建立了一个统一的事实来源。你只要维护好 schema 描述前后端的契约就不会悄悄漂移。日常开发里我也习惯在每次生成后顺手打开一个生成文件快速扫一眼模板输出的实际逻辑确认它和我预期的行为一致——相信我工具再自动化也要保持对生成物的一双人眼。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表