dotansimha / dotansimha/graphql-code-generator

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

Open
#10,932 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
11.3k
Forks
1.4k
Avg merge
1d 1h
Merged PRs (30d)
23

Description

### 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 `FragmentDoc` in the generated file. The operation document still interpolates `${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`
```ts
import { gql } from "@apollo/client";
gql`fragment ThingFields on Thing { id name }`;
gql`query GetThing { thing { ...ThingFields } } ${ThingFields}`;
```

`codegen.ts`
```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 `continue`s when `fragmentDetails.filePath === generatedFilePath`.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the near-operation-file-preset handling of externalFragments and the ClientSideBaseVisitor fragment deduplication introduced in visitor-plugin-common 2.13.8. Run the regression test using fixture issue-6520.ts, then add the assertion that UserFieldsFragmentDoc is exported. Done means same-output-file fragments retain their generated export without introducing imports or conflicts for genuinely external fragments.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.