如何使用GraphQL Codegen生成的Typed DocumentNode片段创建查询
问题背景
我正在使用GraphQL Code Generator生成带类型的DocumentNode片段,示例代码如下:
export const MMovieConnectionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MMovieConnection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MMovieConnection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"mMovieActors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode<MMovieConnectionFragment, unknown>;
尝试使用上述片段创建简单的GraphQL查询,示例代码如下:
gql`query myQuery { myQuery { ...MMovieConnection } } ${MMovieConnectionFragmentDoc} `
运行时抛出错误:Cannot read properties of undefined (reading 'source')
如果采用如下标准方式定义片段,查询可成功创建:
fieldsFragment = gql`fragment MMovieConnection on MMovieConnection { edges { node { id } } }`;
对比两种片段对象发现:标准方式创建的片段与MMovieConnectionFragmentDoc结构基本等价,但前者额外包含loc属性,该属性下挂载了source属性,结构示例如下:
loc: { start: 0, end: 239, source: { body: "fragment MMovieConnection on MMovieConnection {\n edges {\n node {\n id\n mMovieActors {\n id\n }\n }\n }\n }", name: "GraphQL request", locationOffset: { line: 1, column: 1, }, }, },
由此推断gql模板标签在查询中引入片段时,需要依赖对象上的loc与source属性,需要找到可直接使用MMovieConnectionFragmentDoc创建GraphQL查询的可行方法。
解决方案
报错核心原因是:graphql-tag提供的gql模板标签在处理插值传入的DocumentNode时,会默认读取节点上的loc.source字段做片段依赖解析、重复定义过滤,而GraphQL Code Generator默认生成的无字符串AST片段不携带loc字段,直接插值就会触发读取undefined属性的错误。
可行的解决方法有两种,按需选择即可:
- 调整Code Generator配置,生成带
loc属性的兼容片段
如果你使用的是官方@graphql-codegen/client-preset,在codegen配置文件中修改documentMode配置项为graphqlTag,生成的片段会自动用gql标签包裹,自带完整的loc与source属性,可直接在gql模板中插值使用,不需要改动现有业务代码。配置示例:// codegen.ts 配置示例 import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { schema: 'your-schema-path', documents: ['src/**/*.tsx'], generates: { 'src/gql/': { preset: 'client', plugins: [], presetConfig: { documentMode: 'graphqlTag', // 新增该配置项 } } } }; export default config; - 不修改Codegen配置,通过AST合并方式组装查询
如果不想改动codegen生成规则,可以跳过gql模板的插值逻辑,直接用AST合并工具把基础查询和生成的片段合并为完整的DocumentNode,这种方式不依赖loc属性,可直接使用生成的纯AST片段。
首先安装依赖:
代码实现示例:npm install @graphql-tools/mergeimport { gql } from 'graphql-tag'; import { mergeTypeDefs } from '@graphql-tools/merge'; import { MMovieConnectionFragmentDoc } from './gql/fragments'; // 先定义不带片段插值的基础查询 const baseQuery = gql`query myQuery { myQuery { ...MMovieConnection } }`; // 直接合并AST节点,得到完整可执行的查询 const fullQuery = mergeTypeDefs([baseQuery, MMovieConnectionFragmentDoc]);
不推荐手动给生成的FragmentDoc补mock的loc属性,这种方式兼容性差,升级graphql-tag或codegen版本时容易出现隐性问题。
内容的提问来源于stack exchange,提问作者Matthew
相关产品推荐
相关产品推荐

