near-operation-file: fragments defined in another gql tag of the same file lose their *FragmentDoc since visitor-plugin-common 2.13.8

Author: RobHannayCreated Sep 2, 2026Updated Sep 2, 2026

Describe the bug

With the near-operation-file preset, a fragment defined in one gql tag and spread by an operation in a second gql tag of the same source file no longer gets an exported <Name>FragmentDoc in the generated file. The operation document still interpolates ${<Name>FragmentDoc}, so the generated file does not compile (Cannot find name 'ThingFieldsFragmentDoc').

Bisected: passes with @graphql-codegen/visitor-plugin-common 2.13.7, fails with 2.13.8, all other packages held constant. Introduced by #8757, which replaced ClientSideBaseVisitor._fragments (an array) with new Map(fragments.map(f => [f.name, f])).

Why it happens

near-operation-file-preset reports every fragment a document spreads as an external fragment, including one whose generated output path is the same file. Since #6522 ("don't generate import statements for fragments declared in the same file") it deliberately skips the import for that case, but it still pushes the fragment to externalFragments. Plugins then build [...localDefinitions (isExternal: false), ...externalFragments (isExternal: true)].

  • Before 2.13.8 that array was fed into dependency-graph, whose addNode ignores an existing node, so the local definition (first) won and the doc was emitted.
  • Since 2.13.8 the Map keeps the last entry, the external copy, and get fragments() filters out isExternal entries, so nothing is emitted.

#6522's description states the intended contract: the import is unnecessary "since the const is declared later on in the file". The regression test added with it (#6520 - self-importing fragment, fixture issue-6520.ts, which is exactly this shape: a fragment in one gql tag and a query spreading it in another) only asserts not.toMatch('import { UserFieldsFragmentDoc }'); it never asserts that export const UserFieldsFragmentDoc is present, so the 2.13.8 change passed it. Adding expect(result[0].content).toMatch('export const UserFieldsFragmentDoc') to that test reproduces the bug in-repo.

The visitor's own documentation for includeExternalFragments defines external fragments as ones "not defined in the same location as the operation definition", so a fragment emitted into the same output file should not count as external. Note that includeExternalFragments: true is not a workaround: it also emits docs for genuinely external (other-file) fragments next to their imports, producing TS2440: Import declaration conflicts with local declaration.

Reproduction

schema.graphql

graphql
type Query { thing: Thing }
type Thing { id: ID! name: String }

src/Thing.ts

typescript
import { gql } from "@apollo/client";
gql`fragment ThingFields on Thing { id name }`;
gql`query GetThing { thing { ...ThingFields } } ${ThingFields}`;

codegen.ts

typescript
export default {
  schema: "schema.graphql",
  documents: ["src/**/*.ts", "!src/**/*.generated.ts"],
  generates: {
    "src/generated/graphql.ts": { plugins: ["typescript"] },
    "src/": {
      preset: "near-operation-file",
      presetConfig: { extension: ".generated.ts", baseTypesPath: "generated/graphql.ts", folder: "__generated__" },
      plugins: ["typescript-operations", "typescript-react-apollo"],
    },
  },
};
  • @graphql-codegen/[email protected] (pins visitor-plugin-common 2.13.1): src/__generated__/Thing.generated.ts contains export const ThingFieldsFragmentDoc.
  • @graphql-codegen/[email protected] (pins 2.13.8), or 4.3.2 with an npm overrides entry forcing @graphql-codegen/visitor-plugin-common to 2.13.8 or later: the export is gone, the operation document still references it.

Expected behavior

A fragment that is emitted into the same output file is local, so its *FragmentDoc should be generated. Either the preset should not report same-output-file fragments as external (consistent with #6522), or the visitor should prefer the local definition when deduplicating by name (restoring the pre-2.13.8 behavior).

Environment

  • @graphql-codegen/cli 7.3.1, typescript-operations 6.1.6, typescript-react-apollo 5.0.0, near-operation-file-preset 5.2.2, visitor-plugin-common 7.2.5 (also reproduced on cli 5.0.7 / operations 4.6.1 / preset 3.1.0 with only the visitor version changed)
  • graphql 16.14.2, Node 24

We are working around it with a pnpm patch to the preset that continues when fragmentDetails.filePath === generatedFilePath.

Source: dotansimha/graphql-code-generator