资讯动态

t3code 代码生成器实战:用模板统一团队前端工程规范

发布时间:2026/10/27 16:02:17 来源:尧图企业网站定制
最近一个月我主要在带团队做前端工程基建最头疼的问题是每个新模块的初始化流程都不一样。有人习惯复制上一个模块再改字段有人喜欢现场手写还有人从网上找一段示例代码塞进来结果一个项目下来光是文件头部的 import 顺序就能看出四种风格。后来朋友推荐了一个叫 t3code 的命令行工具试用了一周后我把之前靠 Plop 撑着的模板全部迁了过去。t3code 不是一个框架也不是某个 UI 库而是一个偏底层的代码生成与项目模板工具核心思路是用配置文件描述“我要生成什么”用模板文件描述“生成出来的代码长什么样”再通过命令行完成交互、输出和检查。如果你也在做团队基建或者受够了复制粘贴式开发这篇文章值得看完。1. t3code 到底解决什么问题1.1 名字里的信息量我第一眼看到 t3code 的时候以为它是某个和 Next.js 强绑定的框架。T3 在 TypeScript 社区里常被用来指代 TypeScript、Tailwind CSS、tRPC 这三件套create-t3-app 就是定位在这套技术栈上的脚手架。但实际用下来t3code 的定位更通用它没有绑定 React 或 Next.js你可以用它生成纯函数库、Node 服务、前端组件甚至配置文件。名字里的 code 才是重点它更像一个“以代码为产物的模板引擎”。这里有一个容易误解的点t3code 不是 create-t3-app 的替代品。create-t3-app 负责在项目初始化时一次性生成整套工程目录t3code 则更偏向“在已有项目里持续生成模块”。换句话说前者解决“从 0 到 1”后者解决“从 1 到 100”。两者的分工其实很清晰如果你已经有了一个跑起来的项目真正缺的不是初始化脚手架而是后续新增页面、接口、组件时能有一个稳定且不跑偏的生成通道。1.2 它解决的其实是一类流程问题大部分团队的代码混乱不是某一个文件写得太烂而是模块诞生的过程不可控。两个人同时新增 API 接口一个人建了 services/user.ts另一个人建了 utils/getUser.ts路由注册方式也不一样后续维护成本立刻上升。t3code 的处理方式很简单把“新模块应该长什么样”固化成模板每次新增都走同一条生成流水线。工具本身不评判你模板写得好不好它只负责让每个人生成的入口和流程保持一致。我更看重的是它把“生成”和“检查”分开。生成是一回事生成完之后的 diff 审查是另一回事。它内置的命令支持先让用户看清楚即将创建哪些文件、每个文件会有什么内容确认无误后再落盘。这个设计让代码生成工具不再像黑盒操作也更容易在团队里推广——leader 不用因为担心生成一堆不可控代码而拒绝引入。另外这种“先预览后写入”的方式对新人非常友好他们能在正式生成前看到模板和实际代码之间的映射关系理解成本低很多。1.3 谁最适合用适用场景大概有三类。第一类是前端/全栈团队做工程规范用模板统一模块结构第二类是独立开发者维护多个项目希望用同一套模板快速起项目第三类是偏 Node.js 的工具链开发者想基于 t3code 的插件机制做定制化生成能力。这三类人有一个共同特点他们手里有大量重复性、但结构相对稳定的代码需要反复产出。不太适合的场景也很明确纯后端 Java/C# 团队、纯运维脚本场景以及项目已经基本稳定、新增模块极少的情况。工具引入本身也有成本配置模板、培训成员、维护生成器都需要投入。如果团队一个月都加不了几个新模块那用复制粘贴可能还更快一些。所以我的建议是先盘点一下最近三个月重复创建的代码种类和次数再决定要不要引入这类工具。2. 核心原理配置、模板与渲染流水线2.1 三层抽象所有代码生成工具本质上都是三层输入配置、模板渲染、输出文件。t3code 把这三层拆得很清楚。配置层是一个 TypeScript 文件负责声明生成器名称、交互问题、目标路径模板层是一组带标识的模板文件里面嵌入变量占位和条件逻辑输出层则是生成的真实文件由 CLI 在用户确认后写入。这个分层让每一部分都能独立修改互不牵连。这样做的好处是你可以把某一种生成逻辑单独拿出来测试。比如团队想调整 API 模块的目录结构只需要改动配置里的 target 路径或者改模板文件不需要动其他代码。这比在项目里写一个很大的 shell 脚本或者复制一个项目目录再全局替换要安全得多。全局替换最怕的就是替换到不该替换的地方比如注释、字符串、CSS 类名等而模板引擎只处理你明确圈出来的变量位置天然避免了误伤。2.2 变量注入与类型提示模板里最常用的操作是变量注入。t3code 支持在配置中定义 prompts每个 prompt 对应一个输入问题用户回答后得到一个变量对象模板里通过% moduleName %这样的占位符引用。看得出来它借鉴了 EJS 的语法但加入了 TypeScript 类型支持配置文件是.ts模板里面的变量也会根据 prompts 自动推导类型。例如我定义了一个moduleName类型是 string模板里可以直接使用moduleName.trim()、moduleName.toLowerCase()这类方法写错类型会在生成阶段报错。对于团队协作来说这比普通字符串模板要友好得多至少别人改模板时不会因为拼错变量名而生成一堆空值。我在实际使用中还发现类型提示能倒逼配置规范如果某个变量在模板中用到了但配置里没有定义生成器会直接报错而不是静默输出空串省去了很多排查时间。2.3 为什么选择这种设计市面上有很多更“简单”的实现方式比如直接用 shell 脚本做文本替换或者用 JavaScript 的字符串拼接。但那种方式最大的问题是不具备“形态约束”。模板引擎的价值在于它允许你把代码当作普通文本去写同时又能在特定位置插入动态内容还能通过if/for控制代码块是否输出。你可以在模板里保留业务代码的骨架也可以把公共逻辑提取成函数片段灵活性比单纯替换要高一个量级。t3code 默认采用类似 EJS 的渲染语法但允许你配置其他渲染器。我觉得它不是想重新发明一套模板语言而是想把“代码结构的复用力”和“动态参数的灵活性”结合起来。第一次上手的时候你只需要记一个规则需要生成什么就写什么需要变化的地方用尖括号标签包起来。如果你之前用过 EJS 或者 Underscore 的模板语法几乎可以零成本上手。3. 从零配置一个可用的生成器3.1 安装与初始化环境上要求 Node.js 18 以上推荐 20 的 LTS 版本。安装方式很简单全局安装一个命令行包即可。这里基于 t3code 0.6.x 的体验来描述新版字段可能有变化但整体概念是通用的。npm i -g t3code t3code init执行 init 之后它会在当前目录生成最小配置和 templates 目录。我的习惯是先在项目的根目录创建一个单独的 templates 文件夹不让它影响 src。init 会问一下模块结构选择“单一仓库”还是“多包仓库”这一步会影响后续 target 路径默认值。如果是新项目我推荐直接选择“单一仓库”等真正遇到多包场景再调整不迟。3.2 配置文件怎么写一个最基本的配置长这样// t3code.config.ts import { defineConfig } from t3code; export default defineConfig({ templatesDir: ./templates, generators: [ { name: simple-module, prompts: [ { name: moduleName, message: 请输入模块名小驼峰如 userService, validate: (v) /^[a-z][a-zA-Z0-9]*$/.test(v), }, ], templates: [ { src: simple-module/index.ts.ejs, target: src/modules/{{moduleName}}/index.ts, }, ], }, ], });这里的templatesDir表示模板根目录。generators是一个数组每个元素代表一个可被调用的生成器。prompts决定了用户在运行时会回答哪些问题validate用来做输入校验避免生成一堆含空格或大写字母的文件名。target路径里的双花括号是路径级变量模板文件里的尖括号是内容级变量这套区分值得注意很多人一开始都会把两种变量语法搞混。3.3 创建模板文件在templates/simple-module/index.ts.ejs中写模板// % moduleName %.generated.ts // 注意本文件可能由 t3code 自动生成手动修改前请确认。 export function % camelCase(moduleName) %() { return { name: % moduleName %, version: 1.0.0, }; }虽然没有引入 lodash但 t3code 内置了一些常见的字符串工具比如camelCase、pascalCase、kebabCase。在模板里调用这些 helper 时它会自动处理大小写和连字符比用户在 prompts 里手动输入多个命名要好。我一般只让用户输入一个业务标识其他所有命名都由 helper 派生既减少交互负担也避免因为个人命名习惯不同导致目录结构混乱。3.4 运行生成命令t3code generate simple-module它会弹出交互式提问要求输入 moduleName。填入 orderController 后t3code 会先打印即将生成的文件路径再显示文件内容预览确认无误后写入。默认情况下它会跳过已存在的文件避免误覆盖。这个保护机制很重要尤其是当你批量生成多个模块时不至于把之前的手工调整直接冲掉。如果想跳过交互直接生成可以把参数带在命令里t3code generate simple-module --moduleName orderController --yes这个对脚本化调用特别有用尤其在集成 CI 或 pre-commit hook 时可以做到无人值守生成。还可以先执行t3code list查看当前配置里有哪些生成器确认名字没写错再跑命令。我在团队里经常看到有人记不住生成器名全凭菜单提示有了 list 之后基本不会跑错。4. 实战案例生成一个带校验的 API 模块4.1 场景需求假设团队在做一个 Next.js tRPC 的项目每个 API 模块都需要包含路由定义、zod 输入校验、测试文件。以前新增一个 userProfile 模块需要人工创建 3 个文件再手动改成对应的业务名。文件名、目录位置、import 路径稍有疏漏测试就跑不起来。现在用 t3code 生成只要回答一个问题文件结构自动对齐。模板目录结构templates/ api-module/ router.ts.ejs router.test.ts.ejs4.2 配置与模板配置中添加生成器 api-module目标路径放在src/server/api/routers下并自动创建__tests__目录。模板内容大致如下省略了和业务无关的细节但核心结构是完整的// router.ts.ejs import { z } from zod; import { createTRPCRouter, publicProcedure } from ../trpc; const % camelCase(moduleName) %Schema z.object({ id: z.string().uuid(), // 这里可以继续加字段t3code 不会修改模板手写部分 }); export const % camelCase(moduleName) %Router createTRPCRouter({ byId: publicProcedure .input(% camelCase(moduleName) %Schema) .query(async ({ input }) { return { ok: true, id: input.id }; }), });注意生成器的配置里target 可以用{{moduleName}}组织目录也可以使用 kebabCase helper{ src: api-module/router.ts.ejs, target: src/server/api/routers/{{kebabCase moduleName}}.ts }测试文件的模板类似我习惯把被测试的模块路径也用变量拼出来这样就算生成器目录调整了测试文件的 import 也不会失效。测试模板里用 Vitest 举例实际上你用 Jest 或 Node 内置测试框架都行模板引擎不关心这些它只管把占位符替换成真实值。4.3 执行生成与代码审查运行t3code generate api-module --moduleName userProfile它会展示将要生成的文件src/server/api/routers/userProfile.ts和对应的测试文件。在写入前可以用预览模式把生成结果和已有规范做对比我这里说的 diff 是“生成内容预览/对比”的意思具体命令名以你当前版本为准。它在团队评审时特别有用大家不用先落盘直接看预览就能判断模板质量。实际生成后你会发现 t3code 不会帮你格式化代码也不负责 lint。我的做法是在生成完立即跑一次 eslint --fix 和 prettier --write让代码进入团队统一的格式轨道。不要把格式工作交给模板因为模板要保持可读性塞一堆格式控制符只会让模板变成天书。4.4 接入 npm scripts为了让生成器成为日常流程的一部分我会在 package.json 里加脚本{ scripts: { gen:api: t3code generate api-module, gen:list: t3code list } }这样新同事入职后不用记 CLI 语法直接npm run gen:api即可。对于“全项目代码规范统一”这件事来说工具只是载体真正的关键是让生成入口足够简单。我见过很多团队买账了生成工具却因为命令太长、参数太多而没人用最后模板仓库长满灰尘。把常用命令收敛进 scripts是提高使用率最快的方法。5. 常踩的坑与排查清单5.1 变量不渲染最容易遇到的是模板里变量名写错或者使用了配置里没有定义的变量。t3code 的模板引擎默认开启严格模式遇到未定义变量会直接报错而不是静默输出空串。这时候先去配置里检查 prompts 的 name 和模板里的占位符是否完全一致注意拼写和大写。我自己的经验是如果报错信息指向了某个 helper别急着怀疑变量名先看模板调用 helper 时传的参数对不对。5.2 路径级变量与内容级变量混淆target 字段里我用的是双花括号{{moduleName}}模板文件里用的是 EJS 的尖括号语法。一开始我把两者搞混在 target 里写了% moduleName %导致生成的时候路径解析失败。解决办法是记住target 是文件路径字符串t3code 用双花括号做轻量替换模板文件内容走渲染引擎用尖括号标签做表达式输出。这个规则只要记一次后面基本不会再犯。5.3 生成出来的文件命名不符合团队规范这是最常被吐槽的一点。有人在 prompts 里要求输入“模块名”结果填了 UserProfile目录却要求小驼峰。我的建议是不要在 prompts 里收集太多信息只问一个最核心的标识其他命名都通过内置 helper 转换。比如只需要问“业务标识”然后分别用 kebabCase、pascalCase 生成文件名、类名。这样无论用户输入 userProfile、user-profile 还是 UserProfile最终产出的目录和类名都能保持统一。5.4 模板里的空行和注释被保留了吗生成的代码里有没有多余空行直接决定了工具有没有“呼吸感”。t3code 的做法是按模板字面量输出不会帮你压缩格式。如果你想控制空行就在模板里写清楚如果不想保留某段注释就不要写在模板里。千万别指望生成器自动清理多余空行那是格式化工具的工作。我在团队里约定模板里允许保留少量解释性注释作为“生成器使用说明”生成后如果想删除团队成员可以自行处理。5.5 可能导致模板无法运行的其他问题我把常见问题整理成了一个速查表方便对着排查现象可能原因排查建议变量输出为空变量名拼写错误或未定义对比 prompts name 与模板占位符目录没有生成target 路径变量写错使用双花括号而非尖括号文件被覆盖目标文件已存在且未开启保护检查是否跳过旧文件类名首字母小写没有使用 pascalCase helper在模板里显式调用 helper生成的 import 路径错误target 层级和源文件位置不匹配根据相对目录重新计算 import另外还有一个很容易被忽视的问题如果模板文件里写了中文注释一定要保证文件编码是 UTF-8。某些编辑器在不小心保存为 GBK 后生成出来的代码注释会乱码虽然不影响运行但代码审查时会非常难看。这个问题排查起来很费时间最好一开始就在编辑器里设置好默认编码。6. t3code 与常见代码生成工具的选型对比6.1 和 Plop、Hygen 的区别如果你用过 Plop会发现 t3code 的配置思路和它有点接近都是“配置 prompts 模板文件”。区别在于 t3code 默认就支持 TypeScript 配置、内置命名 helper并且把 diff/check 这类审查命令做成了第一等公民。Hygen 则更强调文件夹即生成器模板组织简单直接但类型提示和变量推导要弱一些。我给出的选型建议如果你只需要在项目里快速生成一两个固定文件Plop 够用学习成本也低。如果模板很多且希望团队成员拿到模板后能直接用t3code 更合适。如果已经在用 create-t3-app 搭过项目也别急着替换先看现有没有让人难受的重复工作。下面是更直观的对比工具定位配置语言类型提示代码审查适用场景Plop轻量生成器JS 配置文件弱无内置快速生成单文件Hygen模板即生成器文件结构约定弱无内置小团队、少数模板t3code工程化生成器TypeScript 配置强预览与检查中大规模 TS 项目create-t3-app项目脚手架交互式 CLI中等初始化一次从零搭全栈项目6.2 不建议用 t3code 的场景没有任何工具是银弹。如果项目里组件结构极不稳定今天用 class 明天改成 function今天目录叫 services 明天改成 modules那就不适合把模板固化下来。因为模板的维护成本会非常高最后你会发现所有精力都花在改模板而不是写业务代码。这种情况下先统一设计规范再引入生成工具。还有一种情况也要小心如果团队里没有人愿意当生成器的维护者那这个东西很容易变成“一次性项目”。配置和模板不是写完就能永久运行的业务结构变了、依赖升级了、命名规范调整了都需要有人跟进。所以引入 t3code 之前最好先指定一个基建负责人哪怕只是兼任也要明确职责边界。6.3 我对后续扩展的想法代码生成工具下一步的大方向大概率是跟 AI 提示词结合。你可以把 t3code 的模板当作“可控骨架”把 AI 当作“内容填充器”比如先用 t3code 生成路由结构和校验规则再用 AI 补业务逻辑。这比让 AI 直接生成整个文件要稳得多至少目录和类型是可控的。另外我在多包 Monorepo 项目里也试过让生成器输出到不同包目录只要 target 路径写成相对路径配置里简单判断一下包名即可。不过这类需求比较高级建议等基本功能跑顺之后再尝试。先把一个模块跑通再往组件、页面、配置文件维度扩展远比一开始就想着做一个“万能生成器”要靠谱。维护了这套生成器大约一个月之后我最大的感受是t3code 真正解决的其实不是“写代码”的问题而是“让新代码如何出生”的问题。团队规范写得再漂亮如果每一次新增模块都靠人工对齐早晚会走样。但也要提醒一句生成器不是拿来一次性固化所有模式的它更适合抓住那些“高频、稳定、有固定形态”的重复工作。我现在的习惯是每个季度做一次模板评审把过时的模板删掉把新出现的重复模式补进去。如果你也想试建议先找一个最小的 API 模块跑通比如用户信息的增删改查跑通之后再往组件、页面、配置文件的维度扩展。下一次再有人说“我自己手写更快”的时候你可以让他先看看 t3code 生成完的代码再下结论。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