


维护在浏览器、服务器和原生端都能正常运行的 CSS-in-JS 库
在用 AI 编程助手开发一个遵循标准的 CSS-in-JS 库时使用。它让样式只用标准 CSS,让代码在每个运行时都不出错,按规范先写测试,并规定发布说明、文档和运行真实代码的演示该怎么做。
要使用这个AI规则
## 样式只接受标准 CSS,并让已配置的默认值优先
让编写的样式接口始终与真实的、符合标准的 CSS 保持一致。这样可以让心智模型保持可迁移性,避免 API 演变成一种特立独行的方言。
- 编写的样式中只接受标准的属性名、拼写和取值。绝不发明非标准的别名(例如为仅限平台使用的属性起 kebab-case 命名)。平台专属的样式键应放在明确的“逃生舱”中(内联样式对象,或上游平台支持),而不是放进模板语言里。
- 原生层的职责是将地道的 CSS 映射为等效的平台原语,并在原生运行时跟上之前尽力做 polyfill。它的职责不是扩大可接受的输入语法。
### 配置默认值的优先级
- 按设计,作者配置的属性默认值始终优先于调用方传入的 props。这是刻意为之的约定,不是缺陷。
- 逃生舱是默认值配置的函数形式:它会接收传入的 props,并可以按条件回退(保留调用方的值,或提供一个默认值)。
- 例外情况:如果调用方为某个 prop 显式传入 `undefined`,会抑制该 prop 已配置的默认值。
## 使用项目指定的包管理器和提交写法
- 在每次会话开始时,先阅读当前面向消费者的功能摘要,确保你是基于今天实际支持的接口来工作,而不是凭记忆中的行为。
- 使用项目指定的包管理器及其配套命令,不要替换成别的。
- 不要自行启动开发服务器。如果需要启动,请让维护者来启动。
- 使用约定式提交前缀(`feat|fix|chore|refactor|test|docs|style|perf|build|ci`)。默认使用简短的提交信息:只写标题,或标题加一句简洁的说明。
- 所有文字内容(注释、文档、测试标题、面向用户的字符串)都要保持统一的英文拼写规范。只有在逐字引用规范或标准原文时才保留原始拼写。
## 区分服务器、浏览器和原生环境,不让 DOM 代码进入原生端
发布到多个运行时的代码不能假设只有单一环境。要根据多个独立信号来判断环境,并在入口边界处隔离不兼容的代码。
- 不要把环境检测简化成单一的通用标志位。至少要区分以下几种:构建期的服务器常量(仅在服务器构建中为 true)、运行期的服务器组件条件(有些框架提供的构建版本不带仅客户端的 context API)、以及由 SSR 入口按请求设置的服务器标志位。每个判断都应只依赖能覆盖其所关心场景的最小信号子集。一个常见的陷阱是:仅凭构建期的服务器常量来抑制 DOM 相关操作,会破坏“在 jsdom 下运行服务器构建”的测试路径,因为该路径下 DOM 变更其实仍会发生。应改用运行期/SSR 信号来控制 DOM 相关的判断。
- 原生入口绝不能通过值导入间接引入 DOM/浏览器相关代码。应使用仅类型导入(type-only imports)和带标记的运行期标签(共享的 symbol 注册表),而不是用 `instanceof`。可以用 grep 来验证构建出的原生 bundle 中没有任何 `document.` 引用;部分原生运行时只要出现 `document` 引用,就会在模块求值阶段直接失败。
- 并非在每个运行时都存在的框架 hook(例如在服务器组件中值为 undefined 的 ref hook),必须放在构建期的服务器常量之后,以便被死代码消除。绝不要用运行期的 `typeof hook === 'function'` 检查来做保护,那样会变成一次条件性的 hook 调用。
- 只保留一条规范的输出生成路径。当需要修改输出时,扩展现有的输出生成和解析器/编译器测试套件,而不是新增一条并行路径。
## 用构建期常量去掉无用代码,并用测试锁定入口映射
把打包器能在构建期消除的部分,和只在运行期存在的部分明确区分开,并且要对入口映射保持谨慎、刻意的设计。
- 构建期常量(服务器构建、原生构建、native-web 变体、开发模式)会被替换为字面量,这样打包器就能对无用分支做死代码消除。优先把最外层连续的“仅开发环境”区域包在一个统一的 dev guard 内;不要仅依赖某个警告辅助函数来跳过工作,也绝不要在 dev guard 之外构造消息字符串或警告参数。
- 像 `typeof window !== 'undefined'` 这样的运行期检查是无法被 tree-shaking 掉的。当你需要让某段代码从 bundle 中被真正移除时,应使用构建期常量,而不是运行期检查。
- 入口/子路径映射是静默故障的常见来源。让原生打包器根据平台扩展名来解析对应文件,比使用 `browser` 对象映射更安全——因为有些原生打包器会在所有平台上都套用这个映射,可能会悄无声息地把 web 桥接代码路由到原生环境中。`.js` 兜底文件应当是安全的跨平台桥接层,绝不能是特定平台的引擎实现。要用解析/tree-shake 测试来锁定这些解析结果。
- 当条件式 `exports` 会破坏复合项目(composite projects)中的类型解析时,优先使用 `browser` 字段。要把每一个发布出去的子路径目录都加入包的 `files` 列表中。
## 先写会失败的测试,并用真实负载测量性能优化
- 整个仓库强制执行严格的测试驱动开发。在修改生产代码之前,先新增或扩展一个能体现所需约定、且会失败的测试,然后只实现刚好能让它转绿所需的代码。任何面向用户的行为,如果没有一个“不做改动就会失败”的测试,就不能发布。非功能性的工作也要遵循同样的“红—绿”纪律。
- 对性能优化务必用真实的工作负载做微基准测试,而不是人为构造的最佳情况。只要某个改动会让真实调用方所走的路径变慢,即使它在孤立测试中表现更好,也要撤销。
- 优化目标是降低内存压力,并保持函数的单态性(monomorphic)。
- 任何有规范依据的行为,都要以权威规范当前的编辑草案为准,而不是摘要、参考网站,也不是凭记忆。每次会话都要重新抓取相关章节,将规范性条款逐字引用为注释放在对应测试的上方,并在一个命名清晰的“规范符合性”代码块中为每条规则各写一个测试。如果规范未提及或含糊不清,就在测试旁边记录下这种偏离,并关联对应的跟踪 issue。这适用于所有涉及规范的改动。
## 先把每条规范规则写成测试,再做原生端的 Polyfill
当原生运行时的样式引擎无法理解某个 CSS 特性时,就为它做 polyfill,但在重构内部实现之前,必须先把行为锁定在规范上。
新增或重新审视某个 polyfill 时的流程(同样适用于任何有规范依据的校验代码块):
1. 为本次会话抓取当前的编辑草案。条款编号和措辞会不断变化,所以绝不要把旧的摘录当作权威依据。
2. 将规范中的每一条规则都转化为恰好一个测试,并在其上方逐字引用规范原文,全部放在相关测试套件中一个命名清晰的“规范符合性”代码块内。
3. 先写测试,并记录下当前哪些测试是失败的。
4. 持续实现,直到这些测试全部通过。不要为了迁就实现而修改已经通过的测试;规范才是唯一的事实来源。
5. 运行完整的原生测试套件和主测试套件,检查是否有回归问题。
一旦这个规范代码块全部通过,内部实现(分词、AST、求值器)就可以自由重构;该代码块负责锁定用户可观察到的行为。
- 每当运行时版本发布、支持情况发生变化,或 polyfill 被新增、移除或修改时,都要及时更新面向消费者的原生兼容性清单。
- 对于在原生环境下没有可行变通方案的特性,必须发出一条去重后的警告,附带一个稳定的代码标识,说明是哪个写法出了问题、为什么无法运行,以及具体的替代方案,然后丢弃该声明(回退为 null),而不是输出不合理的结果。
- 变体间的一致性:只要浏览器已经支持某个特性,就原样透传原始 CSS,不做改动;绝不要输出浏览器尚未支持的属性;仅限原生使用的属性提升和枚举扩展,都要放在“仅原生”的构建常量之后。为每个 polyfill 的规范代码块设置一个一致性子测试套件,用于切换 web 变体并锁定其可见输出,并且对任何不适用于某个变体的规范性规则,都要像写偏离说明注释一样严谨地明确豁免。
## 发布说明只写用户能看到的变化,并把每份文档放在合适的位置
让每一份文档的语域和内容都与其目标读者相匹配。
- 面向消费者的文字(发布说明、变更日志条目、PR 描述、公开文档)只陈述用户可观察到的行为,用没读过源码的人也能理解的语言来写。要明确说明支持和不支持的内容,以及该改动本身的边界;链接到兼容性矩阵,而不是罗列一堆无关的差距。不要出现内部 API 名称、实现机制或代码路径;这些内容应放在提交说明正文和贡献者指南里。
- 只描述当前的实际行为。对于从未在任何已发布版本中出现过的状态,避免使用“过去如何”“不再如何”“现在如何”“取代了”这类叙事性表达;对尚未发布的特性所做的修复,应直接并入该特性自身的发布说明中,而不是单独写一条。
- 只为用户可见的改动(修复、新特性、破坏性变更)编写发布说明。内部重构、工具链、测试,以及对同一未发布版本中新引入代码所做的修复,都不需要写。
- 不要手动编辑自动生成的文件(变更日志、自动生成的错误映射表);应通过对应的工具重新生成。
- 面向贡献者的文档中不要出现具体的 AI 服务商名称;使用“一个 AI 编程助手”这类中性表述。
- 不要在 PR 正文、issue 正文或评论中手动换行;单行段落可以自动换行显示,而手动强制换行在移动端和引用视图里会显得错位。提交信息正文可以保留 72 列换行的惯例。
### 文档归放位置
- 贡献者指南用于承载那些跨领域的规则、不变量,以及没有天然归属地、不适合放进源码注释的经验性知识;已经在源码中描述清楚的实现机制不应该放在这里。
- 独立的知识文档用来记录踩过的坑和经验教训,而不是逐步讲解功能实现机制;实现机制以源码为准。
- 源码层面的“为什么”应写在代码旁边的注释/JSDoc 中。不要在生产源码或警告信息中重复引用规范;锚点、规范性引文和理由都应放在对应的锁定测试里。警告信息只应说明是哪个写法出了问题以及具体的替代方案,不要包含其他内容。
## 采用测量过的提速写法,并让演示运行真实代码
在主流 JavaScript 引擎上经过微基准测试验证的模式(应视为初始假设,在具体场景中重新测量):
- 在任何规模下,字符串 `+=` 拼接的性能都优于 `array.push()` + `join()`。
- 使用展开语法 `{...props, extra}` 的性能优于 `Object.assign` 或用 `for..in` 做拷贝。
- `for..in` 的性能优于 `Object.keys()` 加一个循环。
- 把编译好的正则表达式缓存在一个 map 中;用 `indexOf` 做前置检查从而完全跳过工作,会带来更大的性能收益。
- 在紧凑循环中,手动用 `+` 拼接字符串的性能优于模板字符串。
- 直接构造原始的元素对象(使用框架的元素标记)远快于使用框架自带的元素工厂函数。
- `new Array(n)` 会创建带空洞的数组,从而污染类型反馈;应避免为热点数组预先设定大小。
引擎与语言方面的陷阱:
- 赋值给 const 的匿名类表达式上不允许使用 `private` 修饰符。
- 仅类型的星号导入(type-only star import),即便会被类型系统剥离,仍然可能触发打包器的模块解析。
演示与可视化质检的真实性(适用于任何声称要证明某种行为的展示案例):
- 对虚假演示零容忍。每个组件都必须真正走它所声称要展示的那条运行时路径。如果某个特性在某个平台上无法运行,就让对应单元明显地失败(布局错乱、出现警告、字形缺失);这个失败本身就是演示内容。绝不要用模拟规范行为的假实现来替代,也绝不要用硬编码的通过/失败颜色来冒充真实路径被触发。
- 在展示案例中使用不带平台分支的全平台通用组件;针对平台差异的变通方案应放在库里,而不是放在应用里。
- 让“正常”与“损坏”的状态在稍远的距离一眼就能通过明显的宏观差异(布局位移、大面积变色、元素的有无)区分出来,并确保每个组件在浅色和深色模式下都能正确显示。
