资讯详情

t3code 前端工程化基线方案:规范、工具链与落地实践全解析

📅 2026/10/27 23:09:13 | 华诺云谱 👁 阅读
t3code 前端工程化基线方案:规范、工具链与落地实践全解析
半年前我们组做 Code Review 的时候最常听见的一句话是“这代码风格跟我的不太一样”。乍一听是审美问题实际上背后是规范缺失、工具链断裂和责任边界模糊。后来我们三个人花了两个星期把散落在各处的 lint 规则、格式化配置、提交检查和评审清单收拢到一起做成了内部代号为t3code的一套前端工程化基线方案。这篇博文就把 t3code 从想法到落地的完整过程拆开讲一遍包括为什么叫这个名字、每个配置背后的取舍、怎么从零搭起来以及我们踩过的几个比较典型的坑。先交代一下背景t3code 不是某个开源框架也不是什么“低代码平台”它本质上是一套可复用、可版本化、可被存量项目渐进式接入的代码规范与工具链集合。适合正在为“代码风格不统一”“提交信息乱七八糟”“CI 里 lint 形同虚设”而头疼的团队也适合一个人维护多个项目、希望把工程经验沉淀成统一配置的开发者。如果你正处于“知道自己该定规范但不知道怎么落地”的阶段这一篇可以直接抄作业。1. 从代号到体系t3code 的诞生背景与整体思路1.1 为什么是“t3”这三个字符t3code 这个名字源自我们内部的三个关键词Team团队、Three三阶段、Trinity三位一体。三个人组了一个临时小组约定用三个阶段把规范推下去所以取首字母 T加上 code就成了项目代号。第一阶段是“定标准”解决“什么样的代码算好代码”第二阶段是“上工具”让机器替人检查第三阶段是“养习惯”通过提交信息规范、Code Review 模板和文档示例把标准固化成协作方式。三个阶段不是串行的实际执行中会有反复比如第三阶段暴露出的问题又会回流到第一阶段去改规则。从团队协作角度看把这三件事分开很重要。很多团队失败的原因是把“规范”和“工具”混在一起谈最后变成安装了一个 ESLint 插件就以为规范落地了。其实规范是语义层的约定工具只是把它变成机器可执行的形式。t3code 的第一版文档里就明确写了规则不解释清楚动机就一定会有人绕过它。1.2 整体拆解规范、工具、流程三个维度t3code 的体系结构可以拆成三个维度每个维度都有自己的产物和责任人。第一层是规范文档资产包括命名约定、目录结构约定、组件设计约定、CSS 类名约定它以 Markdown 形式存到仓库的 docs 目录里作为评审时的“法律依据”。第二层是工具配置资产包括 ESLint 规则集、Prettier 配置、Stylelint 配置、Commitlint 配置、EditorConfig 配置这些被打包成 npm 包通过版本号管理。选 npm 包而不是直接复制 .eslintrc 文件到各个项目是因为后续改规则只需要发一个新版本存量项目执行一条升级命令就能同步不用挨个仓库去改文件。第三层是流程模板包括 Code Review 指引、PR 模板、issue 标签约定、提交信息格式示例这些以文件形式放在项目根目录的 .github 或 docs 目录中。流程模板的作用是减少“讨论成本”比如提交信息格式定了之后就不要再为“feat 后面要不要加冒号”争论了。这三个维度对外表现为 t3code 的一个仓库对内则对应三条明确的分工一个人维护规则包、一个人维护文档和示例、一个人负责在存量项目中做接入试点。分工明确之后推进速度比之前开三次全员会快得多。1.3 技术选型为什么是 ESLint Prettier Stylelint Commitlint 这条组合t3code 的工具链选了目前社区最成熟的一条组合ESLint 管 JS/TS 的代码质量、Prettier 管格式化、Stylelint 管样式、Commitlint 管提交信息、Husky 负责在 Git 钩子阶段触发检查。这套组合的优点是生态稳定、文档齐全、团队成员的既有认知成本低。选型的时候也考虑过更轻量的方案比如只用 Prettier 简单的脚本检查但后来放弃了。原因是 Prettier 只管格式不管“禁止使用 console.log 提交到主干”这种逻辑层面的问题而这些恰恰是评审中最容易反复出现的问题。ESLint 通过自定义 rule 或引入 eslint-plugin-regexp 这类插件能把团队约定转换成可自动校验的规则这是轻量方案做不到的。还有一点比较关键Husky 的版本选择。我们用的是 Husky v9它要求 Git 版本不低于 2.13且初始化方式与 v4 完全不同。如果你网上搜到的是 v4 的配置方式照着做大概率会失效。这个细节会在后面“常见问题”部分重点展开。2. 核心规范解析把“代码标准”落到纸面上2.1 命名与目录结构约定t3code 的第一份规范文档里争议最大的不是 ESLint 规则而是目录结构和命名。每个团队都有自己的历史包袱约定不可能一蹴而就但至少可以约定“新增代码”的规则。我们为 React TypeScript 项目定下的目录结构是这样的src 下分 components、hooks、pages、services、styles、utils、types 七个顶级目录。components 目录内按业务域分子目录每个组件目录包含 index.tsx、style.ts、types.ts、index.test.tsx 四个文件。文件名使用 PascalCase常量文件使用 camelCase工具函数使用 camelCase。命名规范方面我们吸取了一个比较痛的教训以前项目里大量出现 btn、list、data 这类含糊变量名。t3code 规定组件目录名和组件函数名必须一致状态变量使用名词加状态的组合比如 isLoading、hasError回调函数统一用 handle 开头比如 handleClick、handleSubmit避免出现五花八门的 onClickXXX。事件处理函数里动宾结构优先比如 submitForm 而不是 onForm。这些规范最有效的地方在于写进了 ESLint 的自定义规则或通过 eslint-plugin-react 的 prop-types 相关规则间接校验了一部分。比如组件函数名和目录名的一致性依靠规范文档人工检查但变量命名规范里“禁止出现单个字母变量名除循环变量”是可以通过 no-restricted-syntax 规则做的。2.2 代码质量规则的分级策略t3code 把 ESLint 规则分成两类error必须改和warn建议改。error 级别包括未使用变量、隐式 any、浮点数精度比较、重复导入等会直接导致 bug 或维护困难的问题warn 级别包括 console.log、todo 注释、嵌套过深等可以阶段性处理的问题。这个分级很重要因为如果一开始就把所有规则都设成 error存量项目接入时会产生海量报错团队会直接放弃。t3code 的策略是新项目直接使用最严配置存量项目先用 warn 跑一周沉淀出真实问题样本后再把高频问题升级为 error。规则分级不是技术问题而是推进节奏问题。举一个具体例子我们默认开启了typescript-eslint/no-explicit-any为 warn因为很多老代码里的 any 其实有历史包袱直接改成 unknown 可能引发类型错误。但新增代码出现 any 是绝对不允许的所以我们加了一条自定义规则在 jslint 配置中通过 overrides 对 src 下的新增文件即 git diff 新增的行单独启用 error 级别。这个能力通过 lint-staged 配合实现后面会详细说。2.3 提交信息与 Git 工作流约定提交信息规范是 t3code 里成本最低但回报最明显的一块。我们接入了 Commitlint 和 Husky 的 commit-msg 钩子格式统一为 type(scope): subject其中 type 限定为 feat、fix、refactor、docs、chore、test 六个scope 限定为组件名或模块名subject 用动词开头、不超过 50 个字符。有人会觉得提交信息规范是形式主义但我们统计过在接入 commitlint 之前PR 描述的质量参差不齐因为提交信息太混乱很多 reviewer 根本看不出这个 PR 的意图。有了统一格式之后至少能快速从 Git 历史里筛选出“feat”和“fix”来确认改动范围这在该回滚 release 版本时会非常救命。Git 分支模型方面t3code 没有引入复杂的 Git Flow而是用主干开发 特性分支 短生命周期 PR 的方式。每个功能分支命名格式为 feat/description 或 fix/description方便 CI 自动关联到对应 issue。规则简单到连实习生都不会弄混执行率反而比过去用 Git Flow 时高了很多。3. 实操落地从零搭建 t3code 工具链3.1 初始化项目与基础配置下面进入实操环节假设我们要在一个新的 npm 仓库里搭建 t3code 的核心包。先创建项目并初始化mkdir t3code cd t3code npm init -y git init然后安装基础依赖。这里建议把包的 type 设为 module并配置 exports 字段方便在 ESM 和 CJS 之间做兼容。基础依赖分为三类核心检查工具、ESLint 相关插件、辅助工具。npm install --save-dev eslint prettier typescript typescript-eslint/parser typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks stylelint stylelint-config-standard stylelint-config-prettier commitlint commitlint/cli commitlint/config-conventional husky lint-staged这里有个细节eslint-config-prettier 必须排在 extends 数组的最后否则 Prettier 和 ESLint 在格式规则上冲突时不会正确报错。它做的事情是关闭 ESLint 中所有可能与 Prettier 冲突的格式化类规则而不是让 Prettier 去覆盖 ESLint 的判断。接着创建 .editorconfig 文件统一缩进和换行符root true [*] charset utf-8 indent_style space indent_size 2 end_of_line lf insert_final_newline true trim_trailing_whitespace trueEditorConfig 的优先级高于编辑器的默认配置但低于 ESLint 的 prettier/prettier 规则。所以它更像是一层“编辑器级兜底”真正在 CI 层把关的还是 ESLint 和 Prettier。3.2 集成 ESLint、Prettier 与自动修复ESLint 的配置我们写成 eslint.config.jsESLint 9 的 flat config 格式。解释一下为什么要用 flat configESLint 9 之后旧的 .eslintrc 格式已经进入维护模式新项目直接用 flat config 可以避免以后迁移成本。flat config 和 eslintrc 的差异主要是“不再有 extends 的隐式继承”所有配置都必须显式写在数组里。一段可以用的 ESLint 配置示例import js from eslint/js; import tseslint from typescript-eslint; import reactHooks from eslint-plugin-react-hooks; import prettier from eslint-config-prettier; export default [ js.configs.recommended, ...tseslint.configs.recommended, { files: [**/*.{ts,tsx}], plugins: { react-hooks: reactHooks, }, rules: { ...reactHooks.configs.recommended.rules, typescript-eslint/no-unused-vars: warn, typescript-eslint/no-explicit-any: warn, }, }, prettier, { ignores: [dist, node_modules, coverage], }, ];注意这里我们没有加 react 相关的推荐规则集因为 t3code 的第一版只针对 TS/React 场景做示例实际使用时你可以把 react 插件的 recommended 一起扩展进去。typescript-eslint这个包是官方推荐的统一入口同时提供了 parser 和 plugin代码上比分别安装 typescript-eslint/parser 和 typescript-eslint/eslint-plugin 更简洁。Prettier 的配置故意保持最小化{ printWidth: 100, singleQuote: true, trailingComma: es5, semi: true }之所以尽量少配置是因为 Prettier 的设计哲学是“可配置项越少越好风格问题交给默认值”。团队里最容易起分歧的其实是 semicolon 和单双引号把这两个定了其他交给 Prettier 就行。有些团队喜欢把 printWidth 设成 120我建议先用官方默认 80之后如果真的觉得换行太频繁再改成 100不要一开始就设 120因为过长的行在 Review 工具里体验很差。自动修复链路的最后一环是 lint-staged它只对暂存区的文件运行检查避免全量检查拖慢提交速度。在 package.json 里配置{ lint-staged: { *.{ts,tsx}: [eslint --fix, prettier --write], *.{css,scss}: [stylelint --fix, prettier --write] } }lint-staged 的意义在于把检查范围缩小到 diff 涉及的文件这样就算存量项目历史上有 1000 个 lint 错误也不会阻塞你提交新代码只是新增部分必须干净。3.3 加入 Stylelint 与提交前检查样式规范这一块我们踩过一个坑早期只配置了 ESLint 和 PrettierCSS 里出现重复颜色值、未知属性这类问题完全靠肉眼。后来引入了 Stylelint规则集直接用 stylelint-config-standard再加了几条自定义规则比如禁止 id 选择器、颜色值必须使用变量等。Stylelint 配置示例export default { extends: [stylelint-config-standard, stylelint-config-prettier], rules: { color-no-hex: true, selector-id-pattern: null, }, };把 color-no-hex 开成 true意味着所有颜色值必须走变量或色板这样主题换肤时只需要改变量文件。这个规则在接入了设计令牌design token体系的团队里非常实用。接下来配置 Husky 和 Commitlint。Husky v9 的初始化命令是npx husky init执行后会在项目根目录生成 .husky/pre-commit 文件默认内容是npm test我们把它改成npx lint-staged然后创建 .husky/commit-msg 文件npx --no -- commitlint --edit $1这一行命令的作用是在每次提交时校验提交信息是否符合我们的格式约定。如果你团队用的是 pnpm注意把 npx 换成 pnpm exec避免因为包管理器差异导致钩子命令找不到可执行文件。3.4 发布为团队私有包并接入存量项目配置验证通过后我们把 t3code 发布到公司内部的 npm registry包名定为 team/t3code。里面包含 .eslintrc 或 eslint.config.js 的导出、Prettier 配置、Stylelint 配置以及配套文档。这样存量项目只需要两条命令就能接入npm install --save-dev team/t3code npx t3code initinit 命令做的事情是读取项目现状、生成对应的配置文件入口、并打印一份接入报告告诉你有多少存量 lint 错误、这些错误集中在哪些文件、建议的修复顺序。这个“接入报告”是我们觉得最有价值的功能因为它把“抽象地讲规范”变成了“具体地列任务”。接入一个新项目时我建议按照“格式化修复 → 低风险错误修复 → 中高风险错误修复 → 规则升级”四步走。格式化修复最简单直接跑 prettier --write 全量处理低风险错误包括未使用变量、隐式导入类型错误等中高风险则涉及 any 类型、数组索引访问、React hooks 依赖缺失等需要人工确认。4. 常见问题与排查技巧实录4.1 Husky 钩子不生效的排查这是整个落地过程中被问得最多的问题。常见现象是git commit 时 pre-commit 完全没有触发或者提示command not found: lint-staged。第一步排查 Git 版本Husky v9 要求 Git 2.13老版本会直接失败。第二步确认 .husky 目录是否正确生成并且该目录下的文件有可执行权限。第三点最容易忽略Husky 钩子在 clone 后默认不生效必须执行npx husky install或者首次运行npm install时自动触发 prepare 脚本。所以我们把 husky 的安装命令写成了npx husky init npm run prepare并在 package.json 里配置{ scripts: { prepare: husky } }prepare 脚本会在 npm install 自动执行这样团队成员克隆仓库后装上依赖就能直接用钩子不需要额外命令。很多教程没有提这一点导致其他人拉代码后钩子失效其实根因就是 prepare 脚本缺失。4.2 Monorepo 与 workspace 场景的坑如果用 pnpm workspace 或 npm workspaceshusky 和 lint-staged 的配置位置有讲究。最简单的做法是在 monorepo 根目录统一安装 husky 和 lint-staged钩子文件也放在根目录。因为 Git 默认的 hooks 路径是仓库根目录子包内安装的 husky 不会生效。但 lint-staged 对 monorepo 的支持稍微复杂一点如果每个子包都有独立 ESLint 配置你可能需要按 package.json 里的 lint-staged 配置分区执行。我们的做法是在根目录配置统一的 lint-staged并通过 commands 参数指定每个子包用自己目录下的配置{ lint-staged: { packages/**/src/**/*.{ts,tsx}: [ eslint --fix --config packages/eslint.config.js ] } }这样能保证每个包的规则边界是清晰的不会被根目录的配置误伤。不过 monorepo 中 ESLint 的解析会因为 tsconfig 的 project 引用而变得复杂建议在初步接入时先用parserOptions.project: false关闭类型感知规则等能跑通基础规则后再逐步开启。4.3 规则冲突与覆盖顺序ESLint 在 flat config 中数组后面的配置会覆盖前面的配置这就是为什么 prettier 必须放在最后。但实际中我们遇到过typescript-eslint/no-unused-vars和 Prettier 同时报错的情况原因是某些参数被标记为未使用而 Prettier 的格式化又把参数换行了导致看起来两个工具莫名“互掐”。解决办法是在 ESLint 的 no-unused-vars 规则里配置argsIgnorePattern: ^_约定用下划线开头的参数表示“有意忽略”。这样既保留了未使用变量的报错能力又不会因为函数签名里有一个不用的参数就全局报错。这个约定同时写进了规范文档让 review 的人知道下划线参数是显式声明不是遗留垃圾。还有一个经常被忽视的覆盖问题ESLint 的 overrides 是按文件模式匹配的但 flat config 里没有 overrides而是每个配置对象自己的 files 字段。迁移到 flat config 时如果照搬 .eslintrc 的 overrides很容易出现规则完全不生效的情况。建议对照官方迁移文档逐个检查 files 模式的正则写法。4.4 问题排查速查表现象可能原因排查步骤解决方案commit 不触发 linthusky prepare 未配置执行ls -la .husky/查看钩子文件配置 prepare 脚本后重新npm installlint-staged 不格式化新增文件配置文件 glob 未匹配新增文件名npx lint-staged --debug查看匹配列表调整 glob 表达式如**/*.{ts,tsx}ESLint 和 Prettier 冲突报错缺失 eslint-config-prettier查看报错规则是否与格式有关在 extends/数组末尾加入 prettier 配置commitlint 不能识别中文提交信息subject 长度校验超限看报错是长度还是格式长度规则可自定义为 100但建议保持短提交monorepo 下钩子失效husky 安装在子包检查根目录 .git/hooks在 workspace 根目录集中安装 huskyFlow 类型文件被 lint 检查ESLint files 缺少 ignore 表达式查看 lint 报告里的文件路径在 ignores 数组中排除.js.flow或 build 目录纯 JS 项目非要跑 TS 解析parser 配置错误检查 parserOptions 中 ecmaVersion 与 sourceType移除 typescript-eslint/parser 或仅对 ts/tsx 使用表格之外再分享一个实用配置在 CI 里跑 lint 时建议增加--max-warnings0参数让 warn 级别的警告也导致任务失败。这个听起来“过于严格”但实际执行下来会发现它逼着团队在一周内把 warn 级问题清理干净否则合并请求会被一直拦截。相比人盯人式的督促机器拦截省心得多。还有一个容易踩到的坑Prettier 格式化会在某些情况下把 import 顺序打乱而 eslint-plugin-import 有 order 规则这两者同时开可能会导致无休止的“格式化后报错修复后又不合一”。我们的处理方式是放弃在工具层控制 import 顺序而是靠约定第三方依赖在前绝对路径导入次之相对路径导入最后。人工评审时扫一眼就行不值得为这个上工具复杂度。5. 存量项目渐进式接入t3code 的落地节奏接入存量项目最忌“一步到位”。我们的做法是先用一周时间做体检。体检脚本会统计项目内 lint 错误总数、按规则聚合 Top 20、按文件统计密度然后输出一份 Markdown 报告。这份报告不是用来吓人的而是用来排优先级。假设统计结果显示“no-unused-vars”错误 300 个“react-hooks/exhaustive-deps”错误 50 个。那么显然先从 300 个那个规则开始清理因为它机械性强、不会引发行为变化修完能立刻把错误总数降下来提升团队信心。而 react-hooks/exhaustive-deps 这种规则误报比例可能不低修的时候需要看上下文更适合第二阶段再集中处理。接入期间建议在 CI 上开“warn 模式”两周让错误只出现在日志里不阻塞合并。两周之后做第二次统计看错误数量是否自然下降再决定是否切换为 error 模式。我们实际执行下来这个过渡期大概比预期快一倍因为 warn 模式虽然不阻塞但大家看到自己的 PR 日志里全是红色 warning自尊心会驱使他们顺手修掉。存量项目里还有一个比较特殊的情况老代码里大量使用了相对路径 import甚至路径深度到了 ../../../utils/xxx。t3code 不会强制你立刻改成 alias因为这类重构风险高、收益慢。我们的约定只有一条新增文件必须使用 alias 导入老文件在修改时顺手改。这条约定执行半年后新文件基本都不再出现深层次相对路径了。6. 经验复盘与最后几个建议t3code 落地到现在最大的收获不是 lint 错误清零而是团队对“代码评审”的讨论焦点变了。以前评审是在争论“这个变量名好不好”现在是在讨论“这个设计能不能复用”“边界情况有没有考虑”。前者是审美分歧后者是工程质量。工具把低层次的争议过滤掉人的精力自然就上收到更高层次。如果你也要做类似的事我有几个比较具体的建议。第一不要一开始就追求规则大而全选 30 到 50 条核心规则就够用规则越多误报越多信任消耗越快。第二所有规则必须写成文档并且备注“为什么”比如禁止使用any的原因要写“因为 any 会关闭类型检查导致重构时无法利用编辑器跳转”而不是简单写“禁止 any”。第三配置包一定要走 npm 版本管理不然后期收敛不同项目的规则版本会变成一场灾难。最后再分享一个小技巧在 t3code 的配置包里我们把版本号和变更记录做成了命令行工具执行npx t3code changelog就能看到最近三个版本的规则差异。这个功能在团队升级依赖时特别好用因为你不能再丢出一句“你升级一下 team/t3code”就不管了而是能明确告诉伙伴“这个版本加了哪些新规则、可能影响哪些代码”让升级变成一次有预期的、低风险的操作。t3code 本身不是一个有“技术含量”的项目它没有新颖的算法也没有复杂的架构。但从协作效率和代码可维护性的角度看它是我们这半年投入产出比最高的一笔投资。如果你的团队还在为代码风格和评审效率头疼不妨照这个思路从最小闭环开始搭建属于你们自己的“t3code”。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