


在 TypeScript 前端单体仓库中工作时守住包的边界
让 AI 修改模块化的 TypeScript 前端代码库时使用。内容包括包边界与不依赖框架的核心、通过所属的 action 修改状态、文件与测试的位置、提交前要跑的检查、发布步骤,以及 UI 的分层方式。
要使用这个AI规则
## 只通过公开导出跨越包的边界
- 在多包工作区中,每个包只承担一项职责,只能通过包的公开导出跨越边界;绝不要在应用代码中导入其他包的内部实现,因为内部导入会让调用方与私有结构耦合,一旦结构变化就会出问题。用自动化的 lint 和架构检查来强制边界,而不是依赖人工评审;如果不存在合适的位置,就有意识地创建一个,并更新文档化的约定。
- 保持共享核心与框架无关、不接触浏览器 DOM:将 UI 框架绑定和 DOM/CSS 投影放在专门的层中;像 WASM 模块或大型 SDK 这类较重的运行时依赖,应从一个中心模块加载,由该模块向调用方提供注入实例,而不是在每个调用点临时各自导入。
- 不要创建唯一作用是转发另一个包 API 的重新导出垫片文件;在调用点直接导入所属的包。只有为了保留既有的公开 API 时,兼容性汇总文件(barrel)才可以重新导出所属包。
- 当更小的导入能让依赖意图更清晰时,优先使用范围窄、目标明确的子路径导出,而不是一个包罗万象的汇总文件。
## 共享状态只能通过其所属的 action 修改
- 用一个共享上下文加上各领域专属的动作模块(视口、选择、历史记录、结构等)来组装大型子系统,而不是塞进一个不断膨胀的单一文件。添加新行为前,先查看现有文件夹和相邻的写法模式,把改动放进最匹配的领域模块;不要把不相关的逻辑堆进当前恰好打开的文件里。
- 每一次状态变更都要经过其所属的 setter 或 action,这样事件、撤销批处理、派生更新等副作用才能稳定触发。绝不要直接给共享状态字段赋值,否则这些副作用会悄无声息地不执行;组件内部也不要保留模块级的可变状态,共享状态应统一放在 store 中。
- 通过带类型的事件系统暴露变更,让订阅者以响应事件的方式做出反应,而不是轮询。各领域模块之间应通过上下文对象共享状态,而不是相互导入应用代码或框架代码。
- 应用层的封装可以在核心之外添加响应式状态、持久化和平台 I/O,但核心本身要始终保持与框架无关;共享代码中使用任何浏览器 API 之前,都要用明确的运行时检查加以保护。
## 在 TypeScript 中禁用 any 和非空断言
- 禁止使用 `any`:改用精确类型、泛型和声明合并。禁止使用非空断言:改用类型守卫、可选链和空值合并运算符。这两条规则都能让类型检查器真正捕获错误,而不是把错误掩盖过去。
- 优先使用精确的联合类型,而不是宽泛的原始类型——比如用一个小的字符串字面量联合类型,而不是 `number | string | null`;如果已经存在具名的共享类型,就不要再内联定义一份,直接导入复用即可。
- 把重复出现或跨功能使用的常量统一定义在共享的常量模块中,而不是在代码库各处重复写字面量。
- 绝不要用非加密随机源来生成标识符或任何与安全相关的值;任何地方都要使用加密安全的随机数生成器。修改嵌套数据时,要用结构化克隆做深拷贝,绝不能用浅展开。
## 按领域划分文件夹,并把各类测试放在固定位置
- 按领域命名空间组织代码,而不是搞繁琐的深层目录结构;应用服务、路由或布局视图、UI 各自保留在自己的顶层区域中。
- 对于包含多个文件的领域,使用子文件夹,而不是让多个同级文件共用重复的前缀。当一个领域出现第二个文件时,就应立即建立文件夹,而不是继续用前缀命名——优先使用 `selection/hit-test`,而不是 `selection-hit-test`。
- 像 `index`、`types`、`context` 这类约定俗成的入口文件名要保持小写,组件文件则遵循所在生态系统的命名大小写惯例。不要随意摆放代码或测试文件;如果没有合适的位置,就有意识地创建一个并记录下来。
- 测试文件的放置要严格按位置规则执行:把每种测试类型放进各自既定的目录(端到端、集成、单元、共享辅助函数、按包划分)。用户可见的行为应放进端到端测试;内部状态和数据断言应放进单元测试。绝不要提交临时或用于性能分析的测试文件。
## 用现有的库,不要手写同样的代码
- 不要自己动手重新实现某个依赖已经提供的功能。先检查现有依赖;如果都不合适,就引入一个高质量的库,而不是手写实现——例如生成统一 diff 应使用 diff 库、颜色运算应使用颜色库,而不是自己写循环。
- 在自行构建自定义 UI、控件或可组合函数之前,先阅读依赖的上游文档,而不是靠猜测本地已有的用法;如果有机器可读的文档入口,优先使用它。如果上游文档与本地写法有冲突,以当前的上游 API 为准,并有意识地更新本地的封装层。
- 把需要长期维护的程序和辅助函数放进一个按领域组织的专门工具目录,并各自配备聚焦的测试,而不是随意堆放在根目录下的脚本中。scripts 目录只保留极小的兼容性入口垫片,这些垫片从工具目录导入实现,本身不包含实现逻辑。
- 把工具代码的测试以及 lint、格式检查也纳入标准检查流程,让工具目录接受与产品代码同等的质量标准。
## 提交前跑完全部质量检查,并按 Conventional Commits 写提交信息
- 在提交之前、以及在发起评审之前,都要各运行一遍完整的质量门禁:类型感知的 lint 与类型检查要零错误、格式化工具要配合导入排序、复制粘贴与重复代码检测要零克隆,还要跑相关的单元测试和端到端测试套件。
- 提交前先自我审查:如果发现重复代码增多,就提取共享的辅助函数或复用已有类型;删除与具名类型重复的内联类型定义,以及任何复制粘贴出来的逻辑;文件行数要控制在几百行以内,一旦增长就按领域拆分。
- 提交信息按 Conventional Commits 规范书写(`feat`、`fix`、`refactor`、`perf`、`docs`、`test`、`build`、`ci`、`chore`):首行简短、使用祈使语气,可选加上作用域,type 用小写;正文说明理由,每一行正文都以大写字母开头的单词起始。
- 标题和正文中要保留产品名和缩写原有的大小写形式,不要把缩写一律压成小写文字;作用域要选用如实反映改动范围、尽量窄的写法——当改动跨越多个不相关领域时,可以省略作用域。
## 按固定顺序发布,并让变更日志保持最新
- 遵循固定且有序的发布流程:在每一个携带版本号的文件中更新版本号,把变更日志中「Unreleased」部分的条目移到一个带日期的新版本标题之下,用发布信息提交,然后创建并推送标签。打标签之前要确认所需的签名或发布密钥确实存在。
- 把构建与发布工作流当作打包和发布的唯一权威来源,任何重复维护的包列表都要与它保持同步。生产环境的部署只能由发布标签或明确的手动触发来把关,绝不能由默认分支的普通推送直接触发,以确保每次发布都是有意为之。
- 保持文档与代码同步:只要行为发生变化,就更新变更日志的「Unreleased」部分和面向用户的文档;架构或约定发生变化时,也要更新贡献者或 AI 代理参考文档。
- 已发布的文档中不要包含推测性内容或内部实现计划——把临时性的计划放进一个被忽略跟踪的草稿区域,再把真正持久有效的方向提炼进正式文档;也不要创建占位性质的本地化译文副本——在真正的译文出现之前,本地化导航应链接回权威原文页面。
## 共享 UI 基础组件不得导入应用层代码
- 把共享、可复用的 UI 基础组件层与功能层、外壳层分离开。共享基础组件和底层样式控件绝不能导入应用服务、store 或功能面板;各功能特有的控件应由对应的功能组件自行拥有。
- 样式应通过在同一处定义的静态、带类型的主题变体来解析,并按实例合并,而不是把条件性的工具类分支或针对单个插槽的一次性 class 属性散落各处。视觉状态要通过语义化的 data 属性绑定,并在脚本中解析出带类型的变体。
- 测试定位器要优先使用面向用户的角色(role)、标签(label)和文本;用作用域限定的语义化属性来暴露身份标识,专门的 test id 只留给少数的集成边界场景,而不是在共享组件内部制造全局唯一的 id。
- 不要通过插槽传递命令式的 setter 或 action,除非该组件明确是一个无渲染(renderless)基础组件;应优先使用模型绑定、派发事件和普通 props。图标应使用图标库,而不是原始的内联 SVG 或 Unicode 符号;标签文字要保持语义化,快捷键应从命令元数据中渲染出来,而不是把快捷键文本硬编码进标签里。
