Skip to content

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

Description

@RobHannay

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

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

src/Thing.ts

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

codegen.ts

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/typescript-react-apollo@4.3.2 (pins visitor-plugin-common 2.13.1): src/__generated__/Thing.generated.ts contains export const ThingFieldsFragmentDoc.
  • @graphql-codegen/typescript-react-apollo@4.3.3 (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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions