RxJS Next 仓库 AI 贡献者完全指南:从平台化架构到 Symbol 扩展与验证纪律
【免费下载链接】rxjsA reactive programming library for JavaScript项目地址: https://gitcode.com/gh_mirrors/rx/rxjs
RxJS 正在经历一次根本性的代际转换:从"Observable 的所有者"转变为"web 平台 Observable 的扩展库",这一新一代分支的代号为RxJS Next,对外发布版本为RxJS 9。本指南以仓库根目录的 AGENTS.md 为骨架,结合 docs/rxjs-next 文档集与packages/*源码实现,面向维护者、贡献者以及参与该仓库的 AI 编码代理,完整讲解进入该仓库前必须遵守的阅读顺序、工作规则、计划纪律、架构变更纪律与验证要求。读完本文,你将理解为什么 RxJS Next 不是 RxJS 7 的增量实现,以及如何在 Symbol 扩展、AbortSignal 取消、polyfill 边界等关键约束下安全地提交代码。
一、仓库定位:这不是 RxJS 7 的延续
AGENTS.md开篇即明确了一个重要前提:当前分支是基于平台的新一代 RxJS 的基础,工作名为RxJS Next,对外发布名大概率是RxJS 9,并且"它不是 RxJS 7 的增量实现"。
这意味着该仓库的所有工作都应被当作**探索性实现(exploratory implementation)**对待,而不是已经定稿的架构。AGENTS.md要求贡献者始终区分三类状态:当前行为(current behavior)、已被接受的演进方向(accepted direction)与提案(proposals)。这一区分贯穿于 docs/rxjs-next/ARCHITECTURE.md 的"Current component inventory"表格——表中每个组件都同时列有"当前职责""目标职责"与"当前缺口"三列,例如packages/observable-polyfill目前是"条件性提供平台形态 Observable 的兜底实现",目标则是"可独立发布的、符合规范的兜底包"。
从 docs/rxjs-next/PROJECT_CHARTER.md 可以看到该代际转换的核心动机:
- 让 RxJS 值可以直接参与浏览器及 web 兼容 API,无需再通过适配器定义一套竞争性的基类;
- 当运行时已经提供 Observable 时,应用不必为第二个基础 Observable 实现买单;
- RxJS 可以聚焦于高价值库层:操作符、组合、迁移、测试与开发者工具。
由于被取消的 RxJS 8 版本线已经存在,复用 8 会造成混淆,因此新一代直接以 RxJS 9 发布,首个计划预发布版本为9.0.0-beta.0(见 docs/rxjs-next/DECISIONS.md 的 D-007)。
二、强制阅读顺序:六个文档的地图
AGENTS.md规定,在做出任何修改之前,必须按顺序阅读以下文档:
- docs/rxjs-next/PROJECT_CHARTER.md——项目章程:为什么做这件事、目标、非目标、质量属性与成功标准;
- docs/rxjs-next/ARCHITECTURE.md——目标架构、平台 Observable 生命周期、Symbol 扩展模型、包与导入架构、测试架构;
- docs/rxjs-next/DECISIONS.md——持久决策日志(ADR 风格),记录 D-001 以来的每一项被接受、被提案、被推迟与被取代的决策;
- docs/rxjs-next/PROJECT_PLAN.md——活跃执行队列,唯一的
NEXT标记所在; - docs/rxjs-next/OPEN_QUESTIONS.md——未决问题清单;
- docs/rxjs-next/COMPATIBILITY.md——在变更 API 或行为时必须阅读的迁移与行为证据策略。
这套阅读顺序本身就是一种纪律:先理解"为什么"(章程)、再理解"是什么"(架构)、再理解"曾决定过什么"(决策日志)、再理解"现在做什么"(计划)、再理解"还有什么没定"(开放问题),最后才动手碰 API 或行为。决策日志是这套体系的中枢——例如 D-001 决定"以当前 realm 的 web 平台 Observable 为基础类型",D-002 决定"仅当平台原语缺失时才使用 polyfill",D-003 决定"用导出的 Symbol 键寻址 RxJS 扩展"。任何与这些决策冲突的改动都需要先重开决策,而不是悄悄绕过。
三、核心工作规则之一:平台优先与 polyfill 边界
AGENTS.md第一条硬性规则是:当 web 平台的Observable存在时,必须使用它;polyfill 绝不能取代符合规范的本地实现。
这条规则在源码中的落地方式是"每个公共rxjs入口在触碰Observable之前,都先求值条件初始化器"。packages/rxjs/src/index.ts 的第一行就是import '@rxjs/observable-polyfill';,随后才导出AsyncSubject、ColdObservable、Subject、Notification等非操作符核心值。也就是说,根入口只安装共享的构造内核,不会安装完整的操作符目录;某个操作符子路径(如rxjs/map)则只安装它自己的精确 Symbol 能力以及所需的内核依赖。
平台优先的另一个具体表现是字符串命名方法的所有权:packages/observable-polyfill/src/index.ts 中的ObservableImpl提供map、filter、take、flatMap、switchMap等"平台形态"的字符串方法,以及forEach、first、last等返回 Promise 的方法,还有EventTarget.prototype.when集成。这些方法属于平台契约:本地实现存在时由本地实现拥有,兜底实现仅在平台 Observable 本身缺失时补充。RxJS 不得为了添加库行为而替换平台的字符串命名方法。
四、核心工作规则之二:Symbol 扩展与双契约共存
AGENTS.md规定:RxJS 行为必须通过导出的 Symbol挂载到平台构造函数或其原型上,不得在平台Observable表面上添加字符串命名的 RxJS 方法。
一个容易忽略的细节是:即使某操作符已经拥有平台的字符串方法(如map和filter),RxJS 也必须同时导出对应的 Symbol。两种形式并存:
observable.map(project); // 平台契约 observablemap; // RxJS 契约这一双契约设计(见 docs/rxjs-next/DECISIONS.md 的 D-003)意味着 Symbol 形式可以委托给平台方法、包装它、或提供额外的重载与行为,但绝不能覆盖字符串方法;任何有意的差异都必须记录并测试。从源码看,packages/rxjs/src/map.ts 正是这一模式的样板:export const map: unique symbol = Symbol('map')创建一个模块所有的精确 Symbol,通过declare global扩充Observable<T>接口,然后Observable.prototype[map] = mapOperator;直接赋值。mapOperator内部通过thiscreate构造派生结果,再用subscribeToSource订阅源并把投影结果转发给订阅者。
这种精确 Symbol 方案的碰撞隔离价值在于:Symbol 的描述只是调试标签,Symbol('scan')与另一个Symbol('scan')是不同的键。字符串命名属性是共享的全局领地——这正是 RxJS 5 的rxjs/add/operator/*修补模型容易被加载顺序和意外替换破坏的根源;而 Symbol 键则只有持有了那个精确 Symbol 值的代码才能读取或替换,从而把每个导出 Symbol 的权限边界收窄到"有意协作"的范围内。
不过AGENTS.md同时给出两条红线:
- 不要引入
Symbol.for键,除非有被接受的命名空间与重复安装决策。Symbol.for使用共享全局注册表,任何知道 key 的代码都能取回同一个 Symbol 并写入同一槽位,这会刻意削弱碰撞隔离。 - 唯一的例外是内部构造协议:packages/rxjs/src/create.ts 使用
Symbol.for('rxjs.kernel.create.v1')导出create。理由是兼容的重复副本之间需要就"派生 Observable 如何构造"达成一致——ABI 版本属于协议而非包版本。installCreate只允许已存在的可调用实现继续存活,若槽位被非可调用值占用则抛出TypeError,且该全局键不会让任何公共操作符 Symbol 变成全局可恢复的。
五、核心工作规则之三:分层、取消与生命周期
AGENTS.md反复强调分层:平台语义与 RxJS 7 兼容语义必须放在不同的架构层中,尤其不得让平台Observable悄悄表现得像一个 RxJS 7 冷 Observable。
背后的原因是平台 Observable 的生命周期模型(详见 docs/rxjs-next/ARCHITECTURE.md 的"Platform Observable lifecycle"小节):活动规范把每个 Observable 与一个活动Subscriber的弱引用关联起来——第一个观察者订阅时启动生产者工作,后续观察者加入该活动订阅者,某个观察者中止时被移除,最后一个观察者离开时订阅者关闭并执行生产者清理,之后的观察者可启动新的生产者订阅。这是一个共享、引用计数(ref-counted)的模型。
因此AGENTS.md要求以AbortSignal和平台Subscriber生命周期作为平台层取消的根基。从 packages/observable-polyfill/src/index.ts 可以看到具体实现:活动订阅者持有观察者Set、内部AbortController,观察者集合为空时关闭引用计数,关闭状态先中止订阅者信号再按逆插入顺序执行清理回调;由于 JavaScript 不暴露 DOM 标准的 abort 算法钩子,兜底包只对注册了 Observable 工作的信号桥接AbortController.prototype.abort,其余情况委托给捕获的平台方法。
需要特别注意的是"冷/热"术语的用法。AGENTS.md与 docs/rxjs-next/COMPATIBILITY.md 一致强调:不要用一个固定的"热"或"冷"标签概括平台 Observable 的生命周期。冷(cold)指订阅创建生产者,热(hot)指订阅前生产者已存在;平台 Observable 的首次订阅创建活动生产者、并发订阅加入、引用计数归零后的订阅再创建新生产者——分享、多播、重放与引用计数都是独立属性。已实例化的Subject是热的,因为观察者订阅前生产者就存在。如果确实需要"每次直接订阅创建一个生产者"的语义,应使用显式的ColdObservable(见 packages/rxjs/src/cold-observable.ts 与 packages/rxjs/src/per-subscription-subject-base.ts),但这类类型是有意的 Next API,不会重新定义平台 Observable。
六、核心工作规则之四:测试分类与兼容性边界
AGENTS.md明确警告:不要假设旧的 RxJS 7 测试能原样通过,每个迁移的测试都必须按照 docs/rxjs-next/COMPATIBILITY.md 中的兼容性策略分类。
兼容性策略的核心立场是:RxJS Next 复用 RxJS 7 测试中仍有意义的行为知识,但不提供模拟 RxJS 7 导入、Subscription、pipeable 操作符、调度器或废弃别名的独立运行时包。ColdObservable、Subjects、Symbol 键控的pipe可以留在rxjs中作为有意的 Next API,但通过旧测试只证明"被代表的行为",不构成源码、类型、导入或生命周期兼容声明。
为此 docs/rxjs-next/COMPATIBILITY.md 维护了一张语义基线对照表,逐项列出 RxJS 7 基线、RxJS Next 基线及迁移含义,摘录几条最关键的变化:
| 关注点 | RxJS 7 基线 | RxJS Next 基线 | 迁移含义 |
|---|---|---|---|
| 生产者执行 | 普通冷 Observable 每次订阅创建独立工作 | 平台 Observable 共享一个活动生产者;ColdObservable是显式的独立 Next 类型 | 审计重复订阅并显式选择目标生命周期 |
| 订阅返回值 | subscribe()返回Subscription | 平台subscribe()返回undefined | 用AbortController/AbortSignal所有权替代捕获的订阅 |
| 取消 | Subscription.unsubscribe()与清理链 | AbortSignal、Subscriber.signal与引用计数关闭 | 审查所有权、中止原因与最后观察者行为 |
| void 通知 | Subscriber<void>.next()可省略值 | 平台Subscriber.next始终要求一个参数,void 形式为next(undefined) | 平台 Subscriber 的 void 信号改写为显式undefined |
| 清理注册 | 生产者可返回清理逻辑 | 生产者调用subscriber.addTeardown() | 自定义生产者改用回调注册 |
| 清理顺序 | RxJS 7 聚合语义 | 平台规范按逆插入顺序关闭清理回调 | 对顺序敏感的清理视为语义迁移 |
| 调度 | 调度器参数与类影响大量 API | 宿主 API 与@rxjs/test;无公共调度器抽象 | 移除调度器参数并审查时序敏感代码 |
| 输入转换 | 广泛的ObservableInput生态 | 平台Observable.from的转换顺序与类别 | 审计自定义 subscribable 与旧互操作 |
这条规则还引出一条硬约束:不要为了让测试通过而在平台包中复活已移除的 RxJS 7 内部实现。兼容行为必须放在显式的兼容边界之后。被分类为compatibility-only的旧输入(如只暴露小写subscribe方法的可订阅对象)保留为可执行的迁移证据,在当前表面拒绝任意 subscribable 时显式失败,而不是悄悄通过。
七、核心工作规则之五:记录上游修订与保留历史
对于按"活的 Observable 规范"或 Web Platform Tests 实现的代码,AGENTS.md要求记录所使用的精确上游修订版本。这一点在架构中有非常严格的落地:
- 书面规范参考是 WICG/observable 提交
d74bace7cf80200a01c81cfe20961e29ac7fa3d8的spec.bs,用于理解规则与诊断失败; - 可执行成功门是 web-platform-tests/wpt 提交
6a009d73f0d315941b90cac13a9523a2a08c631b,仓库逐字节内置了来自dom/observable/tentative/的 29 个测试文件与 8 个衍生支持文件(许可证、GC 助手、两个 IDL、四个 WPT 框架/解析脚本),共 37 个文件保持与上游逐字节一致,来源记录每个 Git blob 与 SHA-256; pnpm run test:wpt是严格的符合性门:只有当官方浏览器 WPT 运行器完成、每个期望 URL 恰好运行一次、每个 realm 证明精确的 RxJS 身份、报告完整、每个上游测试与子测试都通过时才成功。当前基线的记录结果是 52/52 URL、525/525 上游子测试与 52/52 身份证明通过。
同时,AGENTS.md要求保留 RxJS 7 的历史:旧实现仍是行为测试、迁移知识与兼容性需求的重要来源。仓库中packages/rxjs/test/ported目录下的 147 个冷模式与 147 个平台模式 Vitest 文件,正是把 2,338 条注册(由 2,201 个物理声明展开)物化为普通可执行测试的成果,详见 docs/rxjs-next/RXJS_7_MARBLE_TEST_PORT_NOTES.md。
八、项目计划纪律:唯一的 NEXT
docs/rxjs-next/PROJECT_PLAN.md是活跃执行队列。docs/rxjs-next/PROJECT_PLAN.md 开篇即描述了从 Phase 0(基础与架构安全护栏)到 Phase 6(发布矩阵、包本地文档、beta 审批)的完整进展。AGENTS.md给出的操作纪律是:
- 只处理标记为
NEXT的单个计划项,除非用户明确改变优先级或存在小的前置依赖; - 始终保持恰好一个
NEXT项; - 完成计划项时更新完成证据,并追加一条简短的会话日志。
计划状态协议为DONE(完成并记录证据)、NEXT(唯一活动步骤)、PLANNED(已排序但未激活)、BLOCKED(缺少命名决策或外部变化)、DEFERRED(接受但有意不安排)。队列关闭时不应再有NEXT项。
从 docs/rxjs-next/PROJECT_PLAN.md 的完成证据可以看出这套纪律的实际形态:每个计划项都有"完成标准(completion bar)"与"完成证据(completion evidence)"两节。例如 P0.5 的完成证据记录了对 Web IDL 必填参数检查的恢复(缺失参数即使在关闭后也会抛出,显式undefined则正常投递)、D-045 取代 D-042、以及next(undefined)对Subscriber<void>的要求;P6.2 的基线记录显示四包列车(polyfill、rxjs、test、migrate)构建、声明消费者、ESM 导入、require(esm)桥接等全部通过,聚焦测试为 51 polyfill + 750 RxJS + 75 测试包 + 166 迁移测试。
九、架构变更纪律:文档随代码一起变更
AGENTS.md规定,当代码改动触及以下任何一项时,必须在同一次变更中更新文档:
- 包或导入边界;
- 本地实现与 polyfill 的选择;
- Symbol 身份或补丁安装;
- 订阅共享、引用计数、取消或清理;
- 子类或 realm 行为;
- 兼容性保证;
- 公共导出;
- 测试或符合性门。
持久决策应记录到 docs/rxjs-next/DECISIONS.md;未决问题根据证据变化移入或移出 docs/rxjs-next/OPEN_QUESTIONS.md。这套"代码-文档同步"纪律在架构层被形式化为 15 条目标架构不变量(docs/rxjs-next/ARCHITECTURE.md 的"Target architecture invariants"),其中包括:导入兜底实现永不替换已有 Observable 或EventTarget.when;本地与兜底测试模式运行同一套平台层操作符套件;不向平台Observable添加 RxJS 专属字符串命名属性;取消通过平台信号传播,最后一个观察者离开后不留活动上游工作;每个 WPT 结果都要证明执行 realm 中的精确 RxJS bundle 身份,期望元数据不能豁免该证明。
此外,迁移工具永远不得推断生命周期意图:迁移必须从已审查的契约清单开始、在各已安装的 harness 适配器间使用同一个规范 Skill 摘要、并通过适用的机械与显式限定的代理结果门。这一原则的完整产品设计见 packages/migrate/docs/MIGRATION_TOOLING_DESIGN.md。
十、验证纪律:最窄测试与诚实记录
AGENTS.md对验证的要求是:运行最窄的相关测试与构建/类型检查,诚实记录失败,不把通过的单元测试当作平台符合性的证明;当前已知基线记录在 docs/rxjs-next/ARCHITECTURE.md 中。
这一点在架构文档里有很多值得引用的细节:
- 严格的
pnpm run test:wpt与显式命名的诊断命令pnpm run test:wpt:baseline是分离的;基线诊断保留完整性(每个 URL 恰好运行一次)与身份(精确 RxJS 身份证明)门,但不是符合性声明,且只有在连续三次完整运行一致后才被接受,意外失败与意外通过都会拒绝基线; - RxJS 单元测试门
test:unit把每个移植案例注册为普通测试:转换程序失败、缺失 API、不支持的 harness 依赖、源跳过案例或精确重复都会导致命令失败,而不是被隔离或用期望失败包装器反转; - 架构文档的"Build and test baseline"一节记录了多次验证快照,例如 P4.I1 验证:106 个文件 750 个测试通过、全部 97 个精确公共 Symbol 在其声明的静态/实例目标上安装、
import 'rxjs/map'打包体积从 15,726 降到 14,447 minified 字节(-8.1%),而import 'rxjs'根入口打包前后逐字节一致——这正好印证了"根入口不安装完整操作符目录"的导入架构。
对于 AI 编码代理而言,这套验证纪律意味着:修改后应优先运行该能力对应的聚焦测试(如pnpm --filter rxjs test的子集)与类型检查,而不是只跑全量套件;遇到失败应如实记录并对照兼容性策略分类,而不是通过改写测试来掩盖。
十一、总结:进入 RxJS Next 仓库的工作流
综合AGENTS.md的全部内容,一个合规的贡献/代理工作流可以归纳为:
- 按序阅读六份必读文档:章程 → 架构 → 决策日志 → 项目计划 → 开放问题 →(变更 API 或行为时)兼容性策略;
- 确认单一
NEXT计划项并只处理它,完成后记录证据、追加会话日志、移动NEXT标记; - 遵守平台优先与 Symbol 扩展规则:不替换本地
Observable与字符串命名方法,不引入未批准的Symbol.for公共键,新扩展按"导出精确 Symbol + 扩充全局接口 + 直接赋值到构造器/原型"的样板实现(参考 packages/rxjs/src/map.ts); - 以
AbortSignal为取消根基,尊重共享、引用计数的平台生命周期,需要每次订阅独立生产者时显式使用ColdObservable; - 按兼容性策略分类每个迁移测试,不复活已移除的 RxJS 7 内部实现,保留旧行为作为可执行证据;
- 触及架构关键面时同步更新文档与决策日志;
- 运行最窄的相关测试与类型检查并诚实记录,必要时对照架构文档中的基线表判断是否符合预期。
这套规则体系的最终目标是让"行为被证明、而非被暗示"(behavior is proved, not implied)——这正是 RxJS Next 从 RxJS 7 走向平台化新世代时,贡献者与 AI 工具之间得以安全协作的契约基础。
【免费下载链接】rxjsA reactive programming library for JavaScript项目地址: https://gitcode.com/gh_mirrors/rx/rxjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考