在 React 项目中嵌入 Ruru:ruru-components 组件库使用指南与源码解析
2026/9/23 5:47:43 网站建设 项目流程

在 React 项目中嵌入 Ruru:ruru-components 组件库使用指南与源码解析

【免费下载链接】crystal🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal

导读

ruru-components 是 Graphile Crystal Monorepo 中Ruru(Grafast 风格 GraphiQL 发行版)背后的 React 组件库。当你想把一套开箱即用的 GraphQL IDE(含查询编辑、变量编辑、订阅支持、Explain 调试面板、文档浏览与历史记录)直接嵌进自己现有的 React 应用,而不是通过 ruru 独立服务器提供时,ruru-components 就是答案。本文将以 grafast/ruru-components/README.md 为主线,完整还原其最小接入流程与 Monaco workers 配置方案,并结合 源码 深入讲解 RuruProps 配置项、Explain 调试链路、事件流自动刷新与本地存储机制,帮助你真正"会用 + 懂原理"。

ruru-components 是什么

根据 grafast/ruru-components/package.json 的描述,ruru-components 是 "Grafast-flavoured GraphiQL distribution; the underlying React components"——即一个 Grafast 风味的 GraphiQL 发行版所依赖的底层 React 组件。

它的定位非常明确:ruru 本身是一个完整的 GraphQL IDE 应用(可独立运行/由服务器托管),而 ruru-components 把这些界面能力拆成可复用的 React 组件,供希望在既有 React 工程中嵌入 IDE 的开发者使用。README 原文如此概括:

The React components behind ruru, in case you want to embed Ruru into an existing React project.

从 src/index.tsx 可以看到,包的公开 API 极其精简,仅导出三样东西:

export type { Fetcher, RuruProps } from "./interfaces.ts"; export { Ruru } from "./ruru.tsx";

也就是说,你真正需要关心的只有:一个组件<Ruru />和它的属性类型RuruProps(以及可选的Fetcher类型)。

快速开始:最小接入

安装与运行环境

ruru-components 发布在 npm 上(包名ruru-components)。根据其 package.json,运行环境需要满足:

  • Node.js >= 22engines.node);
  • peerDependenciesgraphql ^16.9.0(需要自行安装);
  • React 19 运行时(react/react-dom/react-compiler-runtime为其内部依赖)。

在已有 React 工程中,安装方式与普通依赖一致:

yarn add ruru-components graphql # 或 npm install ruru-components graphql

最小使用示例

README 给出的核心用法如下(以下代码为原文档完整示例,可直接复制运行):

import "graphiql/style.css"; import "@graphiql/plugin-explorer/style.css"; import "ruru-components/ruru.css"; // Have Webpack include the Monaco workers import "graphiql/setup-workers/webpack"; // Or: import "graphiql/setup-workers/vite"; // Or: see "Monaco workers" below import { Ruru } from "ruru-components"; React.render(<Ruru endpoint="/graphql" />);

这段代码蕴含了四个关键动作,下面逐一拆解:

  1. 导入三份样式表:GraphiQL 基础样式、Explorer 插件样式、ruru-components 自身的定制样式(ruru.css)。包在 exports 字段 中专门导出了./ruru.css,说明样式是组件的一等公民,缺少它会直接导致布局错乱。
  2. 设置 Monaco workers:GraphiQL 的代码编辑器基于 Monaco(VS Code 的编辑器内核),其语言服务(JSON、GraphQL)运行在 Web Worker 中,必须显式装配。README 给出了 Webpack 与 Vite 两种自动方案,详见下文"Monaco workers 配置"。
  3. 导入Ruru组件:这是唯一的顶层组件入口。
  4. 渲染组件<Ruru endpoint="/graphql" />声明式地指定 GraphQL 端点。注意endpoint可以传相对路径(如/graphql),组件内部会自动拼接为完整地址。

只传入一个 endpoint 就够了?

