From a506b4fde63127ff8ff76492ba85bea5569db486 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sat, 27 Dec 2025 00:54:24 +1100 Subject: [PATCH 1/2] Fix fragments not getting deduped when using documentMode=graphQLTag --- .../src/client-side-base-visitor.ts | 11 ++- .../tests/typed-document-node.spec.ts | 74 ++++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/plugins/other/visitor-plugin-common/src/client-side-base-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/client-side-base-visitor.ts index 33b18190d23..9003733548f 100644 --- a/packages/plugins/other/visitor-plugin-common/src/client-side-base-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/client-side-base-visitor.ts @@ -323,7 +323,7 @@ export class ClientSideBaseVisitor< return fragmentNames.map(document => this.getFragmentVariableName(document)); } - protected _includeFragments(fragments: string[]): string { + protected _includeFragments(fragments: string[], nodeKind: 'FragmentDefinition' | 'OperationDefinition'): string { if (fragments && fragments.length > 0) { if (this.config.documentMode === DocumentMode.documentNode || this.config.documentMode === DocumentMode.string) { return Array.from(this._fragments.values()) @@ -334,6 +334,9 @@ export class ClientSideBaseVisitor< if (this.config.documentMode === DocumentMode.documentNodeImportFragments) { return ''; } + if (nodeKind !== 'OperationDefinition') { + return ''; + } return String(fragments.map(name => '${' + name + '}').join('\n')); } @@ -346,13 +349,15 @@ export class ClientSideBaseVisitor< protected _gql(node: FragmentDefinitionNode | OperationDefinitionNode): string { const includeNestedFragments = - this.config.documentMode === DocumentMode.documentNode || this.config.documentMode === DocumentMode.string; + this.config.documentMode === DocumentMode.documentNode || + this.config.documentMode === DocumentMode.string || + node.kind === 'OperationDefinition'; const fragmentNames = this._extractFragments(node, includeNestedFragments); const fragments = this._transformFragments(fragmentNames); const doc = this._prepareDocument(` ${print(node).split('\\').join('\\\\') /* Re-escape escaped values in GraphQL syntax */} - ${this._includeFragments(fragments)}`); + ${this._includeFragments(fragments, node.kind)}`); if (this.config.documentMode === DocumentMode.documentNode) { let gqlObj = gqlTag([doc]); diff --git a/packages/plugins/typescript/typed-document-node/tests/typed-document-node.spec.ts b/packages/plugins/typescript/typed-document-node/tests/typed-document-node.spec.ts index 42764efdc45..2fb94bbb3a5 100644 --- a/packages/plugins/typescript/typed-document-node/tests/typed-document-node.spec.ts +++ b/packages/plugins/typescript/typed-document-node/tests/typed-document-node.spec.ts @@ -1,4 +1,5 @@ -import { Types } from '@graphql-codegen/plugin-helpers'; +import { mergeOutputs, Types } from '@graphql-codegen/plugin-helpers'; +import { DocumentMode } from '@graphql-codegen/visitor-plugin-common'; import { buildSchema, parse } from 'graphql'; import { plugin } from '../src/index.js'; @@ -9,6 +10,77 @@ describe('TypedDocumentNode', () => { expect(result.prepend.length).toBe(0); }); + it('dedupes fragments automatically when documentMode=graphQLTag', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + person(id: ID!): Person! + } + type Person { + id: ID! + name: String! + children: [Person!] + } + `); + + const document = parse(/* GraphQL */ ` + query Person { + person(id: 1) { + ...PersonDetails + children { + ...BasePersonDetails + } + } + } + + fragment PersonDetails on Person { + ...BasePersonDetails + name + } + + fragment BasePersonDetails on Person { + id + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + documentMode: DocumentMode.graphQLTag, + }, + { outputFile: '' } + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; + import gql from 'graphql-tag'; + export const BasePersonDetailsFragmentDoc = gql\` + fragment BasePersonDetails on Person { + id + } + \` as unknown as DocumentNode; + export const PersonDetailsFragmentDoc = gql\` + fragment PersonDetails on Person { + ...BasePersonDetails + name + } + \` as unknown as DocumentNode; + export const PersonDocument = gql\` + query Person { + person(id: 1) { + ...PersonDetails + children { + ...BasePersonDetails + } + } + } + \${PersonDetailsFragmentDoc} + \${BasePersonDetailsFragmentDoc}\` as unknown as DocumentNode;" + `); + }); + describe('addTypenameToSelectionSets', () => { it('Check is add __typename to typed document', async () => { const schema = buildSchema(/* GraphQL */ ` From 7572bb12f904056cb4f4e91c2244e9d893cfd9d2 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sat, 27 Dec 2025 00:55:19 +1100 Subject: [PATCH 2/2] Add changeset --- .changeset/eager-mice-admire.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/eager-mice-admire.md diff --git a/.changeset/eager-mice-admire.md b/.changeset/eager-mice-admire.md new file mode 100644 index 00000000000..2c31037f6fc --- /dev/null +++ b/.changeset/eager-mice-admire.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/typed-document-node': patch +'@graphql-codegen/visitor-plugin-common': patch +--- + +Fix fragments not getting deduped when documentMode=graphQLTag