@graphcommerce/graphql-codegen-relay-optimizer-plugin 2.101.3 → 2.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -5
- package/src/__tests__/index.spec.ts +149 -0
- package/src/index.ts +104 -0
- package/src/tyes.d.ts +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphcommerce/graphql-codegen-relay-optimizer-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.102.0",
|
|
4
4
|
"description": "GraphQL Code Generator plugin for optimizing your GraphQL queries relay style.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/main/index.js",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"relay-compiler": "12.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@graphcommerce/eslint-config-pwa": "^3.0.
|
|
23
|
+
"@graphcommerce/eslint-config-pwa": "^3.0.3",
|
|
24
24
|
"@graphcommerce/prettier-config-pwa": "^3.0.2",
|
|
25
25
|
"@graphql-codegen/testing": "1.17.7",
|
|
26
|
-
"@types/jest": "27.0.
|
|
26
|
+
"@types/jest": "27.0.2",
|
|
27
27
|
"@types/relay-compiler": "8.0.1",
|
|
28
28
|
"graphql": "^15.6.0",
|
|
29
29
|
"jest": "27.2.2",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"build:module": "tsc --target es2017 --outDir dist/module",
|
|
37
37
|
"build:main": "tsc --target es5 --outDir dist/main",
|
|
38
38
|
"build": "yarn build:module && yarn build:main",
|
|
39
|
-
"
|
|
39
|
+
"prepack": "yarn build",
|
|
40
|
+
"postinstall": "yarn build"
|
|
40
41
|
},
|
|
41
42
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
42
43
|
"browserslist": [
|
|
@@ -53,8 +54,9 @@
|
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"dist/**/*",
|
|
57
|
+
"src",
|
|
56
58
|
"LICENSE",
|
|
57
59
|
"README.md"
|
|
58
60
|
],
|
|
59
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "1345d9b55763894d3cdedb5751895f2d3f89d1b4"
|
|
60
62
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-ignore */
|
|
2
|
+
import '@graphql-codegen/testing'
|
|
3
|
+
|
|
4
|
+
import { Types } from '@graphql-codegen/plugin-helpers'
|
|
5
|
+
import { buildSchema, parse, print, ASTNode } from 'graphql'
|
|
6
|
+
import { plugin } from '..'
|
|
7
|
+
|
|
8
|
+
const testSchema = buildSchema(/* GraphQL */ `
|
|
9
|
+
type Avatar {
|
|
10
|
+
id: ID!
|
|
11
|
+
url: String!
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type User {
|
|
15
|
+
id: ID!
|
|
16
|
+
login: String!
|
|
17
|
+
avatar(height: Int!, width: Int!): Avatar
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Query {
|
|
21
|
+
user: User!
|
|
22
|
+
users: [User!]!
|
|
23
|
+
}
|
|
24
|
+
`)
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line jest/expect-expect
|
|
27
|
+
it('can be called', async () => {
|
|
28
|
+
const testDocument = parse(/* GraphQL */ `
|
|
29
|
+
query user {
|
|
30
|
+
user {
|
|
31
|
+
id
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`)
|
|
35
|
+
await plugin(testSchema, [{ document: testDocument }], {})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line jest/expect-expect
|
|
39
|
+
it('can be called with queries that include connection fragments', async () => {
|
|
40
|
+
const testDocument = parse(/* GraphQL */ `
|
|
41
|
+
query user {
|
|
42
|
+
users @connection(key: "foo") {
|
|
43
|
+
id
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`)
|
|
47
|
+
await plugin(testSchema, [{ document: testDocument }], {})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('can inline @argumentDefinitions/@arguments annotated fragments', async () => {
|
|
51
|
+
const fragmentDocument = parse(/* GraphQL */ `
|
|
52
|
+
fragment UserLogin on User
|
|
53
|
+
@argumentDefinitions(
|
|
54
|
+
height: { type: "Int", defaultValue: 10 }
|
|
55
|
+
width: { type: "Int", defaultValue: 10 }
|
|
56
|
+
) {
|
|
57
|
+
id
|
|
58
|
+
login
|
|
59
|
+
avatar(width: $width, height: $height) {
|
|
60
|
+
id
|
|
61
|
+
url
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`)
|
|
65
|
+
const queryDocument = parse(/* GraphQL */ `
|
|
66
|
+
query user {
|
|
67
|
+
users {
|
|
68
|
+
...UserLogin @arguments(height: 30, width: 30)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
`)
|
|
72
|
+
const input: Types.DocumentFile[] = [{ document: fragmentDocument }, { document: queryDocument }]
|
|
73
|
+
await plugin(testSchema, input, {})
|
|
74
|
+
const queryDoc = input.find((doc) => doc.document?.definitions[0].kind === 'OperationDefinition')
|
|
75
|
+
|
|
76
|
+
expect(queryDoc).toBeDefined()
|
|
77
|
+
expect(print(queryDoc?.document as ASTNode)).toBeSimilarStringTo(/* GraphQL */ `
|
|
78
|
+
query user {
|
|
79
|
+
users {
|
|
80
|
+
id
|
|
81
|
+
login
|
|
82
|
+
avatar(width: 30, height: 30) {
|
|
83
|
+
id
|
|
84
|
+
url
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('handles unions with interfaces the correct way', async () => {
|
|
92
|
+
const schema = buildSchema(/* GraphQL */ `
|
|
93
|
+
type User {
|
|
94
|
+
id: ID!
|
|
95
|
+
login: String!
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface Error {
|
|
99
|
+
message: String!
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type UserNotFoundError implements Error {
|
|
103
|
+
message: String!
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type UserBlockedError implements Error {
|
|
107
|
+
message: String!
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
union UserResult = User | UserNotFoundError | UserBlockedError
|
|
111
|
+
|
|
112
|
+
type Query {
|
|
113
|
+
user: UserResult!
|
|
114
|
+
}
|
|
115
|
+
`)
|
|
116
|
+
|
|
117
|
+
const queryDocument = parse(/* GraphQL */ `
|
|
118
|
+
query user {
|
|
119
|
+
user {
|
|
120
|
+
... on User {
|
|
121
|
+
id
|
|
122
|
+
login
|
|
123
|
+
}
|
|
124
|
+
... on Error {
|
|
125
|
+
message
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
`)
|
|
130
|
+
|
|
131
|
+
const input: Types.DocumentFile[] = [{ document: queryDocument }]
|
|
132
|
+
await plugin(schema, input, {})
|
|
133
|
+
const queryDoc = input.find((doc) => doc.document?.definitions[0].kind === 'OperationDefinition')
|
|
134
|
+
|
|
135
|
+
expect(queryDoc).toBeDefined()
|
|
136
|
+
expect(print(queryDoc?.document as ASTNode)).toBeSimilarStringTo(/* GraphQL */ `
|
|
137
|
+
query user {
|
|
138
|
+
user {
|
|
139
|
+
... on User {
|
|
140
|
+
id
|
|
141
|
+
login
|
|
142
|
+
}
|
|
143
|
+
... on Error {
|
|
144
|
+
message
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
`)
|
|
149
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import { Types, PluginFunction } from '@graphql-codegen/plugin-helpers'
|
|
3
|
+
import { GraphQLSchema, parse, printSchema, DefinitionNode, visit } from 'graphql'
|
|
4
|
+
|
|
5
|
+
import { Parser as RelayParser } from 'relay-compiler'
|
|
6
|
+
import CompilerContext from 'relay-compiler/lib/core/CompilerContext'
|
|
7
|
+
import { print as relayPrint } from 'relay-compiler/lib/core/IRPrinter'
|
|
8
|
+
import { create as relayCreate } from 'relay-compiler/lib/core/Schema'
|
|
9
|
+
|
|
10
|
+
import { transform as applyFragmentArgumentTransform } from 'relay-compiler/lib/transforms/ApplyFragmentArgumentTransform'
|
|
11
|
+
import { transformWithOptions as flattenTransformWithOptions } from 'relay-compiler/lib/transforms/FlattenTransform'
|
|
12
|
+
import { transform as inlineFragmentsTransform } from 'relay-compiler/lib/transforms/InlineFragmentsTransform'
|
|
13
|
+
import { transform as skipRedundantNodesTransform } from 'relay-compiler/lib/transforms/SkipRedundantNodesTransform'
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
16
|
+
export interface RelayOptimizerPluginConfig {}
|
|
17
|
+
|
|
18
|
+
function isFragment(documentFile: Types.DocumentFile) {
|
|
19
|
+
let name = false
|
|
20
|
+
|
|
21
|
+
visit(documentFile.document!, {
|
|
22
|
+
enter: {
|
|
23
|
+
FragmentDefinition: () => {
|
|
24
|
+
name = true
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
return name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const plugin: PluginFunction<RelayOptimizerPluginConfig> = (
|
|
32
|
+
schema: GraphQLSchema,
|
|
33
|
+
documents: Types.DocumentFile[],
|
|
34
|
+
_config: RelayOptimizerPluginConfig,
|
|
35
|
+
) => {
|
|
36
|
+
const isFrag = documents.every((d) => isFragment(d))
|
|
37
|
+
|
|
38
|
+
if (isFrag) return { content: '' }
|
|
39
|
+
|
|
40
|
+
// @TODO way for users to define directives they use, otherwise relay will throw an unknown directive error
|
|
41
|
+
// Maybe we can scan the queries and add them dynamically without users having to do some extra stuff
|
|
42
|
+
// transformASTSchema creates a new schema instance instead of mutating the old one
|
|
43
|
+
const adjustedSchema = relayCreate(printSchema(schema)).extend([
|
|
44
|
+
/* GraphQL */ `
|
|
45
|
+
directive @connection(key: String!, filter: [String!]) on FIELD
|
|
46
|
+
directive @client on FIELD
|
|
47
|
+
`,
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
const documentAsts = documents.reduce(
|
|
51
|
+
(prev, v) => [...prev, ...(v.document?.definitions ?? [])],
|
|
52
|
+
[] as DefinitionNode[],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const relayDocuments = RelayParser.transform(adjustedSchema, documentAsts)
|
|
56
|
+
|
|
57
|
+
const fragmentCompilerContext = new CompilerContext(adjustedSchema).addAll(relayDocuments)
|
|
58
|
+
|
|
59
|
+
const fragmentDocuments = fragmentCompilerContext
|
|
60
|
+
.applyTransforms([
|
|
61
|
+
applyFragmentArgumentTransform,
|
|
62
|
+
flattenTransformWithOptions({ flattenAbstractTypes: false }),
|
|
63
|
+
skipRedundantNodesTransform,
|
|
64
|
+
])
|
|
65
|
+
.documents()
|
|
66
|
+
.filter((doc) => doc.kind === 'Fragment')
|
|
67
|
+
|
|
68
|
+
const queryCompilerContext = new CompilerContext(adjustedSchema)
|
|
69
|
+
.addAll(relayDocuments)
|
|
70
|
+
.applyTransforms([
|
|
71
|
+
applyFragmentArgumentTransform,
|
|
72
|
+
inlineFragmentsTransform,
|
|
73
|
+
flattenTransformWithOptions({ flattenAbstractTypes: false }),
|
|
74
|
+
skipRedundantNodesTransform,
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
const newQueryDocuments: Types.DocumentFile[] = queryCompilerContext.documents().map((doc) => ({
|
|
78
|
+
location: 'optimized by relay',
|
|
79
|
+
document: parse(relayPrint(adjustedSchema, doc)),
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
let newDocuments: Types.DocumentFile[] = []
|
|
83
|
+
if (newQueryDocuments.length === 0) {
|
|
84
|
+
return { content: '' }
|
|
85
|
+
}
|
|
86
|
+
if (newQueryDocuments.length === 1) {
|
|
87
|
+
newDocuments = newQueryDocuments
|
|
88
|
+
} else {
|
|
89
|
+
newDocuments = [
|
|
90
|
+
...fragmentDocuments.map((doc) => ({
|
|
91
|
+
location: 'optimized by relay',
|
|
92
|
+
document: parse(relayPrint(adjustedSchema, doc)),
|
|
93
|
+
})),
|
|
94
|
+
...newQueryDocuments,
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
documents.splice(0, documents.length)
|
|
99
|
+
documents.push(...newDocuments)
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
content: '',
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/tyes.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare module 'relay-compiler/lib/core/Schema' {
|
|
2
|
+
export function create(schema: string): import('relay-compiler').Schema
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
declare module 'relay-compiler/lib/core/IRPrinter' {
|
|
6
|
+
export function print(schema: import('relay-compiler').Schema, document: any): string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module 'relay-compiler/lib/core/CompilerContext' {
|
|
10
|
+
let CompilerContext: typeof import('relay-compiler').CompilerContext
|
|
11
|
+
export = CompilerContext
|
|
12
|
+
}
|