Skip to content

Commit 386cf90

Browse files
authored
Fix fragment imports for near-operation-file with graphQLTag (#9301)
1 parent 5890ccc commit 386cf90

File tree

5 files changed

+173
-2
lines changed

5 files changed

+173
-2
lines changed

.changeset/big-bottles-bow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': patch
3+
---
4+
5+
Fix fragment imports for near-operation-file with graphQLTag

packages/plugins/other/visitor-plugin-common/src/client-side-base-visitor.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
import gqlTag from 'graphql-tag';
1717
import { BaseVisitor, ParsedConfig, RawConfig } from './base-visitor.js';
1818
import { LoadedFragment, ParsedImport } from './types.js';
19-
import { buildScalarsFromConfig, getConfigValue } from './utils.js';
19+
import { buildScalarsFromConfig, unique, flatten, getConfigValue, groupBy } from './utils.js';
20+
import { FragmentImport, ImportDeclaration, generateFragmentImportStatement } from './imports.js';
2021

2122
gqlTag.enableExperimentalFragmentVariables();
2223

@@ -568,7 +569,7 @@ export class ClientSideBaseVisitor<
568569
return path;
569570
}
570571

571-
public getImports(): string[] {
572+
public getImports(options: { excludeFragments?: boolean } = {}): string[] {
572573
for (const i of this._additionalImports || []) {
573574
this._imports.add(i);
574575
}
@@ -621,6 +622,31 @@ export class ClientSideBaseVisitor<
621622
break;
622623
}
623624

625+
const excludeFragments =
626+
options.excludeFragments || this.config.globalNamespace || this.config.documentMode !== DocumentMode.graphQLTag;
627+
628+
if (!excludeFragments) {
629+
const deduplicatedImports = Object.values(groupBy(this.config.fragmentImports, fi => fi.importSource.path))
630+
.map(
631+
(fragmentImports): ImportDeclaration<FragmentImport> => ({
632+
...fragmentImports[0],
633+
importSource: {
634+
...fragmentImports[0].importSource,
635+
identifiers: unique(
636+
flatten(fragmentImports.map(fi => fi.importSource.identifiers)),
637+
identifier => identifier.name
638+
),
639+
},
640+
emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
641+
})
642+
)
643+
.filter(fragmentImport => fragmentImport.outputPath !== fragmentImport.importSource.path);
644+
645+
for (const fragmentImport of deduplicatedImports) {
646+
this._imports.add(generateFragmentImportStatement(fragmentImport, 'document'));
647+
}
648+
}
649+
624650
return Array.from(this._imports);
625651
}
626652

packages/plugins/other/visitor-plugin-common/src/utils.ts

+16
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,19 @@ export function isOneOfInputObjectType(
524524

525525
return isOneOfType;
526526
}
527+
528+
export function groupBy<T>(array: Array<T>, key: (item: T) => string | number): { [key: string]: Array<T> } {
529+
return array.reduce<{ [key: string]: Array<T> }>((acc, item) => {
530+
const group = (acc[key(item)] ??= []);
531+
group.push(item);
532+
return acc;
533+
}, {});
534+
}
535+
536+
export function flatten<T>(array: Array<Array<T>>): Array<T> {
537+
return ([] as Array<T>).concat(...array);
538+
}
539+
540+
export function unique<T>(array: Array<T>, key: (item: T) => string | number = item => item.toString()): Array<T> {
541+
return Object.values(array.reduce((acc, item) => ({ [key(item)]: item, ...acc }), {}));
542+
}

packages/plugins/other/visitor-plugin-common/tests/client-side-base-visitor.spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,75 @@ describe('getImports', () => {
202202
}
203203
});
204204
});
205+
206+
describe('when documentMode "graphQLTag"', () => {
207+
const schema = buildSchema(/* GraphQL */ `
208+
type Query {
209+
a: A
210+
}
211+
212+
type A {
213+
foo: String
214+
bar: String
215+
}
216+
`);
217+
218+
it('imports FragmentDocs', () => {
219+
const fileName = 'fooBarQuery';
220+
const importPath = `src/queries/${fileName}`;
221+
222+
const document = parse(
223+
`query fooBarQuery {
224+
a {
225+
...fields
226+
}
227+
}
228+
fragment fields on A {
229+
foo
230+
bar
231+
}
232+
`
233+
);
234+
235+
const visitor = new ClientSideBaseVisitor(
236+
schema,
237+
(document.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION) as FragmentDefinitionNode[]).map(
238+
fragmentDef => ({
239+
node: fragmentDef,
240+
name: fragmentDef.name.value,
241+
onType: fragmentDef.typeCondition.name.value,
242+
isExternal: false,
243+
})
244+
),
245+
{
246+
emitLegacyCommonJSImports: true,
247+
importDocumentNodeExternallyFrom: 'near-operation-file',
248+
documentMode: DocumentMode.graphQLTag,
249+
fragmentImports: [
250+
{
251+
baseDir: '/',
252+
baseOutputDir: '',
253+
outputPath: '',
254+
importSource: {
255+
path: '~types',
256+
identifiers: [
257+
{ name: 'FieldsFragmentDoc', kind: 'document' },
258+
{ name: 'FieldsFragment', kind: 'type' },
259+
],
260+
},
261+
emitLegacyCommonJSImports: true,
262+
typesImport: false,
263+
},
264+
],
265+
},
266+
{},
267+
[{ document, location: importPath }]
268+
);
269+
270+
visitor.OperationDefinition(document.definitions[0] as OperationDefinitionNode);
271+
272+
const imports = visitor.getImports();
273+
expect(imports.some(i => i.includes('FragmentDoc'))).toBeTruthy();
274+
});
275+
});
205276
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { describe, expect, it } from '@jest/globals';
2+
import { flatten, groupBy, unique } from '../src/utils';
3+
4+
describe('utils', () => {
5+
describe('flatten', () => {
6+
it('should flatten a nested array', () => {
7+
const array = [
8+
[1, 2, 3],
9+
[4, 5, 6],
10+
[7, 8, 9],
11+
];
12+
const actual = flatten(array);
13+
const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9];
14+
expect(actual).toEqual(expected);
15+
});
16+
});
17+
18+
describe('groupBy', () => {
19+
it('should group by a property', () => {
20+
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
21+
const actual = groupBy(array, i => i % 2);
22+
const expected = { 0: [2, 4, 6, 8, 10], 1: [1, 3, 5, 7, 9] };
23+
expect(actual).toEqual(expected);
24+
});
25+
});
26+
27+
describe('unique', () => {
28+
it('should return unique items when no key selector is passed', () => {
29+
const array = [1, 2, 3, 1, 2, 4];
30+
const actual = unique(array);
31+
const expected = [1, 2, 3, 4];
32+
expect(actual).toEqual(expected);
33+
});
34+
35+
it('should return unique items based on key selector', () => {
36+
const array = [
37+
{ id: 1, name: 'Alice' },
38+
{ id: 2, name: 'Bob' },
39+
{ id: 1, name: 'Alice #2' },
40+
{ id: 3, name: 'Charlie' },
41+
];
42+
43+
const actual = unique(array, item => item.id);
44+
const expected = [
45+
{ id: 1, name: 'Alice' },
46+
{ id: 2, name: 'Bob' },
47+
{ id: 3, name: 'Charlie' },
48+
];
49+
50+
expect(actual).toEqual(expected);
51+
});
52+
});
53+
});

0 commit comments

Comments
 (0)