Skip to content

Commit 2b22acb

Browse files
authored
[near-operation-file-preset] Add presetConfig.filePerOperation to generate one file per operation (#1399)
1 parent 1b13db4 commit 2b22acb

12 files changed

Lines changed: 248 additions & 9 deletions

.changeset/afraid-buckets-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-codegen/near-operation-file-preset': minor
3+
---
4+
5+
Add `filePerOperation` config to generate filename based on named operation or fragment, instead of source filename

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"coverage": true,
1818
"npm": true
1919
},
20-
"typescript.tsdk": "node_modules/typescript/lib",
20+
"js/ts.tsdk.path": "node_modules/typescript/lib",
2121
"editor.formatOnSave": true,
2222
"editor.defaultFormatter": "esbenp.prettier-vscode",
2323
"[typescript]": {

packages/presets/near-operation-file/src/fragment-resolver.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function buildFragmentRegistry(
8989
const registry = documents.reduce<FragmentRegistry>((prev: FragmentRegistry, documentRecord) => {
9090
const fragments: FragmentDefinitionNode[] = documentRecord.document.definitions.filter(
9191
d => d.kind === Kind.FRAGMENT_DEFINITION,
92-
) as FragmentDefinitionNode[];
92+
);
9393

9494
for (const fragment of fragments) {
9595
const schemaType = schemaObject.getType(fragment.typeCondition.name.value);
@@ -100,8 +100,12 @@ function buildFragmentRegistry(
100100
);
101101
}
102102

103+
const filePath = generateFilePath({
104+
location: documentRecord.location,
105+
meta: { operations: [], fragments: [fragment] },
106+
});
107+
103108
const fragmentName = fragment.name.value;
104-
const filePath = generateFilePath(documentRecord.location);
105109
const possibleTypes = getPossibleTypes(schemaObject, schemaType);
106110
const possibleTypeNames = possibleTypes.map(t => t.name);
107111
const imports = createFragmentImports(baseVisitor, fragment.name.value, possibleTypeNames);

packages/presets/near-operation-file/src/index.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export type NearOperationFileConfig = {
177177
folder?: string;
178178
/**
179179
* @description Optional, override the name of the import namespace used to import from the `baseTypesPath` file.
180-
* @default Types
180+
* @default Types (if `baseTypesPath` is set)
181181
*
182182
* @exampleMarkdown
183183
* ```ts filename="codegen.ts" {11}
@@ -200,6 +200,30 @@ export type NearOperationFileConfig = {
200200
* ```
201201
*/
202202
importTypesNamespace?: string;
203+
204+
/**
205+
* @description Optional, generates one file per operation, using the operation name as the filename. Note: if your documents are in `.graphql` files and there are multiple operations or fragments in a single file, the generated filename will be based on the first operation or fragment found.
206+
* @default false
207+
*
208+
* @exampleMarkdown
209+
* ```ts filename="codegen.ts" {11}
210+
* import type { CodegenConfig } from '@graphql-codegen/cli';
211+
* const config: CodegenConfig = {
212+
* // ...
213+
* generates: {
214+
* 'path/to/file.ts': {
215+
* preset: 'near-operation-file',
216+
* presetConfig: {
217+
* filePerOperation: true
218+
* },
219+
* plugins: ['typescript-operations'],
220+
* },
221+
* },
222+
* };
223+
* export default config;
224+
* ```
225+
*/
226+
filePerOperation?: boolean;
203227
};
204228

205229
export type FragmentNameToFile = {
@@ -226,6 +250,7 @@ export const preset: Types.OutputPreset<NearOperationFileConfig> = {
226250
(options.presetConfig.baseTypesPath ? 'Types' : undefined); // When there is `baseTypesPath`, we assume there'd be a type import, so we default `importTypesNamespace` value to `Types` for convenience.
227251
const importAllFragmentsFrom: FragmentImportFromFn | string | null =
228252
options.presetConfig.importAllFragmentsFrom || null;
253+
const filePerOperation = options.presetConfig.filePerOperation || false;
229254

230255
const shouldAbsolute = !baseTypesPath.startsWith('~');
231256

@@ -239,10 +264,20 @@ export const preset: Types.OutputPreset<NearOperationFileConfig> = {
239264
schemaObject,
240265
{
241266
baseDir,
242-
generateFilePath(location: string) {
267+
generateFilePath({ location, meta }) {
243268
const newFilePath = defineFilepathSubfolder(location, folder);
244269

245-
return appendFileNameToFilePath(newFilePath, fileName, extension);
270+
let customFilename = fileName;
271+
if (filePerOperation) {
272+
// Note: If a file only has unnamed operations (i.e. undefined operation name) or fragment (i.e. undefined fragment name).
273+
// In such case, the generated filename will be based on the source document file.
274+
customFilename =
275+
meta['operations'][0]?.name?.value ||
276+
meta['fragments'][0]?.name?.value ||
277+
customFilename;
278+
}
279+
280+
return appendFileNameToFilePath(newFilePath, customFilename, extension);
246281
},
247282
schemaTypesSource: {
248283
path: shouldAbsolute ? join(options.baseOutputDir, baseTypesPath) : baseTypesPath,

packages/presets/near-operation-file/src/resolve-document-imports.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { FragmentDefinitionNode, GraphQLSchema } from 'graphql';
1+
import {
2+
GraphQLSchema,
3+
Kind,
4+
type FragmentDefinitionNode,
5+
type OperationDefinitionNode,
6+
} from 'graphql';
27
import { isUsingTypes, Types } from '@graphql-codegen/plugin-helpers';
38
import {
49
FragmentImport,
@@ -25,7 +30,13 @@ export type DocumentImportResolverOptions = {
2530
/**
2631
* Generates a target file path from the source `document.location`
2732
*/
28-
generateFilePath: (location: string) => string;
33+
generateFilePath: (params: {
34+
location: string;
35+
meta: {
36+
operations: OperationDefinitionNode[];
37+
fragments: FragmentDefinitionNode[];
38+
};
39+
}) => string;
2940
/**
3041
* Schema base types source
3142
*/
@@ -69,7 +80,22 @@ export function resolveDocumentImports<T>(
6980

7081
return documents.map(documentFile => {
7182
try {
72-
const generatedFilePath = generateFilePath(documentFile.location);
83+
const meta: {
84+
operations: OperationDefinitionNode[];
85+
fragments: FragmentDefinitionNode[];
86+
} = {
87+
operations: [],
88+
fragments: [],
89+
};
90+
for (const definition of documentFile.document.definitions) {
91+
if (definition.kind === Kind.OPERATION_DEFINITION) {
92+
meta.operations.push(definition);
93+
} else if (definition.kind === Kind.FRAGMENT_DEFINITION) {
94+
meta.fragments.push(definition);
95+
}
96+
}
97+
const generatedFilePath = generateFilePath({ location: documentFile.location, meta });
98+
7399
const importStatements: string[] = [];
74100
const { externalFragments, fragmentImports } = resolveFragments(
75101
generatedFilePath,
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export const user1a = /* GraphQL */ `
2+
query User1a {
3+
user1a: user {
4+
id
5+
}
6+
}
7+
`;
8+
export const user1b = /* GraphQL */ `
9+
query User1b {
10+
user1b: user {
11+
id
12+
name
13+
}
14+
}
15+
`;
16+
17+
export const anon = /* GraphQL */ `
18+
query {
19+
anon: user {
20+
__typename
21+
}
22+
}
23+
`;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const user2 = /* GraphQL */ `
2+
query User2 {
3+
user2: user {
4+
...UserFragment3
5+
}
6+
}
7+
`;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const userFragment3 = /* GraphQL */ `
2+
fragment UserFragment3 on User {
3+
id
4+
}
5+
`;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
query User4($id: ID!) {
2+
user4: user {
3+
name
4+
}
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const userFragment5a = /* GraphQL */ `
2+
fragment UserFragment5a on User {
3+
id
4+
}
5+
`;
6+
7+
export const user5b = /* GraphQL */ `
8+
query User5b {
9+
user1a: user {
10+
id
11+
}
12+
}
13+
`;

0 commit comments

Comments
 (0)