尧图精选

回顾 Relay 新编译器关键演进:从 2017 年初团队同步记录看部署扩展、持久化查询与订阅支持

🕒 发布时间:2026/9/20 9:52:06 📁 来源:尧图网络
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载2017 年 1 月 17 日Relay 团队在假期后召开了一次轻量级同步会议记录于仓库 meta/meeting-notes/2017-01-17-team-sync.md。这次会议表面上是团队成员的工作进展汇报实际上勾勒出了 Relay「新 core」当时内部称为 Relay 2从实验走向生产的关键节点从 React Native 环境扩展到 www 服务器基础设施、构建产物纳入版本控制、自动化 Flow 类型生成、持久化查询换代、iOS 原生预取以及计划中的GraphQLSubscription。阅读本文后你将理解这些历史计划如何沉淀为当前仓库中可运行的编译器配置、运行时 API 与源码实现并能在自己的项目中复现对应能力。会议背景假期后的轻量同步主题高度集中这份同步记录延续了 Relay 团队自 2016 年 2 月起公开会议笔记的传统见 meta/meeting-notes/README.md。与早期侧重于产品形态的笔记不同这次会议是一场工程内部同步参会者包括 yuzhi、kassens、wincent、JenniferWang 与轮岗工程师 AGS-讨论焦点完全落在「新 core」的落地推进上。所谓「新 core」可以从前一份 2016-12-09-update.md 中得到官方背景过去数月团队一直在构建一个实验性的新内核Relay 2其核心假设是「更静态的 core 能在移动设备上带来性能提升」并且已经通过产品集成验证了这一方向。该文档还预告了迁移路径旧的RelayContainer、RelayRenderer等 API 将被更精简的新 API 取代产品完全迁移后可切换开关启用新 core解锁性能优化与**持久化查询persisted queries**等新特性。把两份文档放在一起2017-01-17 的每条进展都能对应到新 core 的不同拼图。以下逐条结合当前仓库的源码与配置展开。新 core 的部署扩展从 React Native 走向 www 基础设施kassens 的更新是本次会议最核心的内容让新 core 能够部署到 www 基础设施——此前新 core 的全部开发都发生在 React Native 应用语境中。这一转变的意义在于新 core 不再只是移动端的实验而要支撑起 Web 端的大型单体应用。yuzhi 的工作与之呼应与各产品团队会面协调新 core 的 rollout并用新 core 做 raw query 抓取。从当前仓库的形态看这一「跨平台部署」的方向最终落实为与平台无关的编译器 与平台无关的运行时编译器侧整个编译器是纯 Rust 实现compiler/crates 下的relay-compiler、relay-schema、graphql-syntax、graphql-ir等 crate它不关心产物最终跑在 Web 还是 React Native只负责把 GraphQL 文档编译成统一的ConcreteRequest产物运行时侧packages/relay-runtime 与 packages/react-relay 的组件层解耦环境、存储、网络层可在任何宿主环境下组合。wincent 的工作——「探索在新 Relay compiler 与现有 GraphQL 基础设施之间增加代码共享与复用」——也在这份 Rust 编译器里得到印证compiler/crates目录下存在graphql-cli通用 GraphQL 命令行工具、graphql-syntax、graphql-ir、schema等一系列可独立复用的 crate而不是把解析、校验、IR 表示全部堆在单一二进制里。这种 crate 拆分正是「编译基础设施共享化」的直接产物。构建产物纳入版本控制自动冲突解决与重建kassens 提到的另一项工作是「利用工具处理源码控制下的构建产物自动冲突解决与重建等」。这在今天对应 Relay 编译器对产物文件生命周期的管理。以源码为准compiler/crates/relay-compiler/src/build_project/artifact_writer.rs 中的ArtifactWritertrait 定义了完整的产物写入协议should_write(path, content, hash)写入前判断磁盘内容是否已过期write/remove记录新增与删除的产物路径finalize在 flush 时统一执行源码控制操作add/removereset当 watch 循环因源码控制更新如 rebase而重启时丢弃基于旧状态缓存的待处理操作content_matches_last_write当 watchman 报告产物文件变更时区分「编译器自己写的」与「外部改动」。ArtifactFileWriter同文件内部维护added/removed列表与可选的SourceControl实现新文件写入时自动登记到added。也就是说产物作为源码提交进版本控制时编译器的冲突解决与增量重建是有明确设计支撑的而不是依赖开发者的手工操作。这与会议记录中「自动冲突解决和重建」的描述完全吻合。自动化 Flow 类型生成同一时期kassens 还在「让 www 中的 Flow 类型生成自动化」。这一点在当前仓库中已是编译器的标准能力compiler/crates/relay-typegencrate 专门负责根据操作与 fragment 生成类型定义其中 compiler/crates/relay-typegen/src/flow.rs 的FlowPrinter实现了 Flow 语言的类型输出同类还有 TypeScript 输出器。类型生成由relay.config.json中的language配置驱动。以仓库中的 compiler/test-project/relay.config.json 为参照典型配置形如{ language: flow, src: ./src, schema: ./schema.graphql, artifactDirectory: ./src/__generated__ }language可选flow或typescript。开启后编译器会为每个 fragment / query 生成对应的*.graphql.js或.ts类型声明产物开发者在编辑器与 CI 中即可获得端到端类型检查——这正是「自动化 Flow 类型生成」在今天的完整形态。仓库中的生成产物示例见 packages/react-relay/flowtests/generated目录。持久化查询换代移除旧支持拥抱新的静态 ID 机制JenniferWang 的更新之一是「做清理工作移除旧的 persisted query 支持」。结合 2016-12-09-update 中「新 core 可解锁持久化查询」的预告这里的「旧」指新 core 之前基于动态查询文本的旧式持久化方案而新 core 的静态结构让「编译期把操作文本替换为 hash ID」成为可能。这一换代如今完整沉淀在仓库中。客户端配置persistConfig官方指南 website/docs/guides/persisted-queries.mdx 说明了配置入口在package.json的relay配置段中声明persistConfig。远端持久化编译期把每个操作 POST 到服务端换取 ID的配置如下scripts: { relay: relay-compiler }, relay: { src: ./src, schema: ./schema.graphql, persistConfig: { url: http://localhost:2999, params: {} } }对照源码 compiler/crates/relay-config/src/project_config.rs 中RemotePersistConfig的定义#[serde(deny_unknown_fields, rename_all camelCase)]完整的远端持久化字段还包括字段类型默认值说明urlstring必填接收 POST 请求的持久化端点 URLparamsmap{}附加的 POST 表单参数主文档始终放在text参数中headersmap{}附加 HTTP 请求头includeSchemaTextbooleanfalse每次请求附带schema_text参数见下文concurrencynumber未限流与服务端建立的并发请求数上限不允许为 0includeQueryTextbooleanfalse持久化产物中是否保留查询文本concurrency的反序列化校验在 project_config.rs 中可见传入0会直接报错Invalid persistConfig.concurrency value避免「0 并发」导致构建悬挂。本地持久化生成operation_id 查询文本映射文件不需要远端服务时可以让编译器把映射表写成本地 JSONrelay: { src: ./src, schema: ./schema.graphql, persistConfig: { file: ./persisted_queries.json, algorithm: MD5 } }algorithm取值来自 project_config.rs 的LocalPersistAlgorithm枚举即MD5默认、SHA1、SHA256。其实现见 compiler/crates/relay-compiler/src/operation_persister/local_persister.rsLocalPersister启动时读取已有文件作为query_map文件不存在则视为首次运行从空表开始对每个操作按所选算法计算 hash随后把新条目写回文件includeQueryText控制产物中是否保留原文。产物形态的变化官方指南给出了开启持久化前后ConcreteRequest产物的对比。未开启时return { kind: Request, operationKind: query, name: TodoItemRefetchQuery, id: null, // NOTE: id 为空 text: query TodoItemRefetchQuery(\n $itemID: ID!\n) {\n node(id: $itemID) {\n ...TodoItem_item_2FOrhs\n }\n}\n\nfragment TodoItem_item_2FOrhs on Todo {\n text\n isComplete\n}\n };开启后return { kind: Request, operationKind: query, name: TodoItemRefetchQuery, id: 3be4abb81fa595e25eb725b2c6a87508, // NOTE: id 现在是查询文本的 md5 hash text: null // NOTE: text 变为 null };客户端请求体从「完整查询文本」缩小为「一个 hash 字符串」这正是持久化查询的两个核心收益节省客户端到服务端的上传字节以及服务端可以对查询做白名单限制、提升安全性。底层的 HTTP 细节persist-querycrate远端持久化的 HTTP 行为由 compiler/crates/persist-query/src/lib.rs 实现细节清晰可验证build_persist_request用 URL-encoded 表单构造请求体先追加调用方传入的params最后追加text即完整文档文本头部列表中content-type: application/x-www-form-urlencoded始终排在第一位dispatch_persist_request发出POST请求并把响应按#[serde(untagged)]枚举解析为两种形态{id: ...}表示成功{error: {message: ...}}表示失败并透传错误消息includeSchemaText开启时服务端无法按名字找到 schema 的应用例如构建期动态组装 schema可通过schema_text参数随每个文档一起发送 schema但注意源码注释明确说明schemaCompact项目的 schema 是二进制格式无法作为文本发送此时必须使用schema或schemaDir。编译器侧把这些请求接入构建管线的地方是 compiler/crates/relay-compiler/src/build_project/persist_operations.rs它对每个操作产物并行调用OperationPersister并用正则识别产物中relayHash与relayRequestID注释完成 ID 替换。RemotePersisterremote_persister.rs在发送 schema 时会把并发请求数默认限制为 16避免每个 in-flight 请求各自携带一份 schema 副本导致内存暴涨。网络层与服务端的配合开启持久化后客户端网络层必须改为按 ID 发请求。官方指南 persisted-queries.mdx 中的网络层改造如下function fetchQuery(operation, variables) { return fetch(/graphql, { method: POST, headers: { content-type: application/json }, body: JSON.stringify({ doc_id: operation.id, // NOTE: 把 md5 hash 传给服务端 // query: operation.text, // 已废弃因为 text 现在是 null variables, }), }).then(response { return response.json(); }); }服务端则负责「由 ID 反查查询文本」可以在部署时把persisted_queries.json同步到服务端universal 应用直接放公共位置前后端分离项目则用「编译时推送」脚本把映射推到服务端仓库或数据库更复杂的「运行时推送」方案则由客户端先发 ID、服务端发现缓存未命中后再向客户端索要全文。指南还给出了基于express-graphql的最小服务端示例使用persistedQueries中间件并指定queryIdKey: doc_id完成映射。与--watch的组合persistConfig可以与relay-compiler --watch同时使用持续生成映射文件。这仅对 universal 应用客户端与服务端同一仓库、本地同时运行有意义且服务端需配置热重载才能感知queryMap.json的变化。原生数据预取从 iOS prefetching 到现代的loadQueryJenniferWang 在会议中报告「landed 了最后一个 diff让 iOS 原生预取native prefetching可用」。预取意味着在组件渲染之前就把数据请求发出去让后续渲染直接消费已就绪的数据。这在今天的 Relay 中对应一套完整 API 族packages/relay-runtime/query/fetchQuery.js在给定环境上抓取查询与变量并对相同的 in-flight 请求去重返回RelayObservable可订阅start/next/error/complete事件packages/react-relay/relay-hooks/loadQuery.js 与usePreloadedQuery在渲染树外发起加载拿到PreloadedQuery后由组件消费useQueryLoaderpackages/react-relay/relay-hooks/useQueryLoader.js管理「query variables」的加载句柄配合事件处理器实现「提前获取、稍后渲染」packages/relay-runtime/query/PreloadableQueryRegistry.js注册可按名字预取的查询支撑按需加载场景。从这些 API 的形态可以推断2017 年初在 iOS 上验证的「原生预取」后来演进为跨平台的、以loadQuery为核心的现代预取模型并成为 Relay hooks 时代的标准数据获取范式。计划中的GraphQLSubscription如今已成型的订阅体系会议记录中 JenniferWang 的「下一个大项目」是给新 core 添加GraphQLSubscription。在当时的语境下这是「待规划」的能力而在当前仓库中订阅体系已经完整落地包含编译器与运行时两侧运行时侧requestSubscriptionpackages/relay-runtime/subscription/requestSubscription.js负责在给定环境上发起订阅并返回DisposableReact 侧的 Hook 封装是 packages/react-relay/relay-hooks/useSubscription.js其源码注释明确提醒若config或requestSubscriptionFn未做 memoize每次渲染都会重新订阅因此传入的配置对象不应内联定义。典型用法import {useSubscription} from react-relay; import {graphql} from react-relay; const subscription graphql subscription TodoSubscription($id: ID!) { todoUpdated(id: $id) { ...TodoItem_item } } ; function useTodoSubscription(id) { useSubscription({ subscription, variables: {id}, onError: error console.error(error), }); }编译器侧订阅操作同样参与完整的编译管线见 compiler/crates/relay-transforms/src/match_/subscription_transform.rs 中的transform_subscriptions它会为订阅生成对应的 reader / normalization 操作与{operation}__subscription命名。推进节奏rollout 策略与团队分工最后会议记录还体现了新 core 落地的方法论kassens「主持了规划下半年的会议明确新 core 的 rollout 策略」yuzhi 负责对外协调与各产品团队会面、内部支持wincent 投入 GraphQL 工具与基础设施共享JenniferWang 在完成 iOS 预取后转向旧代码清理与新能力规划。这是一种典型的「编译器内核 跨产品 rollout 工具链建设」三线并进的推进方式。从结果看会议中提到的方向www 部署、产物版本控制、自动类型生成、持久化查询、预取、订阅如今都能在当前仓库中找到对应的成熟实现Rust 编译器、persistConfig、loadQuery预取 API、useSubscription与subscription_transform。这份轻量的会议记录因此可以当作理解 Relay 现代架构Relay Modern 方向演进脉络的一把钥匙——它记录的正是这些能力从「计划」变为「可运行代码」的起点。结语2017-01-17 的团队同步没有给出任何配置示例或代码片段但它的每一条工作更新都与当前仓库的源码一一对应。如果你正在使用 Relay可以对照本文依次打开 persisted-queries.mdx 配置持久化查询、查看 persist-query/src/lib.rs 理解 POST 协议细节、阅读 loadQuery.js 与 useSubscription.js 掌握预取与订阅的现代用法——这些能力正是八年前那次「轻量同步」中埋下的种子。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Puter Events API 系列puter.events.list() 持久化订阅查询完全指南Puter Events API 系列 puter.events.list 持久化订阅查询完全指南 puter.events.list 是 Puter Eve后端前端云原生Wekan 2017 年度版本演进全记录从 v0.11.1 到 v0.63 的关键功能与安全修复详解Wekan 2017 年度版本演进全记录从 v0.11.1 到 v0.63 的关键功能与安全修复详解 导读 本文基于 Wekan 仓库中的 old CHANG后端前端协同办公5分钟快速上手QHotkey10行代码实现Qt全局热键监听5分钟快速上手QHotkey10行代码实现Qt全局热键监听 QHotkey 是一个专为 Qt 桌面应用设计的全局热键监听库它让程序即使在后台、最小化甚至没有桌面应用跨平台创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →