From 0857570e4c191f1ef28ebb0be600ea291b19ab25 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 09:47:30 +0000 Subject: [PATCH] Add documentMode plainString Generates documents as plain template strings with fragments embedded, without the TypedDocumentString class, its import, or a type cast. Refs #10873 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013F2QpCXY9jqU8vHpGPwbXu --- .changeset/plain-string-document-mode.md | 6 ++ .../src/client-side-base-visitor.ts | 11 +++- .../tests/typed-document-node.spec.ts | 55 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 .changeset/plain-string-document-mode.md diff --git a/.changeset/plain-string-document-mode.md b/.changeset/plain-string-document-mode.md new file mode 100644 index 00000000000..73ddb2275ce --- /dev/null +++ b/.changeset/plain-string-document-mode.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': minor +'@graphql-codegen/typed-document-node': minor +--- + +Add `documentMode: 'plainString'`. Documents are generated as plain template strings with fragments embedded, instead of `TypedDocumentString` instances. No class, import or type cast is generated, so each document is typed as `string`. 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 df17a0e05ab..c319c6d04af 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 @@ -30,6 +30,7 @@ export enum DocumentMode { documentNodeImportFragments = 'documentNodeImportFragments', external = 'external', string = 'string', + plainString = 'plainString', } const EXTENSIONS_TO_REMOVE = ['.ts', '.tsx', '.js', '.jsx']; @@ -134,6 +135,8 @@ export interface RawClientSideBasePluginConfig extends RawConfig { * - `documentNode`: document nodes will be generated as objects when we generate the templates. * - `documentNodeImportFragments`: Similar to documentNode except it imports external fragments instead of embedding them. * - `external`: document nodes are imported from an external file. To be used with `importDocumentNodeExternallyFrom` + * - `string`: documents are generated as `TypedDocumentString` instances, a `String` subclass that carries result and variables types. The class is generated into the output. + * - `plainString`: documents are generated as plain template strings, with fragments embedded. No class, import or type cast is generated, so the documents are typed as `string`. * * Note that some plugins (like `typescript-graphql-request`) also supports `string` for this parameter. * @@ -335,7 +338,8 @@ export class ClientSideBaseVisitor< if (fragments && fragments.length > 0) { if ( this.config.documentMode === DocumentMode.documentNode || - this.config.documentMode === DocumentMode.string + this.config.documentMode === DocumentMode.string || + this.config.documentMode === DocumentMode.plainString ) { return Array.from(this._fragments.values()) .filter(f => fragments.includes(this.getFragmentVariableName(f.name))) @@ -362,6 +366,7 @@ export class ClientSideBaseVisitor< const includeNestedFragments = this.config.documentMode === DocumentMode.documentNode || this.config.documentMode === DocumentMode.string || + this.config.documentMode === DocumentMode.plainString || node.kind === 'OperationDefinition'; const fragmentNames = this._extractFragments(node, includeNestedFragments); const fragments = this._transformFragments(fragmentNames); @@ -453,6 +458,10 @@ export class ClientSideBaseVisitor< return `new TypedDocumentString(\`${doc}\`)`; } + if (this.config.documentMode === DocumentMode.plainString) { + return `\`${doc}\``; + } + const gqlImport = this._parseImport(this.config.gqlImport || 'graphql-tag'); return (gqlImport.propName || 'gql') + '`' + 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 53a8698865f..1e147d8f896 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 @@ -81,6 +81,61 @@ describe('TypedDocumentNode', () => { `); }); + it('generates plain strings with embedded fragments when documentMode=plainString', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + person(id: ID!): Person! + } + type Person { + id: ID! + name: String! + } + `); + + const document = parse(/* GraphQL */ ` + query Person { + person(id: 1) { + ...PersonDetails + } + } + + fragment PersonDetails on Person { + id + name + } + `); + + const result = mergeOutputs([ + await plugin( + schema, + [{ document }], + { + documentMode: DocumentMode.plainString, + }, + { outputFile: '' }, + ), + ]); + + expect(result).toMatchInlineSnapshot(` + "export const PersonDetailsFragmentDoc = \` + fragment PersonDetails on Person { + id + name + } + \`; + export const PersonDocument = \` + query Person { + person(id: 1) { + ...PersonDetails + } + } + fragment PersonDetails on Person { + id + name + }\`;" + `); + }); + describe('addTypenameToSelectionSets', () => { it('Check is add __typename to typed document', async () => { const schema = buildSchema(/* GraphQL */ `