
从 2019 OSS Day 看 Relay 开源演进对象身份、SSR、Mutation 语义与编译器配置化【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay一、会议背景与纪要定位meta/meeting-notes/2019-01-25-oss-sync.md记录了 Relay 团队首次举行的 OSS Day开源工作日会议纪要。这场会议的核心内容包括与社区协作者会面、跟进上一轮会议遗留的内部讨论以及推进 Relay 2.0.0 版本的发布工作。核心团队将大部分时间投入到开源事务上包括与多位外部贡献者讨论路线图和社区关注的重要问题、对 issue 和 PR 进行分流处理triaging以及发布新版本。从这份纪要中我们可以提炼出 2019 年上半年 Relay 开源工作的关键议题更灵活的对象身份识别、服务端渲染支持、mutation 结果重叠问题、降低 Relay 使用门槛、本地状态探索、编译器配置化、以及冻结 JSON 标量的修复。下文将逐项深入剖析并结合当前仓库的源码实现进行验证。二、2019 H1 路线图React Suspense 兼容 API 回归开源会议首先回顾了 issue #2554 评论区 中讨论的 2019 年 H1 路线图。纪要的核心结论是新的 React Suspense 兼容 API 将尽快移回开源版本OSS。这一决策的历史背景是当时 Relay 团队正在推进基于 React Suspense 的下一代数据获取 API也就是后来的useLazyLoadQuery、usePreloadedQuery等 Relay Hooks。从当前仓库的代码可以看到这些 API 已经在 react-relay/relay-hooks 中全面落地例如useLazyLoadQuery.js、usePreloadedQuery.js、useQueryLoader.js等并提供了配套的.d.ts类型声明。这印证了当时路线图的最终成果。三、更灵活的对象身份识别Object Identity3.1 问题背景Relay 的对象身份规范Object Identification Spec要求实体必须通过Node接口的id字段来唯一标识。具体规范可参考 website/spec/ObjectIdentification.md。社区成员 Justin来自 Artsy提出理想情况下应用即使不完全遵循 Relay 的对象身份规范也应该能够使用 Relay。主要存在两类不遵循规范的应用使用全局唯一标识符但字段名不是id例如使用__id完全不使用全局唯一标识符可能使用类型特定的主键。针对第一种情况PR #2249 提出了一个解决方案从 schema 的Node接口推断全局 id 字段的名称。例如如果 schema 的Node接口将 id 字段命名为__idRelay 就应该使用__id字段作为标识符。3.2 核心团队的顾虑核心团队对引入灵活性持谨慎态度主要顾虑是它可能给那些确实遵循规范的应用带来运行时开销。使用固定名称查找标识符字段比额外做一次查找或调用一个函数更高效。但这也意味着 Artsy 需要维护一个 Relay 的分支将查找id改为查找__id。更普遍地说理想情况是支持那些不遵循身份规范的应用。另一个使用场景是即使应用大部分遵循规范也可能存在个别不遵循的类型例如某些类型的id字段并非全局唯一。3.3 提出的解决方案选项会议讨论了两个主要方向选项一分叉Fork身份解析逻辑。通过模块 shimming、自定义构建等方式为 Facebook 内部和 OSS 提供不同的身份解析逻辑。OSS 版本调用用户提供的函数来解析对象身份默认行为与当前一致。OSS 用户通常更能容忍小的性能折中来换取灵活性因此这个方案是合理的。选项二为LinkedField增加变体。该变体要么指定 id 字段的名称要么指示在运行时调用某个函数。符合 Relay 对象身份规范的类型继续使用标准的LinkedField低开销只有需要定制的类型才承担额外开销。这个方案更灵活因为它既保留了现有场景的性能又启用了更多灵活性。下一步行动josephsavona跟进该 PR。3.4 源码验证最终落地为可配置的getDataID从当前仓库源码来看这一议题的最终解决方案落在了可配置的getDataID函数上相当于选项一的演进版本。核心实现在packages/relay-runtime/store/defaultGetDataID.js 定义了默认行为对普通类型直接返回fieldValue.id对viewer类型在id缺失时返回固定的VIEWER_IDpackages/relay-runtime/store/RelayStoreTypes.js 中定义了GetDataID的类型签名(fieldValue: {...}, typeName: string) DataIDpackages/relay-runtime/store/NormalizationEngine.js 在规范化引擎中使用config.getDataID ?? defaultGetDataID即用户不配置时自动回退到默认实现packages/relay-runtime/store/OperationExecutor.js 通过构造参数注入getDataID并沿整个执行链路第 791、910、995、1462 行传递。也就是说现代 Relay 允许应用通过环境配置覆盖getDataID自定义对象身份的解析逻辑——这正是当年讨论中OSS 版本调用用户提供的函数来解析对象身份的直接产物。同时defaultGetDataID作为默认实现保证了不配置时的低开销兼顾了讨论中提到的性能诉求。四、服务端渲染Server-side Rendering经验与挑战社区成员 Rob Richard 分享了用 Relay 实现服务端渲染的经验并提出了几个遇到的问题。4.1 实践路线Rob 发现更灵活的做法是不直接使用QueryRenderer而是手动获取数据设置 context渲染将数据序列化到客户端用于初始化seed环境在客户端同样手动设置 context 并渲染。这种方式的限制是服务端不会预取嵌套的QueryRenderer不过由于 fragment 组合fragment composition的特性初始加载时通常也不需要嵌套渲染器。4.2 具体问题与进展内联 requireinline requires性能问题Relay 使用内联 require 在 Node.js 上较慢纪要说截至撰写时OSS 构建已禁用内联 require。React context 不稳定 APIRelay 当时使用了一个不稳定的 API 读取 React context 且不工作已修复为使用另一个当前可用的不稳定 API。Relay 团队会在有最终 API 后再更新。defer支持对defer的内置支持将解决嵌套查询渲染器的情况。Rob 正在使用 Apollo server 实验性的defer配合自定义 network layer 来修补增量 payload。requestCache相关 bugQueryRenderer的共享requestCache存在相关问题。下一步行动jstejada负责排查。4.3 源码验证requestCache的实现从当前仓库源码看requestCache是 packages/relay-runtime/query/fetchQueryInternal.js 中按环境维护的请求缓存第 37 行通过WEAKMAP_SUPPORTED判断是否用 WeakMap 为每个环境建立requestCachesByEnvironment第 127-160 行展示了典型的缓存读写流程先getRequestCache(environment)获取缓存再按RequestIdentifier查缓存命中则复用未命中则发起请求并在.finally()中清理第 182-210 行展示了RequestCacheEntry的复用逻辑getObservableForCachedRequest。这段源码印证了纪要中共享 requestCache的设计——多个QueryRenderer共享同一环境内的请求缓存以去重而当时正是这个共享缓存的行为存在 bug需要跟进修复。4.4 后续演进纪要提到即将推出的新 API / 变更将使使用 QueryRenderer 从缓存中的数据同步渲染、必要时手动设置 context 变得更加容易。结合 2019 H1 路线图Suspense 兼容 API 回归 OSS这正是后来 Relay Hooks 体系中usePreloadedQuery、loadQuery等 API 提供的服务端渲染能力。当前仓库 packages/react-relay/relay-hooks 与 packages/relay-runtime/query/fetchQuery.js 已经承载了这套能力。五、Mutation 结果重叠Mutation Overlap问题5.1 问题与官方推荐 workaroundRob Richard 指出不同的 mutation 结果在 store 中可能发生重叠。官方推荐的 workaround 是在 mutation 变量中包含一个用于破坏缓存的caching-breaking值例如将clientMutationId参数设置为自增的值。这一问题的本质是如果两个 mutation 写入的数据在 store 中共享了相同的记录和路径后一个 mutation 的响应可能覆盖前一个的结果或者触发意外的订阅通知。5.2 解决 PR 与讨论结论针对此问题的 PR #2349 提出为每个 mutation 设置一个新的唯一 root id。核心团队对这一方向表示支持。下一步行动josephsavona跟进该 PR。从设计哲学上看这个方案与为每个请求生成唯一标识的思路一脉相承。当前仓库中packages/relay-runtime/store/RelayModernOperationDescriptor.js 负责生成 operation descriptor包含request、fragment、root等标识而clientMutationId作为缓存破坏变量的实践在今天依然是 Relay 处理并发 mutation 的常用手段。六、让 Relay 更容易上手Sibelius 反馈经常听到Relay 比 Apollo 难用的说法。会议深入拆解了到底哪里难。6.1 实际难点分析认知误区Apollo 和 Relay 都支持类似的模式——声明一个包含所有字段的单一 queryApollo 风格vs 将 fragment 与组件放在一起colocation。但人们往往没意识到使用 Relay 时可以先用简单 query等应用规模增长后再逐步引入 colocated fragments。价值传递不足人们常常不理解 colocation 和数据掩蔽data-masking的价值。刚起步时这不是问题但随着应用扩展其重要性会凸显。文档与示例文档和示例可以帮助阐明fragment / colocation / masking 是高级概念一开始不必使用。6.2 其他难点编译器需要配置相关的可配置化 PR 在后面讨论见第八节。更好的文档已在路线图规划中。示例欢迎社区帮助。能否使用 babel-macros 运行编译器这是 create-react-app 支持 Relay 的方式。对新人的整体门槛如果你是不用 GraphQL 的 React 开发者需要配置的东西很多schema、server 库、client 库等。下一步行动Relay 团队思考如何在官网/文档中把价值主张value proposition和目标受众intended audience表达得更清晰。6.3 源码验证文档与上手路径的现状从当前仓库看这一议题的后续成果体现在多个方面website/docs/getting-started 提供了一套分步入门教程website/docs/guided-tour 将 fragment、colocation、data-masking 等概念组织成专题向导packages/babel-plugin-relay 中保留了 Babel 插件形态的编译入口BabelPluginRelay.js、compileGraphQLTag.js这正是当时讨论的 babel-macros / babel 插件思路在开源版中的延续create-react-app 通过它支持 Relay。七、本地状态Local State的探索Sibelius 询问 Relay 对本地状态local state的规划。核心团队回应正在与 React 团队及其他同事协作探索目前还没有具体方案但在思考 React Suspense 和 Concurrent mode 背景下复杂本地状态应该如何工作。从当前仓库看这一探索最终演化为客户端 schema 扩展client schema extensions与客户端字段client fields见 packages/relay-test-utils-internal/schema-extensions客户端 query / 本地 query 支持见 react-relay/relay-hooks/useClientQuery.jscommitLocalUpdate等本地更新 API见 packages/relay-runtime/mutations/commitLocalUpdate.js。八、Relay 编译器可配置化Compiler Optionsissue #2518 提出了一种思路像 Babel、Metro 等 JS 工具一样通过配置文件来配置编译器。讨论结论Relay 团队表示支持cosmiconfig库看起来不错社区若能提供详细方案文档将不胜感激见 issue 评论区。8.1 源码验证relay.config.json的落地这一议题在开源版的最终成果就是今天每个 Relay 项目根目录下的relay.config.json。当前仓库中的相关证据compiler/crates/relay-config 是完整的配置解析 crate包含src/下多个模块如connection_interface.rs、defer_stream_interface.rs、js_module_format.rs等通过schemars生成 JSON Schemacompiler/crates/relay-compiler/relay-compiler-config-schema.json 是编译器配置的 JSON Schema 定义供编辑器与工具链校验配置compiler/test-project/relay.config.json 是真实的配置示例编译器默认会在项目根目录自动查找relay.config.json支持src、schema、language、artifactDirectory等关键配置项。当年讨论的像 Babel、Metro 一样通过配置文件配置编译器的设想如今已通过relay.config.json成为 Relay 编译器compiler/crates/relay-compiler的标准工作方式。九、冻结 JSON 标量修复Frozen JSON Scalars9.1 问题背景Rob 提到 PR #2193 处于开放状态。这个问题影响一些使用复杂标量complex scalars如 JSON 标量的 OSS 用户。9.2 修复方案与讨论修复方式在recycleNodesInto中停止在 DEV 模式下修改被冻结frozen的对象。顾虑这会在技术上造成 DEV 与生产模式之间的可观察行为差异observable behavior change而历史上团队一直尽量避免这种差异。权衡但这只发生在自定义标量数据上不涉及标准 Relay 数据。结论决定推进该修复以解除社区阻碍因为复杂标量本身就是 opt-in 的。另外需要提醒如果使用复杂标量其对象身份的稳定性并不能得到保证。该 PR 已合入。9.3 源码验证recycleNodesInto的冻结保护当前仓库中packages/relay-runtime/util/recycleNodesInto.js 的注释明确写道Recycles subtrees fromprevDataby replacing equal subtrees innextData.Does not mutate a frozen subtree.回收prevData中相等的子树以替换到nextData不会修改被冻结的子树。具体实现细节第 44 行与第 64 行分别对数组和对象计算canMutateNext canMutate !Object.isFrozen(nextArray / nextObject)即先检查目标是否被冻结冻结则放弃原地写入第 53-55 行、73-76 行只有在canMutateNext为 true 时才写回nextArray[ii]/nextObject[key]packages/relay-runtime/util/shallowFreeze.js 的注释同样说明其用途Shallow freeze to prevent Relay from mutating the value in recycleNodesInto or deepFreezing the value浅冻结以防止 Relay 在recycleNodesInto中修改该值。这正是 2019 年 1 月那次讨论的修复在今日源码中的最终形态在回收子树时尊重冻结状态避免 DEV 模式下修改用户提供的自定义标量对象。十、从纪要看 Relay 的开源协作模式这份纪要在方法论层面也很有价值它体现了 Relay 团队当时建立的开源协作节奏定期 OSS Day核心团队集中一整天处理开源事务包括与社区贡献者会面社区→内部的双向通道外部贡献者如 Artsy 的 Justin、Rob Richard、Sibelius提出真实生产环境中的痛点核心团队评估取舍后给出方向明确的下一步行动每个议题都标注了负责人如josephsavona、jstejada确保讨论不落空性能与灵活性的权衡反复出现的主题是灵活性的代价无论是对象身份额外查找 vs 固定字段、冻结保护DEV/生产行为差异还是编译器配置配置解析开销。这些议题与当前仓库源码的对应关系可以概括为下表纪要议题当前仓库中的落地点关键证据对象身份灵活性可配置getDataIDdefaultGetDataID.js、NormalizationEngine.js服务端渲染requestCache按环境缓存、Relay HooksfetchQueryInternal.js、relay-hooksMutation 重叠operation descriptor /clientMutationId实践RelayModernOperationDescriptor.js降低上手门槛分步入门文档、Babel 插件编译website/docs/getting-started、packages/babel-plugin-relay本地状态client schema / client query /commitLocalUpdateuseClientQuery.js、commitLocalUpdate.js编译器配置化relay.config.json JSON Schemarelay-config、relay-compiler-config-schema.json冻结 JSON 标量recycleNodesInto尊重冻结状态recycleNodesInto.js、shallowFreeze.js结语2019-01-25-oss-sync.md这份纪要虽然只有一页却浓缩了 Relay 从Facebook 内部框架走向成熟开源框架的关键转折期决策。会议中讨论的每一个议题——对象身份、SSR、mutation 语义、开发者体验、编译器配置、复杂标量——几乎都能在当前仓库的源码中找到直接或间接的落点。对于研究 Relay 演进历史、理解其设计取舍尤其是性能 vs 灵活性这一主线的读者来说这份纪要与仓库源码对照阅读是理解 Relay 架构决策的最佳路径。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考