Skip to content

Commit ad27d75

Browse files
committed
Only include used fragments in operations
1 parent ccbf0db commit ad27d75

File tree

6 files changed

+60
-26
lines changed

6 files changed

+60
-26
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ import it in your TypeScript code.
143143
144144
```ts
145145
import { IssuesQuery } from './query.graphql.ts'
146-
````
146+
```
147147

148148
The `IssuesQuery` variable is a string with the GraphQL query. You can use it
149149
directly in your code, or pass it to a function that accepts a query.
@@ -166,7 +166,7 @@ import type { IssuesQuery } from './query.graphql.ts'
166166
<details>
167167
<summary><strong>How to get the return type of a query?</strong></summary>
168168

169-
Megaera generates TypeScript types for queries as functions.
169+
Megaera generates TypeScript types for queries as functions.
170170

171171
```ts
172172
type UserQuery = (vars: { login?: string }) => {
@@ -228,7 +228,7 @@ function query<T extends Query>(query: T, variables?: Variables<T>) {
228228
}
229229

230230
// Return type, and types of variables are inferred from the query.
231-
const {issues} = await query(IssuesQuery, {login: 'webpod'})
231+
const { issues } = await query(IssuesQuery, { login: 'webpod' })
232232
```
233233

234234
</details>

src/cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void (async function main() {
9999
'%d operation',
100100
'%d operations',
101101
)
102-
const frg = plural(content.fragments.length, '%d fragment', '%d fragments')
102+
const frg = plural(content.fragments.size, '%d fragment', '%d fragments')
103103
console.log(`> ${styleText('green', 'done')} (${ops}, ${frg})`)
104104

105105
const prefix = `// DO NOT EDIT. This is a generated file. Instead of this file, edit "${fileName}".\n\n`

src/generate.test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ function getFieldType(name: string, fieldName: string) {
2929
if (!isObjectType(type)) {
3030
throw new Error(`${name} is not object type.`)
3131
}
32-
const field = type.astNode?.fields?.find((f) => f.name.value === fieldName)?.type
32+
const field = type.astNode?.fields?.find(
33+
(f) => f.name.value === fieldName,
34+
)?.type
3335
if (!field) {
3436
throw new Error(`Cannot find ${fieldName} field in ${name}.`)
3537
}

src/generate.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { isObjectType } from 'graphql/type/index.js'
1111

1212
export function generate(content: Content) {
1313
const code: string[] = []
14-
for (const f of content.fragments) {
14+
for (const f of content.fragments.values()) {
1515
code.push(`const ${f.name} = \`#graphql
1616
${f.source}\`
1717
@@ -25,8 +25,9 @@ export type ${f.name} = ${generateSelector(f, 0, true)}
2525
}
2626

2727
let querySource = q.source
28-
for (const f of content.fragments) {
29-
querySource = '${' + f.name + '}\n' + querySource
28+
29+
for (const fName of usedFragments(q, content)) {
30+
querySource = '${' + fName + '}\n' + querySource
3031
}
3132

3233
code.push(`export const ${q.name} = \`#graphql
@@ -39,6 +40,25 @@ export type ${q.name} = (${generateVariables(q.variables)}) => ${generateSelecto
3940
return code.join('\n')
4041
}
4142

43+
function usedFragments(q: Selector, content: Content): string[] {
44+
const fragments: string[] = []
45+
for (const field of q.fields) {
46+
if (field.isFragment) {
47+
fragments.push(field.name)
48+
const fragment = content.fragments.get(field.name)
49+
if (!fragment) {
50+
throw new Error(`Fragment ${field.name} is not defined.`)
51+
}
52+
fragments.push(...usedFragments(fragment, content))
53+
}
54+
fragments.push(...usedFragments(field, content))
55+
}
56+
for (const inlineFragment of q.inlineFragments) {
57+
fragments.push(...usedFragments(inlineFragment, content))
58+
}
59+
return fragments
60+
}
61+
4262
function generateVariables(variables?: Variable[]) {
4363
if (!variables || variables.length === 0) {
4464
return ''

src/visitor.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { GraphQLNonNull, GraphQLOutputType, GraphQLType, isNonNullType } from 'graphql/type/definition.js'
2-
import { typeFromAST, TypeInfo, visitWithTypeInfo } from 'graphql/utilities/index.js'
1+
import {
2+
GraphQLOutputType,
3+
GraphQLType,
4+
isNonNullType,
5+
} from 'graphql/type/definition.js'
6+
import {
7+
typeFromAST,
8+
TypeInfo,
9+
visitWithTypeInfo,
10+
} from 'graphql/utilities/index.js'
311
import { parse, print, Source, visit } from 'graphql/language/index.js'
412
import { GraphQLSchema } from 'graphql/type/index.js'
513
import { GraphQLError } from 'graphql/error/index.js'
@@ -23,7 +31,7 @@ export type Selector = {
2331

2432
export type Content = {
2533
operations: Selector[]
26-
fragments: Selector[]
34+
fragments: Map<string, Selector>
2735
}
2836

2937
export function traverse(schema: GraphQLSchema, source: Source): Content {
@@ -32,19 +40,19 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
3240

3341
const content: Content = {
3442
operations: [],
35-
fragments: []
43+
fragments: new Map(),
3644
}
3745

3846
const stack: Selector[] = []
3947

4048
const visitor = visitWithTypeInfo(typeInfo, {
4149
OperationDefinition: {
42-
enter: function(node) {
50+
enter: function (node) {
4351
if (node.name === undefined) {
4452
throw new GraphQLError(
4553
firstLetterUpper(node.operation) + ' name is required',
4654
node,
47-
source
55+
source,
4856
)
4957
}
5058
checkUnique(node.name.value, content)
@@ -55,7 +63,7 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
5563
variables.push({
5664
name: v.variable.name.value,
5765
type: type,
58-
required: v.defaultValue === undefined && isNonNullType(type)
66+
required: v.defaultValue === undefined && isNonNullType(type),
5967
})
6068
}
6169

@@ -65,15 +73,15 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
6573
fields: [],
6674
inlineFragments: [],
6775
variables: variables,
68-
source: print(node)
76+
source: print(node),
6977
}
7078

7179
stack.push(s)
7280
content.operations.push(s)
7381
},
7482
leave() {
7583
stack.pop()
76-
}
84+
},
7785
},
7886

7987
FragmentDefinition: {
@@ -85,15 +93,15 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
8593
type: typeInfo.getType() ?? undefined,
8694
fields: [],
8795
inlineFragments: [],
88-
source: print(node)
96+
source: print(node),
8997
}
9098

9199
stack.push(s)
92-
content.fragments.push(s)
100+
content.fragments.set(s.name, s)
93101
},
94102
leave() {
95103
stack.pop()
96-
}
104+
},
97105
},
98106

99107
Field: {
@@ -109,7 +117,7 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
109117
},
110118
leave() {
111119
stack.pop()
112-
}
120+
},
113121
},
114122

115123
FragmentSpread: {
@@ -121,13 +129,17 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
121129
fields: [],
122130
inlineFragments: [],
123131
})
124-
}
132+
},
125133
},
126134

127135
InlineFragment: {
128136
enter(node) {
129137
if (!node.typeCondition) {
130-
throw new GraphQLError('Inline fragment must have type condition.', node, source)
138+
throw new GraphQLError(
139+
'Inline fragment must have type condition.',
140+
node,
141+
source,
142+
)
131143
}
132144
const s: Selector = {
133145
name: node.typeCondition.name.value,
@@ -141,7 +153,7 @@ export function traverse(schema: GraphQLSchema, source: Source): Content {
141153
leave() {
142154
stack.pop()
143155
},
144-
}
156+
},
145157
})
146158

147159
visit(ast, visitor)
@@ -153,7 +165,7 @@ function checkUnique(name: string, content: Content) {
153165
if (content.operations.find((o) => o.name === name)) {
154166
throw new GraphQLError(`Operation with name "${name}" is already defined.`)
155167
}
156-
if (content.fragments.find((f) => f.name === name)) {
168+
if (content.fragments.has(name)) {
157169
throw new GraphQLError(`Fragment with name "${name}" is already defined.`)
158170
}
159171
}

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"strict": true,
88
"outDir": "./dist",
99
"declaration": true,
10-
"allowJs": true,
10+
"allowJs": true
1111
},
1212
"include": ["./src/**/*"]
1313
}

0 commit comments

Comments
 (0)