从 ruru-types/src/index.ts 的RuruProps定义看,endpoint是唯一"必需"的语义化属性,其余均为可选。组件内部(ruru.tsx)会为未显式传入的大量配置提供合理默认值,例如:

  • inputValueDeprecation默认true(提示已弃用的输入值);
  • schemaDescription默认true
  • defaultQuery默认使用组件内置的 defaultQuery.ts;
  • showPersistHeadersSettings默认true(提供"持久化 Headers"开关);
  • 同时会忽略queryvariables这两个已废弃的旧属性,并在RuruInner中预留了onEditQueryresponseTooltipforcedTheme等 GraphiQL 扩展点(当前以注释形式标注,说明这些透传能力正在演进中)。

Monaco workers 配置

Monaco 编辑器会把 JSON 与 GraphQL 的语言解析放到 Worker 线程中执行。如果直接用<Ruru />而不同时装配 workers,编辑器往往表现为无语法高亮、无智能提示或直接报错。README 提供了两条路线:

路线一:交给打包器自动处理(推荐)

在 Webpack 工程中,只需一行副作用导入:

import "graphiql/setup-workers/webpack";

在 Vite 工程中,则替换为:

import "graphiql/setup-workers/vite";

这两种方式会分别利用 Webpack 的?worker模块规则与 Vite 的 worker 打包能力,把 Monaco 的editor.workerjson.workergraphql.worker自动打包并注册到globalThis.MonacoEnvironment

路线二:手动注入 MonacoEnvironment

如果打包器方案在你的工程中不可用(例如自定义构建链、CDN 分发、或 worker 策略受限),README 给出了手动方案——在引入 Ruru 之前,于 HTML 中加入如下<script type="module">块(完整代码,可原样复制):

<script type="module"> /* Set up monaco workers */ import createJSONWorker from "https://esm.sh/monaco-editor/esm/vs/language/json/json.worker.js?worker"; import createGraphQLWorker from "https://esm.sh/monaco-graphql/esm/graphql.worker.js?worker"; import createEditorWorker from "https://esm.sh/monaco-editor/esm/vs/editor/editor.worker.js?worker"; globalThis.MonacoEnvironment = { getWorker(_workerId, label) { switch (label) { case "json": return createJSONWorker(); case "graphql": return createGraphQLWorker(); default: return createEditorWorker(); } }, }; </script>

其原理是:Monaco 在创建编辑器时会调用MonacoEnvironment.getWorker(workerId, label),我们根据label(语言标识)返回对应的 Worker 构造器——json返回 JSON 语言 worker,graphql返回 Monaco GraphQL 的 worker,其余语言一律回退到通用编辑器 worker。这段脚本必须先于Ruru 应用代码执行,否则 Monaco 找不到 worker 工厂。

RuruProps 完整配置项

ruru-components 的全部属性定义集中在 ruru-types/src/index.ts,ruru-components 只是从该 workspace 包原样再导出(见 src/interfaces.ts)。核心配置如下:

