
graphiql/toolkit 实战指南用 createGraphiQLFetcher 构建支持 defer/stream 与订阅的 GraphiQL Fetcher【免费下载链接】graphiqlGraphiQL the GraphQL LSP Reference Ecosystem for building browser IDE tools.项目地址: https://gitcode.com/GitHub_Trending/gr/graphiqlgraphiql/toolkit是 GraphiQL 生态中面向构建 GraphQL IDE场景的通用工具库被graphiql与graphiql/react等包直接使用其中最核心的能力是通过createGraphiQLFetcher一键生成一个功能完整的fetcher。阅读本文后你将掌握如何基于 create-fetcher 文档 与 createFetcher 源码为自己的 GraphiQL 实例接入 HTTP POST、multipart 增量交付stream/defer、graphql-ws订阅与 legacy WebSocket 协议并理解每条配置背后的源码级实现原理。一、graphiql/toolkit是什么按照 README 的定义这是一个用于构建 GraphQL IDE 的通用库general purpose library for building GraphQL IDEs。它服务于两类角色被其他包使用graphiql、graphiql/react等包在内部依赖它供开发者使用它提供了一批在处理这些包时非常实用的工具函数。从 src/index.ts 的导出清单可以看出工具集分为五大模块模块目录说明async-helpersPromise / Observable / AsyncIterable 的类型判定与相互转换例如fetcherReturnToPromisecreate-fetcher生成 GraphiQLfetcher的核心模块本文重点format格式化相关工具graphql-helpersGraphQL AST 辅助工具如merge-ast、auto-complete、operation-namestorage存储相关工具如query、history、custom其中createFetcher是文档明确点名的旗舰功能详见 docs/create-fetcher.md它是一个用于生成 HTTP GET、POST含 multipart以及 WebSocket fetcher 的实用工具。二、createGraphiQLFetcher一个 fetcher 覆盖全场景createGraphiQLFetcher用于生成一个功能完整的 GraphiQLfetcher支持HTTP 请求POST 查询与变更Incremental Delivery增量交付通过 multipart 响应支持stream与defer订阅基于graphql-ws的新版订阅协议或subscriptions-transport-ws的 legacy 协议。在实现上它依赖两个关键客户端库见 package.json 的dependenciesgraphql-wsWebSocket 订阅客户端作为 peer dependency可选安装merosmultipart 响应解析库作为 GraphQL over HTTP Working Group Spec 规范与主流传输提案的客户端参考实现。meros与n1ru4l/push-pull-async-iterable-iterator是它的直接运行时依赖前者负责解析multipart/mixed响应流后者负责把 sink 回调驱动的订阅流转换为 AsyncIterable使 fetcher 返回值统一为 GraphiQL 可消费的形式。三、安装与最小接入3.1 安装npm install graphiql/toolkit3.2 最小示例纯 HTTP / Multipart 增量交付只需传入urlfetcher 便可用于stream/defer增量交付——此时甚至不会初始化 WebSocket 客户端import * as React from react; import { createRoot } from react-dom/client; import { GraphiQL } from graphiql; import { createGraphiQLFetcher } from graphiql/toolkit; const url https://my-schema.com/graphql; const fetcher createGraphiQLFetcher({ url }); export const App () GraphiQL fetcher{fetcher} /; const root createRoot(document.getElementById(graphiql)); root.render(App /);这段代码正是 GraphiQL 默认接入远程 schema 的标准姿势把fetcher作为 prop 传给GraphiQL即可获得完整的查询、内省与增量交付体验。3.3 加装graphql-ws订阅graphql-ws是可选 peer dependency使用时需单独安装npm install graphql-ws只要再提供一个subscriptionUrlfetcher 便会同时支持 HTTP/Multipart 增量交付defer/stream与 WebSocket 订阅import * as React from react; import { createRoot } from react-dom/client; import { GraphiQL } from graphiql; import { createGraphiQLFetcher } from graphiql/toolkit; const url https://my-schema.com/graphql; const subscriptionUrl wss://my-schema.com/graphql; const fetcher createGraphiQLFetcher({ url, subscriptionUrl }); export const App () GraphiQL fetcher{fetcher} /; const root createRoot(document.getElementById(graphiql)); root.render(App /);注意subscriptionUrl方案要求服务端兼容新版graphql-ws订阅规范。四、完整配置项详解createGraphiQLFetcher接收一个CreateFetcherOptions对象完整类型定义见 src/create-fetcher/types.ts。各选项如下选项必填说明url✅所有 HTTP 请求与 schema 内省请求使用的 URLsubscriptionUrl否据此生成一个graphql-ws客户端要求服务端兼容新版订阅规范wsClient否自带的订阅客户端匹配graphql-ws的Client签名传入后绕过subscriptionUrlwsConnectionParams否使用subscriptionUrl时提供的初始连接参数对应graphql-wsClientOptions.connectionParamslegacyWsClient否匹配subscriptions-transport-ws签名的 legacy 订阅客户端传入后绕过subscriptionUrllegacyClient否legacyWsClient的别名headers否静态请求头会附加到所有请求fetch否自定义 fetch 实现如isomorphic-fetch适用于 SSR 等场景enableIncrementalDelivery否默认true设为false可禁用 multipart退化为简单 POSTschemaFetcher否专门用于 schema 内省的自定义 fetcher多数场景下urlheaders已足够4.1wsConnectionParams用法示例const fetcher createGraphiQLFetcher({ url: https://localhost:3000, subscriptionUrl: https://localhost:3001, wsConnectionParams: { Authorization: token 1234 }, }); const App () { return GraphiQL fetcher{fetcher} /; };连接参数在 lib.ts 的getWsFetcher中被合并{ ...options.wsConnectionParams, ...fetcherOpts?.headers }即每次请求携带的 headers 会覆盖静态连接参数中的同名键。4.2 关于headers的覆盖语义源码注释特别说明headers是静态请求头但如果用户开启了请求头编辑器并在界面上提供了同名 header用户的值会覆盖静态值。这一点在请求合并逻辑中同样体现...options.headers在前...fetcherOpts?.headers在后见下节源码保证每次请求的动态 header 优先。五、源码级原理fetcher 的请求调度createGraphiQLFetcher的实现createFetcher.ts本质是一个请求分拣器逻辑清晰且可以用测试用例 buildFetcher.spec.ts 验证export function createGraphiQLFetcher(options: CreateFetcherOptions): Fetcher { const httpFetch options.fetch || (typeof window ! undefined window.fetch); if (!httpFetch) { throw new Error(No valid fetcher implementation available); } options.enableIncrementalDelivery options.enableIncrementalDelivery ! false; // 内省等 schema 请求使用的简单 fetcher const simpleFetcher createSimpleFetcher(options, httpFetch); // 普通查询/变更使用的 fetcher默认 multipart const httpFetcher options.enableIncrementalDelivery ? createMultipartFetcher(options, httpFetch) : simpleFetcher; return async (graphQLParams, fetcherOpts) { // 1. 内省请求 → simpleFetcher或 schemaFetcher if (graphQLParams.operationName IntrospectionQuery) { return (options.schemaFetcher || simpleFetcher)(graphQLParams, fetcherOpts); } // 2. 订阅请求 → WebSocket fetcher const isSubscription fetcherOpts?.documentAST ? isSubscriptionWithName(fetcherOpts.documentAST, graphQLParams.operationName || undefined) : false; if (isSubscription) { const wsFetcher await getWsFetcher(options, fetcherOpts); if (!wsFetcher) { throw new Error( Your GraphiQL createFetcher is not properly configured for websocket subscriptions yet. ${ options.subscriptionUrl ? Provided URL ${options.subscriptionUrl} failed : Please provide subscriptionUrl, wsClient or legacyClient option first. }, ); } return wsFetcher(graphQLParams); } // 3. 其余查询/变更 → httpFetcher return httpFetcher(graphQLParams, fetcherOpts); }; }5.1 三条路由的分工内省请求operationName IntrospectionQuery走simpleFetcher除非显式提供了schemaFetcher。内省不关心增量交付因此使用最轻量的实现这也是 GraphiQL 加载 schema 时不走 multipart 的原因。订阅请求通过isSubscriptionWithName遍历documentAST按操作名匹配OperationDefinition且operation subscription见 lib.ts。命中后调用getWsFetcher按优先级依次尝试wsClient→subscriptionUrl懒加载graphql-ws→legacyClient/legacyWsClient。普通查询/变更默认走createMultipartFetcher支持增量交付若enableIncrementalDelivery: false则退化为simpleFetcher。5.2 两种 HTTP fetcher 的请求头差异两个 HTTP fetcher 的实现位于 lib.tscreateSimpleFetcherL57-L71发送content-type: application/jsonaccept为application/graphql-responsejson, application/json;q0.9符合 GraphQL over HTTP 规范createMultipartFetcherL140-L178accept为application/json, multipart/mixed响应经meros解析若服务端未返回 multipart 流即非 AsyncIterable则退化为直接response.json()若 multipart 分块中存在非 JSON 部分则抛出带详细头部/体信息的错误。两条路径的 header 合并顺序均为默认 accept →...options.headers→...fetcherOpts?.headers因此静态 headers 与每请求 headers 都可以覆盖 accept 等默认值。这一点由 acceptHeaders.spec.ts 用三个用例逐一验证。5.3 WebSocket fetcher 的三种构建方式createWebsocketsFetcherFromUrl(url, connectionParams)L73-L96动态import(graphql-ws)并createClient若包未安装则抛出请先安装 graphql-ws的引导性错误createWebsocketsFetcherFromClient(wsClient)L101-L121把wsClient.subscribe的 sink 转换为 AsyncIterable借助makeAsyncIterableIteratorFromSink并对CloseEvent输出可读的错误信息createLegacyWebsocketsFetcher(legacyWsClient)L127-L135适配subscriptions-transport-ws的request()返回的 Observable。源码注释明确提示该库已废弃且存在安全问题因此仅保留兼容层而不再提供类型定义。六、高级定制示例6.1 自定义wsClient基于graphql-ws如果你需要对订阅客户端做精细控制如心跳间隔可以自行创建 client 并通过wsClient传入import * as React from react; import { createRoot } from react-dom/client; import { GraphiQL } from graphiql; import { createClient } from graphql-ws; import { createGraphiQLFetcher } from graphiql/toolkit; const url https://my-schema.com/graphql; const subscriptionUrl wss://my-schema.com/graphql; const fetcher createGraphiQLFetcher({ url, wsClient: createClient({ url: subscriptionUrl, keepAlive: 2000, }), }); export const App () GraphiQL fetcher{fetcher} /; const root createRoot(document.getElementById(graphiql)); root.render(App /);6.2 自定义legacyClient不推荐若需对接仍使用subscriptions-transport-ws协议的服务端可传入legacyWsClient或其别名legacyClientimport * as React from react; import { createRoot } from react-dom/client; import { GraphiQL } from graphiql; import { SubscriptionClient } from subscriptions-transport-ws; import { createGraphiQLFetcher } from graphiql/toolkit; const url https://my-schema.com/graphql; const subscriptionUrl wss://my-schema.com/graphql; const fetcher createGraphiQLFetcher({ url, legacyWsClient: new SubscriptionClient(subscriptionUrl), }); export const App () GraphiQL fetcher{fetcher} /; const root createRoot(document.getElementById(graphiql)); root.render(App /);注意需单独安装该客户端npm install subscriptions-transport-ws6.3 自定义fetchSSR 场景服务端渲染等无全局fetch的环境可注入isomorphic-fetchimport * as React from react; import { createRoot } from react-dom/client; import { GraphiQL } from graphiql; import { fetch } from isomorphic-fetch; import { createGraphiQLFetcher } from graphiql/toolkit; const url https://my-schema.com/graphql; const fetcher createGraphiQLFetcher({ url, fetch }); export const App () GraphiQL fetcher{fetcher} /; const root createRoot(document.getElementById(graphiql)); root.render(App /);源码中对fetch的取值逻辑是options.fetch || (typeof window ! undefined window.fetch)并在两者都不可用时抛出No valid fetcher implementation available这正是 SSR 下必须显式传fetch的原因。七、配套工具让 fetcher 返回值统一可消费GraphiQL 需要处理同步结果、Promise、Observable、AsyncIterable等多种 fetcher 返回值async-helpers模块async-helpers/index.ts为此提供了isPromise/isObservable/isAsyncIterable鸭子类型判定后者兼容 iOS Safari 等未实现Symbol.asyncIterator的环境通过Symbol.toStringTag AsyncGenerator兜底fetcherReturnToPromise将任意的 fetcher 返回值统一收敛为 Promise。此外graphql-helpers中的merge-ast、auto-complete、operation-name等工具配套测试见 merge-ast.spec.ts以及storage模块中的query/history存储测试见 storage 测试目录共同构成了graphiql与graphiql/react可复用的基础设施。若需要完整的类型与 API 参考可查阅仓库中的类型定义文件 types.ts 与各模块入口。八、测试与行为验证graphiql/toolkit使用 Vitest 组织测试见 vitest.config.mts可通过yarn test运行。针对 fetcher 的关键行为均有用例覆盖buildFetcher.spec.ts验证默认不初始化 WebSocket 客户端、内省请求走 simpleFetcher、enableIncrementalDelivery: false时跳过 multipart、以及wsClient/legacyClient传入后绕过subscriptionUrl等调度逻辑acceptHeaders.spec.ts验证两种 HTTP fetcher 的规范accept头以及静态 headers、每请求 headers 对 accept 的覆盖优先级。这些测试同时是理解 fetcher 行为边界的绝佳入口——例如不传subscriptionUrl时绝不会初始化订阅客户端内省请求永不走 multipartenableIncrementalDelivery默认为true等结论都能在测试断言中找到直接证据。九、小结createGraphiQLFetcher用极少的配置覆盖了 GraphiQL 数据层的全部主流场景HTTP POST multipart 增量交付开箱即用订阅能力通过subscriptionUrl/wsClient/legacyClient三种方式按需接入。理解 createFetcher.ts 中内省走简单、订阅走 WebSocket、其余走 multipart的调度逻辑能帮助你在集成远程 schema、私有订阅服务端或 SSR 环境时精准地选择配置组合快速定位订阅不工作内省失败等常见问题。【免费下载链接】graphiqlGraphiQL the GraphQL LSP Reference Ecosystem for building browser IDE tools.项目地址: https://gitcode.com/GitHub_Trending/gr/graphiql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考