1. 从“t3code”这个标题说起:它到底是什么
第一次看到“t3code”这个词,我脑子里蹦出来的第一反应是——这大概率是个技术圈里的缩写或者代号,而不是一个面向大众的产品名。事实也确实如此。在开发者社区里,类似“t3code”这种命名方式非常常见,通常由“项目代号 + code”组成,比如“t3”可能是某个团队、某个框架、某个工具链的第三代版本,而“code”则直接点明了它的核心属性:跟代码打交道。
那它到底能做什么?简单来说,t3code 是一套围绕代码生成、代码组织或者代码模板化管理的实践方案集合。它不是一个单一的库,也不是一个开箱即用的软件,更像是一种“约定 + 工具 + 流程”的组合拳。你把它理解成一套“让重复代码自己写自己”的方法论就行。它解决的问题很具体:在一个中大型项目里,总有大量结构相似、逻辑雷同的代码需要反复手写,比如 CRUD 接口、数据模型定义、API 请求封装、配置文件模板等等。这些东西写一遍两遍还行,写十遍二十遍就是纯粹的体力活,而且极易出错。t3code 的思路就是把这些“有规律可循的代码”抽象成模板,通过一套轻量的生成机制,让开发者只需要描述“我要什么”,而不是“怎么写”。
适合谁来参考?我认为有三类人最应该花时间研究一下:第一类是前端或全栈开发者,尤其是那些在业务快速迭代团队里工作的人,每天被重复代码折磨得够呛;第二类是小团队的技术负责人,需要在不引入重型框架的前提下提升团队整体产出效率;第三类是对代码工程化感兴趣的中高级开发者,想理解“模板驱动开发”到底怎么落地。不管你是刚入行一两年,还是已经写了七八年代码,只要你的项目里存在“反复写相似代码”的场景,t3code 这套东西就值得你花半个小时看进去。
我接下来会从设计思路、核心细节、实操流程、常见坑四个维度,把 t3code 这套东西掰开揉碎讲清楚。所有内容都基于我在实际项目中反复试错后总结出来的经验,不是纸上谈兵。
2. t3code 的整体设计思路与方案选型
2.1 为什么是“模板驱动”而不是“全自动生成”
很多人一听到“代码生成”,第一反应是上大而全的代码生成器,比如根据数据库表结构直接生成整个后端服务。这种方案听起来很爽,但实际用起来问题很多。最大的问题是:生成出来的代码往往“太完整”了,完整到你不敢改。一旦你手动修改了生成出来的文件,下次再生成就会覆盖你的改动,或者生成器和你的修改之间产生冲突。这种“全有或全无”的生成方式,在真实项目里非常容易失控。
t3code 的设计哲学恰恰相反:它走的是“模板驱动 + 局部生成”的路线。什么意思?它不试图替你生成整个模块,而是让你把那些“结构固定、内容可变”的代码片段抽成模板,然后在需要的时候,通过一个简单的命令或者脚本,把模板渲染成实际代码,插入到指定位置。你可以把它想象成一个“代码印章”——你刻好一个章,哪里需要盖哪里,而不是让机器帮你把整封信都写了。
这种选择背后的逻辑很实在:真实项目里的代码,80% 是重复的结构,20% 是业务逻辑。t3code 只帮你搞定那 80%,剩下的 20% 留给你自己写。这样既保证了效率,又不会让你失去对代码的控制权。我试过那种“一键生成整个项目”的方案,刚开始很爽,但到了第二次需求变更的时候,你会发现生成器和你的业务代码已经纠缠不清了,改起来比手写还累。
2.2 核心组成:模板、上下文、渲染器
t3code 这套东西拆开来看,其实就三个核心概念:模板、上下文、渲染器。理解这三个词,你就理解了它的全部。
模板就是你预先写好的代码骨架,里面用占位符标记出可变的部分。比如一个 API 请求函数的模板可能是这样的:
export async function fetch{{ResourceName}}(params) { const response = await request.get('/api/{{resourcePath}}', { params }); return response.data; }这里的{{ResourceName}}和{{resourcePath}}就是占位符,实际生成的时候会被替换成具体的值。
上下文就是你传给模板的那组数据。比如你要生成一个“用户列表”的请求函数,上下文就是{ ResourceName: 'UserList', resourcePath: 'users' }。上下文可以来自命令行参数、配置文件、甚至数据库表结构。t3code 的灵活之处在于,上下文的来源完全由你决定,它不强制你用什么。
渲染器就是负责把模板和上下文合在一起,输出最终代码的那个引擎。t3code 通常不会自己造一个渲染引擎,而是复用现有的模板引擎,比如 Mustache、Handlebars、EJS 这些。为什么?因为这些引擎足够成熟,语法简单,学习成本低,而且社区里有大量现成的工具和插件。我个人的偏好是 Mustache,因为它逻辑最少,模板里几乎不能写复杂逻辑,这反而逼着你把逻辑放在上下文准备阶段,模板保持干净。
2.3 与主流方案的对比:为什么不用 Yeoman 或 Plop
说到代码生成,很多人会想到 Yeoman 或者 Plop。这两个工具确实很流行,但我最终没有选它们,原因有几个。
Yeoman 太重了。它本质上是一个脚手架系统,适合从零开始生成整个项目。但 t3code 的使用场景是“在已有项目里局部生成代码”,用 Yeoman 就像用大炮打蚊子,配置复杂,依赖多,而且它的交互式命令行在自动化流程里很难用。
Plop 倒是轻量,而且也是基于 Handlebars 的。但 Plop 的问题在于它和文件系统的耦合太深,它默认的行为是“生成新文件”,而 t3code 经常需要“在已有文件里插入代码片段”。虽然 Plop 也能通过自定义 action 实现插入,但配置起来比较绕。另外 Plop 的配置文件是 JavaScript,对于只想写模板的人来说,多了一层不必要的认知负担。
t3code 的做法更直接:它就是一个薄薄的封装层,核心逻辑可能只有几十行代码,负责读取模板、准备上下文、调用渲染器、输出结果。你可以把它集成到 npm scripts 里,也可以做成一个独立的 CLI 工具,甚至可以直接在 Node.js 脚本里调用。这种“薄”的设计,让它在不同项目之间的迁移成本极低。
2.4 目录结构与约定
t3code 在项目里的目录结构通常长这样:
project-root/ t3code/ templates/ api/ fetch.mustache create.mustache model/ entity.mustache contexts/ user-list.json order-detail.json config.js src/ ...templates目录放所有模板文件,按类别分子目录。contexts目录放上下文数据,通常一个生成任务对应一个 JSON 文件。config.js是 t3code 的配置文件,里面定义模板路径、输出路径、渲染引擎等。
这种约定的好处是:任何人拿到这个项目,看一眼目录就知道代码生成是怎么回事。模板在哪里、数据在哪里、配置在哪里,一目了然。我见过一些团队把模板散落在各个角落,过两个月连自己都找不到,那就失去意义了。
3. 核心细节解析与实操要点
3.1 模板怎么写才不容易出错
写模板这件事,看起来简单,实际上有很多细节要注意。我踩过的坑包括:占位符命名混乱、模板里塞了太多逻辑、缩进和换行处理不当导致生成出来的代码格式乱七八糟。
先说占位符命名。我的经验是:用大驼峰命名占位符,并且加上明确的前缀或后缀。比如{{ResourceName}}、{{ApiPath}}、{{ModelName}}。不要用{{name}}这种太泛的词,因为一个模板里可能有多个“名字”,你分不清哪个是哪个。另外,占位符的名字要和上下文里的 key 完全一致,大小写敏感,这一点在 Mustache 里尤其要注意。
再说模板里的逻辑。Mustache 支持简单的条件判断和循环,比如{{#hasAuth}}...{{/hasAuth}}和{{#fields}}...{{/fields}}。但我的建议是:能不用就不用。模板里的逻辑越少,模板就越容易理解和维护。如果你发现某个模板里需要大量条件判断,那说明这个模板的抽象层级不对,应该拆成多个更小的模板,或者把逻辑移到上下文准备阶段。比如,与其在模板里判断“如果有权限就加一行代码”,不如在上下文里直接准备好authCode字段,有权限就传具体代码,没权限就传空字符串。
缩进和换行是另一个大坑。Mustache 默认会保留模板里的空白字符,这意味着如果你在模板里写了缩进,生成出来的代码也会带着那些缩进。但问题是,模板里的缩进是相对于模板文件的,而生成出来的代码要插入到目标文件的某个位置,那个位置的缩进可能和模板不一样。我的做法是:模板里不写缩进,所有缩进在渲染后通过代码格式化工具统一处理。比如生成完代码后,跑一遍 Prettier 或 ESLint --fix,让格式化工具去操心缩进问题。这样模板可以写得很干净,不用在缩进上纠结。
3.2 上下文数据的准备技巧
上下文数据的准备,是 t3code 使用过程中最灵活也最容易出问题的环节。上下文可以来自很多地方:命令行参数、JSON 文件、数据库查询结果、甚至另一个 API 的返回值。我的经验是:尽量让上下文来源单一且可追溯。
举个例子,如果你要从数据库表结构生成前端的数据模型定义,那上下文就应该直接来自数据库的information_schema查询结果,而不是手动在 JSON 文件里再抄一遍。手动抄写不仅容易出错,而且当数据库结构变更时,你还得记得去更新 JSON 文件,很容易漏掉。
但直接来自数据库也有问题:数据库里的字段名可能是user_name,而前端习惯用userName。这时候就需要一个“转换层”,把数据库的原始数据转换成模板需要的格式。这个转换层可以是一个简单的 JavaScript 函数,放在config.js里。比如:
function transformColumns(columns) { return columns.map(col => ({ name: toCamelCase(col.column_name), type: mapDbTypeToJs(col.data_type), comment: col.column_comment || '' })); }这个转换层的好处是:它把“数据清洗”和“代码生成”分开了。模板只负责渲染,不负责理解数据库的原始格式。这样当数据库类型映射规则变化时,你只需要改转换函数,不用动模板。
另外,上下文数据最好有版本控制。我习惯把常用的上下文 JSON 文件提交到 Git 里,这样每次生成代码时用的数据都是可追溯的。如果某次生成的代码有问题,你可以直接看对应的上下文文件,确认是数据错了还是模板错了。
3.3 渲染引擎的选择与配置
前面提到,t3code 通常复用现有的模板引擎。Mustache、Handlebars、EJS 是最常见的选择。我简单对比一下它们的适用场景:
| 引擎 | 语法复杂度 | 逻辑能力 | 学习成本 | 适合场景 |
|---|---|---|---|---|
| Mustache | 极简 | 弱 | 低 | 结构固定的代码片段 |
| Handlebars | 中等 | 中等 | 中 | 需要少量条件判断的模板 |
| EJS | 接近 JS | 强 | 中高 | 需要复杂逻辑的模板 |
我的建议是:从 Mustache 开始。如果发现确实需要更多逻辑,再考虑 Handlebars。EJS 虽然灵活,但它的语法太像 JavaScript 了,容易让人在模板里写业务逻辑,最后模板变得比手写代码还难维护。
配置渲染引擎的时候,有一个细节要注意:转义问题。Mustache 默认会对 HTML 特殊字符进行转义,但代码生成场景下,我们通常不需要 HTML 转义。比如模板里写{{value}},如果 value 是a > b,Mustache 会输出a > b,这在代码里就错了。解决办法是用三花括号{{{value}}}来禁止转义,或者在配置里关闭默认转义。我一般直接在配置里关掉转义,因为代码生成场景下,HTML 转义几乎总是错的。
3.4 输出路径与文件命名策略
生成出来的代码往哪里放,文件名怎么起,这也是有讲究的。我的原则是:输出路径和文件名必须能从上下文里推导出来,不能靠人工指定。
比如你要生成一个用户相关的 API 文件,输出路径应该是src/api/user.js,文件名user来自上下文的resourceName字段。这样当你批量生成多个资源的时候,只需要准备多组上下文,输出路径会自动计算出来,不需要一个个手动指定。
实现方式很简单,在config.js里定义一个路径模板:
outputPath: 'src/api/{{resourceName}}.js'然后渲染这个路径模板,得到最终的文件路径。如果目标文件已经存在,t3code 的行为应该是“询问是否覆盖”或者“跳过”,而不是直接覆盖。我一般配置成“如果文件存在且内容相同则跳过,内容不同则询问”。这样可以避免误覆盖手动修改过的文件。
还有一个细节:生成的文件头部加一个标记注释。比如:
// AUTO-GENERATED BY t3code. DO NOT EDIT MANUALLY.这个注释的作用是提醒自己和同事:这个文件是生成的,手动改的话下次生成会被覆盖。如果确实需要手动修改,应该改模板或者上下文,而不是直接改生成结果。这个习惯能省掉很多“为什么我的修改不见了”的困惑。
4. 完整实操流程:从零搭建一个 t3code 工作流
4.1 初始化项目结构与依赖安装
假设我们有一个 Node.js 项目,现在要引入 t3code 来生成 API 请求函数。第一步是创建目录结构和安装依赖。
mkdir -p t3code/templates/api t3code/contexts npm install mustache --save-dev这里只装了 Mustache,因为它是我们选择的渲染引擎。如果你用 Handlebars,就换成npm install handlebars --save-dev。不需要装其他东西,t3code 的核心逻辑我们自己写,保持轻量。
然后创建t3code/config.js:
const path = require('path'); const fs = require('fs'); const Mustache = require('mustache'); // 关闭 HTML 转义 Mustache.escape = function(text) { return text; }; module.exports = { templatesDir: path.join(__dirname, 'templates'), contextsDir: path.join(__dirname, 'contexts'), outputBase: path.join(__dirname, '..', 'src'), // 根据上下文计算输出路径 getOutputPath(context) { return path.join(this.outputBase, 'api', `${context.resourceName}.js`); }, // 读取模板 getTemplate(templateName) { const templatePath = path.join(this.templatesDir, `${templateName}.mustache`); return fs.readFileSync(templatePath, 'utf-8'); }, // 读取上下文 getContext(contextName) { const contextPath = path.join(this.contextsDir, `${contextName}.json`); return JSON.parse(fs.readFileSync(contextPath, 'utf-8')); } };这个配置文件定义了模板目录、上下文目录、输出基础路径,以及三个核心方法:计算输出路径、读取模板、读取上下文。代码量很少,但足够支撑整个工作流。
4.2 编写第一个模板文件
在t3code/templates/api/下创建fetch.mustache:
import request from '@/utils/request'; /** * 获取{{resourceLabel}}列表 * @param {Object} params - 查询参数 * @returns {Promise<Object>} */ export async function fetch{{ResourceName}}(params) { const response = await request.get('{{apiPath}}', { params }); return response.data; } /** * 获取单个{{resourceLabel}} * @param {string|number} id - {{resourceLabel}}ID * @returns {Promise<Object>} */ export async function fetch{{ResourceName}}ById(id) { const response = await request.get(`{{apiPath}}/${id}`); return response.data; }这个模板定义了两个函数:获取列表和获取单个详情。占位符有{{resourceLabel}}、{{ResourceName}}、{{apiPath}}。注意这里用了{{ResourceName}}大驼峰和{{resourceLabel}}小驼峰,分别用于函数名和注释,这是有意的区分。
4.3 准备上下文数据
在t3code/contexts/下创建user.json:
{ "resourceName": "user", "ResourceName": "User", "resourceLabel": "用户", "apiPath": "/api/users" }这个上下文对应“用户”这个资源。resourceName用于文件名,ResourceName用于函数名,resourceLabel用于注释,apiPath用于实际请求路径。
4.4 编写生成脚本并运行
创建t3code/generate.js:
const fs = require('fs'); const path = require('path'); const config = require('./config'); const Mustache = require('mustache'); function generate(templateName, contextName) { const template = config.getTemplate(templateName); const context = config.getContext(contextName); const outputPath = config.getOutputPath(context); const result = Mustache.render(template, context); // 确保输出目录存在 const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 检查文件是否已存在 if (fs.existsSync(outputPath)) { const existing = fs.readFileSync(outputPath, 'utf-8'); if (existing === result) { console.log(`跳过 ${outputPath},内容无变化`); return; } console.log(`警告:${outputPath} 已存在且内容不同,将覆盖`); } fs.writeFileSync(outputPath, result, 'utf-8'); console.log(`生成成功:${outputPath}`); } // 从命令行参数获取模板名和上下文名 const [,, templateName, contextName] = process.argv; if (!templateName || !contextName) { console.error('用法:node generate.js <templateName> <contextName>'); process.exit(1); } generate(templateName, contextName);然后在package.json里加一个 script:
{ "scripts": { "gen": "node t3code/generate.js" } }运行:
npm run gen api/fetch user如果一切正常,你会在src/api/user.js看到生成好的代码。打开文件检查一下,函数名、注释、请求路径都应该正确替换了。
4.5 批量生成多个资源
单个生成跑通之后,批量生成就很简单了。你可以写一个简单的 shell 脚本或者 Node.js 脚本,遍历contexts目录下的所有 JSON 文件,依次调用生成逻辑。比如:
const contextFiles = fs.readdirSync(config.contextsDir); contextFiles.forEach(file => { const contextName = path.basename(file, '.json'); generate('api/fetch', contextName); });这样你只需要往contexts目录里丢 JSON 文件,跑一次脚本,所有 API 文件就都生成了。我实际项目里用这种方式一次性生成了二十多个 API 文件,前后不到十秒钟,而且格式完全一致,比手写快太多了。
5. 常见问题与排查技巧实录
5.1 生成出来的代码格式乱了怎么办
这是最常见的问题。原因通常是模板里的缩进和换行跟目标文件的风格不一致。解决办法有两个:一是模板里不写缩进,生成后用 Prettier 格式化;二是在模板里用 Mustache 的“独立行”特性来控制换行。
Mustache 有一个特性:如果一个 section 标签(比如{{#fields}})单独占一行,那么渲染后这一行会被移除,不会留下空行。利用这个特性,可以写出很干净的模板。比如:
{{#fields}} {{name}}: {{type}}, {{/fields}}如果fields有三项,渲染结果就是三行,每行前面有两个空格缩进。但如果fields为空,整个块都不会输出,也不会留下空行。
不过说实话,靠模板控制格式始终不如格式化工具靠谱。我的建议是:生成完代码后,自动跑一遍 Prettier。在generate.js的最后加一行:
require('child_process').execSync(`npx prettier --write ${outputPath}`);这样不管模板写得多乱,最终输出的代码都是格式统一的。
5.2 占位符没被替换,原样输出了
这种情况通常是上下文里缺少对应的 key。Mustache 在找不到 key 的时候,默认会输出空字符串,但如果你用了{{#key}}这种 section 语法,找不到 key 时整个块都不会输出。排查方法是:在渲染之前,打印一下上下文对象,确认所有需要的 key 都存在。
另一个可能的原因是 key 的大小写不匹配。Mustache 是大小写敏感的,{{ResourceName}}和{{resourceName}}是两个不同的 key。我建议在上下文准备阶段就统一命名规范,比如所有用于函数名的 key 都用大驼峰,所有用于变量名的 key 都用小驼峰,这样不容易搞混。
5.3 生成的文件覆盖了手动修改的内容
这是最危险的问题。我的解决方案是在生成脚本里加一个“内容比对”逻辑:如果目标文件已存在,先读取现有内容,和生成结果比对。如果完全相同,跳过;如果不同,输出警告并询问是否覆盖。在自动化脚本里,可以加一个--force参数来强制覆盖,默认行为是“不覆盖,只警告”。
另外,前面提到的“文件头部加 AUTO-GENERATED 注释”也很重要。它至少能让后来的人知道这个文件是生成的,手动改之前会三思。
5.4 模板越来越复杂,维护困难
这是模板驱动开发的一个常见陷阱。一开始模板很简单,但随着需求变化,你不断往模板里加条件判断、循环、特殊处理,最后模板变得比手写代码还难懂。我的经验是:当一个模板超过 50 行,或者包含超过 3 个条件判断时,就应该考虑拆分。
拆分的维度可以是“按功能拆”或“按层级拆”。比如一个完整的 CRUD 模板,可以拆成create.mustache、read.mustache、update.mustache、delete.mustache四个独立模板,每个模板只负责一个操作。这样每个模板都很短,容易理解和修改。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 占位符原样输出 | 上下文缺少 key | 打印上下文对象 | 补全上下文数据 |
| 代码缩进混乱 | 模板缩进与目标不一致 | 检查模板空白字符 | 生成后跑 Prettier |
| 文件被覆盖 | 未做存在性检查 | 查看生成脚本逻辑 | 加内容比对和确认提示 |
| 模板难以维护 | 模板过于复杂 | 统计模板行数和条件数 | 拆分为多个小模板 |
| 批量生成时部分失败 | 某个上下文数据有误 | 逐个检查上下文文件 | 修复数据后重新生成 |
5.6 几个我踩过的坑和对应技巧
第一个坑:在模板里用了 JavaScript 保留字作为占位符名。比如{{class}}、{{default}},这些词在 Mustache 里可能没问题,但生成出来的代码里如果直接用了这些词作为变量名,就会报语法错误。解决办法是占位符名加前缀,比如{{className}}、{{defaultValue}}。
第二个坑:上下文里的数组为空时,模板输出了多余的空行。比如{{#fields}}...{{/fields}}如果fields是空数组,Mustache 不会输出任何内容,但如果你在{{#fields}}前后写了换行,那些换行会保留。解决办法是用 Mustache 的“独立行”特性,把 section 标签单独放一行。
第三个坑:生成的文件路径包含特殊字符。比如资源名里有空格或斜杠,直接拼到路径里会导致文件创建失败。解决办法是在上下文准备阶段对资源名做 slug 化处理,把空格替换成连字符,去掉特殊字符。
第四个坑:Windows 和 Unix 的换行符不一致。模板文件在 Windows 上编辑后可能变成 CRLF 换行,生成出来的代码在 Unix 环境下跑 lint 会报错。解决办法是在.gitattributes里设置*.mustache text eol=lf,强制模板文件用 LF 换行。
6. 进阶玩法:让 t3code 融入日常开发流程
6.1 与 Git Hooks 结合,自动检查生成文件是否最新
一个很实用的技巧是:在 Git 的 pre-commit hook 里加一步,检查生成的文件是否和模板+上下文一致。如果不一致,说明有人改了模板或上下文但忘了重新生成,hook 可以直接阻止提交并提示运行生成命令。
实现方式很简单,写一个脚本,重新生成所有文件到临时目录,然后和当前工作区的文件比对。如果有差异,输出提示信息并退出码设为 1。这样能保证仓库里的生成文件始终和模板保持同步。
6.2 与 CI 集成,自动生成并提交
如果你的团队习惯把生成的文件也提交到仓库(而不是在构建时生成),那可以在 CI 流程里加一步:每次合并到主分支时,自动运行生成脚本,如果有文件变更就自动提交。这样开发者只需要维护模板和上下文,生成文件由 CI 负责保持最新。
不过这种做法有争议。有些团队认为生成文件不应该提交,应该在构建时动态生成。我的看法是:如果生成文件是项目运行必需的(比如前端代码),那就提交;如果只是开发辅助(比如文档),那就不提交。具体看项目情况。
6.3 扩展方向:从代码生成到文档生成
t3code 这套机制不仅限于生成代码。你完全可以用同样的模板+上下文+渲染器的思路来生成 API 文档、数据库设计文档、甚至测试用例。比如,你可以写一个 Markdown 模板,把上下文里的字段信息渲染成表格,自动生成接口文档。这样当接口变更时,只需要更新上下文数据,文档自动跟着变,不用手动维护。
我实际项目里就用这种方式生成了 Swagger 文档的一部分。上下文来自数据库表结构和路由配置,模板是一个 YAML 文件。每次数据库变更后跑一次生成脚本,Swagger 文档就更新了,省掉了大量手动同步的工作。
6.4 团队协作中的约定与规范
如果团队里多个人都要用 t3code,那必须有一些约定。我的建议是:
- 模板文件必须经过 code review 才能合并,因为模板的错误会影响所有生成结果。
- 上下文文件的命名要统一,比如都用
资源名.json的格式。 - 生成脚本要放在项目根目录的
scripts/或tools/下,不要散落在各处。 - 在 README 里写清楚怎么用,包括依赖安装、生成命令、常见问题。
这些约定看起来琐碎,但能避免很多“为什么我生成的代码和别人不一样”的扯皮。
7. 一些个人体会和后续扩展思路
t3code 这套东西我用了大概一年多,最大的感受是:它把“写代码”变成了“描述代码”。以前写一个 API 文件,我要打开编辑器,一行行敲,敲完还要检查命名、路径、注释格式。现在我只用准备一个 JSON 文件,跑一条命令,代码就出来了,而且格式完全统一。省下来的时间可以花在真正需要思考的业务逻辑上。
当然,它也不是银弹。如果你的项目里重复代码不多,或者每个模块的结构差异很大,那引入 t3code 可能反而增加维护成本。我的判断标准是:如果你发现自己在复制粘贴同一段代码超过三次,那就值得把它模板化。
后续我打算把 t3code 和数据库 schema 更紧密地结合起来。现在的上下文还是手动准备的 JSON 文件,下一步想直接从数据库的information_schema读取表结构,自动生成上下文,然后渲染出前端的数据模型和 API 文件。这样当数据库变更时,前端代码可以一键同步,减少手动同步的遗漏。
另外,我也在考虑把模板引擎从 Mustache 换成 Handlebars,因为有些场景确实需要更灵活的条件判断。但换引擎的成本不高,因为模板语法差异不大,主要是配置和渲染调用的改动。等实际换了之后再分享经验。
如果你也在用类似的方式管理重复代码,或者有更好的方案,欢迎交流。这个东西没有标准答案,适合自己团队的就是最好的。