属性类型说明
endpointstringGraphQL HTTP 端点(http://https://,也支持/graphql这类相对路径,默认/graphql
subscriptionEndpointstringGraphQL 订阅端点(ws://wss://)。不传时,若提供了endpoint会自动推导出 ws 地址
fetcherFetcher可选覆盖默认 fetcher(来自@graphiql/toolkit),用于自定义请求/认证逻辑
debugToolsArray<"explain" \| "plan">开放给用户的调试工具列表:explain(输出执行的 SQL)、plan(输出执行的计划)
eventSourceInitRuruEventSourceInit透传给new EventSource(url, eventSourceInit)的初始化参数。规范只定义withCredentials,但实现可扩展,例如reconnectInterval: 1000maxReconnectAttempts: 3
editorTheme/defaultThemestring编辑器主题与默认主题
maxHistoryLengthnumber历史记录最大条数
initialQuery/initialVariables/initialHeadersstring初始查询、变量、Headers
defaultQuery/defaultHeadersstring默认查询与默认 Headers
onEditQuery/onEditVariables/onEditHeaders函数编辑回调
responseTooltipdefaultEditorToolsVisibilityisHeadersEditorEnabledforcedThemeconfirmCloseTabclassName透传其余 GraphiQL 界面属性,原样转发给底层GraphiQLInterface

endpoint 与订阅端点的自动推导

在 useFetcher.ts 中可以看到端点的处理逻辑:

  • endpoint/开头,自动补全为window.location.origin + endpoint
  • 订阅地址通过makeWsUrl推导:相对路径补全为ws://wss://(视当前页面协议而定),http(s)://前缀则直接替换为ws(s)://,其他形式原样使用;
  • 仅在显式传入subscriptionEndpoint时才覆盖推导结果。

自定义 fetcher 与调试请求头

若未传fetcher,组件会用createGraphiQLFetcher创建默认 fetcher。当 Explain 功能开启时(详见下文),fetcher 会自动携带两个调试请求头:

headers["X-PostGraphile-Explain"] = "on"; headers["X-GraphQL-Explain"] = "plan,sql";

这组请求头同时作为 WebSocket 连接的wsConnectionParams,保证订阅场景下 Explain 能力同样可用。

源码级架构:Ruru 组件内部长什么样

<Ruru />并非一个黑盒,其内部结构(src/ruru.tsx)清晰地分为三层:

<GraphiQLProvider> ← 全局状态(fetcher、插件、默认查询) <ExplainContext.Provider> ← Ruru 自定义:Explain 开关、结果与面板状态 <HistoryStore> ← 历史记录存储 <DocExplorerStore> ← 文档浏览存储 <RuruInner> ← GraphiQLInterface + 工具栏 + 页脚 + 错误弹窗

内置插件集

Ruru 预置了 5 个 GraphiQL 插件:

插件来源用途
DOC_EXPLORER_PLUGIN@graphiql/plugin-doc-explorer文档浏览器
DOWNLOAD_PLUGIN本地 plugins/download.tsx下载查询结果/请求
HISTORY_PLUGIN@graphiql/plugin-history历史记录
explorerPlugin@graphiql/plugin-explorer可视化 Schema 浏览器(关闭了归属水印showAttribution: false
EXPLAIN_PLUGIN本地 plugins/explain.tsxRuru 核心卖点:Explain 面板

工具栏与 Options 菜单

RuruInner在 GraphiQL 工具栏中注册了三个快捷键按钮:

  • Prettify Query(Shift-Ctrl-P):由 usePrettify.tsx 实现——优先动态加载prettier/standalone及其 estree/babel/graphql 插件,用prettier.formatWithCursor分别以graphql(查询)和jsonc(变量/Headers)解析器格式化三个编辑器,并保留光标位置;若 Prettier 在 2 秒内加载失败则回退到 GraphiQL 内置的 prettify 动作;
  • Merge Query(Shift-Ctrl-M):合并查询片段;
  • Copy query(Shift-Ctrl-C):复制当前查询。

工具栏右侧的Options下拉菜单由本地状态驱动,包含五项开关(详见下一节)。

错误弹窗

当 EventSource 连接异常等运行时错误发生时,ErrorPopup.tsx 会以浮层形式展示错误信息(源码注释也坦承该组件"需要更完善的设计与无障碍支持",属于仍在打磨的部分)。

Options 菜单与本地存储机制

Options 菜单中的每个开关都与localStorage双向绑定,存储实现位于 useStorage.ts。所有键均以Ruru:为前缀(除graphiql:explorerIsOpen复用了 GraphiQL 既有键):

菜单项存储键效果
Explain (if supported)Ruru:explain"true"/""开启后向 fetcher 注入调试请求头
VerboseRuru:verbose"true"/""是否在响应中保留extensions.explain(不开启则隐藏)
CondensedRuru:condensed"true"/""为界面追加condensedclass,压缩垂直空间
onError: PROPAGATERuru:onError"PROPAGATE"传统 GraphQL 错误处理(错误正常传播)
onError: NULLRuru:onError"NULL"客户端负责错误处理(对应 Grafast 的 null 语义)
onError: HALTRuru:onError"HALT"遇到首个错误立即停止执行

其中onError是 Grafast 执行语义在界面层的暴露:wrappedFetcher会把存储中的onError值作为附加参数合并进每个 fetcher 请求(见 useFetcher.ts 中{ ...params, onError }),从而让用户在不改服务端代码的情况下切换 Grafast 的错误处理策略。

RuruStorage接口还提供了toggle(key)便捷方法,内部实现针对condensed做了特判(默认为开)。所有set操作都会递增一个 revision state 触发重渲染,保证 UI 与存储即时同步。

Explain:Ruru 的调试核心

Explain 是 Ruru 区别于普通 GraphiQL 发行版的关键能力,也是 ruru-components 中最值得深入的部分。

启用条件

在 useFetcher.ts 中,Explain 的启用需要同时满足:

const explain = options.explain && (!props.debugTools || props.debugTools.includes("explain"));

即:用户在 Options 菜单中打开了 Explain 开关(未限制debugToolsdebugTools中包含了"explain")。若你希望完全屏蔽调试能力,可显式传debugTools={[]}

数据流

  1. 开启后,fetcher 携带X-PostGraphile-Explain: onX-GraphQL-Explain: plan,sql请求头;
  2. 服务端(PostGraphile / Grafast)在响应extensions.explain中返回结构化的解释结果,形如:
interface ExplainResults { operations: Array< | { type: "sql"; query: string; explain?: string } // SQL 操作 | { type: "plan"; plan: GrafastPlanJSON } // 计划操作 >; }
  1. wrappedFetcher在每次非 introspection 响应后:若extensions.explain格式合法,则延迟 100ms 将其写入 state 并渲染到 Explain 面板;同时若未开启 Verbose,会通过Object.definePropertyexplain属性改为不可枚举hideProperty),做到"从结果中隐藏、但面板可见";
  2. 若响应携带的是旧版 PostGraphile v4 的顶层explain数组,则兼容转换为type: "sql"的 Legacy explain 条目;
  3. introspection 查询会被短路直接返回,避免干扰用户视图。

Explain 面板

plugins/explain.tsx 把 Explain 注册为带放大镜图标的 GraphiQL 插件,其内容由 components/Explain.tsx 渲染。面板的尺寸与位置偏好通过 useExplain.ts 持久化到Ruru:explainIsOpenRuru:explainSize(默认 300px)、Ruru:explainAtBottom(默认在底部)等键中。

事件流:Schema 变更自动刷新

ruru-components 还内置了对 GraphQL Live/Schema 变更事件流的支持。机制如下(useGraphQLChangeStream.ts + useFetcher.ts):

  1. 默认 fetcher 包装了window.fetch,每次响应都会检查X-GraphQL-Event-Stream响应头;
  2. 若存在,则以该响应头值为地址创建EventSource(支持通过eventSourceInit传入withCredentialsreconnectInterval等扩展选项);
  3. EventSource 收到change事件时,自动触发一次 introspection(refetch),使编辑器内的 Schema 文档与智能提示实时跟随服务端变更;
  4. 连接异常(如服务端重启导致 WebSocket 意外终止)时,会以友好错误信息提示用户,而不是输出原始的{"isTrusted": true}噪音。

这套设计让 Ruru 在开发态(schema 热更新)与生产态(实时 schema 演进)下都能保持文档与提示的最新状态。

其他使用模式与进阶参考

README 明确指出:其他使用模式请参考主包 ruru。ruru 提供了独立运行、CLI 与服务器托管等更完整的形态,相关文档见 grafast/ruru/README.md(其服务端集成、CLI 用法与 HTML 分发方案见 grafast/ruru/src/server.ts、grafast/ruru/src/cli.ts)。

如果你的诉求是"最快的现成 IDE",优先使用 ruru 独立包;如果你的诉求是"把 IDE 深度嵌进自己的 React 应用、并自定义 fetcher 与调试工具",ruru-components 就是那个正确的切入点。二者共享同一套 RuruProps 语义,迁移成本很低。

结语

ruru-components 以极小的公开 API(Ruru+RuruProps)封装了一个功能完整的 Grafast 风格 GraphQL IDE:开箱即用的五个插件、Monaco workers 的三种装配方案、endpoint驱动的自动 fetcher、Explain 调试链路,以及基于localStorage的完整偏好持久化。通过本文对照 README 与 src/ruru.tsx、src/hooks、ruru-types/src/index.ts 等源码阅读,你既可以按最小示例快速接入,也能深入理解其请求头、错误语义与事件流机制,进而在自己的产品中定制出符合需求的 GraphQL 工作台。

【免费下载链接】crystal🔮 Graphile's Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址: https://gitcode.com/gh_mirrors/cry/crystal

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询