Skip to content

Commit 030100a

Browse files
authored
Migration guide: typescript-operations and client-preset to v6.0 (#10599)
* Draft migration guide * Update next-env * Add notes * Add callout * Update doc meta * Format * Minor text updates * Improve generateOperationTypes and importSchemaTypesFrom comments * Fix typo * Add breaking change note about changing of the default ID type * Clean up
1 parent 8ae0fdd commit 030100a

3 files changed

Lines changed: 340 additions & 1 deletion

File tree

website/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
/// <reference types="next/image-types/global" />
33

44
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/basic-features/typescript for more information.
5+
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

website/src/pages/docs/migration/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export default {
33
'from-0-18': 'v0.18 -> v1.0',
44
'from-0-13': 'v0.13 -> v0.17',
55
'from-4-0': 'v4.0 -> v5.0',
6+
'operations-and-client-preset-from-5-0': 'typescript-operations and client-preset v5.0 -> v6.0',
67
};
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
---
2+
description: Migrating `typescript-operations` and `client-preset` from v5 to v6. What has changed? How to migrate? What are the new features?
3+
---
4+
5+
<style global jsx>
6+
{`
7+
[data-highlighted-line-id='lineremoved'] {
8+
background: #7f1d1d4d;
9+
}
10+
`}
11+
</style>
12+
13+
import { Callout } from '@theguild/components'
14+
15+
# Migrating to `typescript-operations` and `client-preset` v6.0
16+
17+
<Callout type="default">
18+
This major version has not been released yet. You can find upcoming changes and alpha releases in the [feature
19+
branch](https://github.com/dotansimha/graphql-code-generator/pull/10496).
20+
</Callout>
21+
22+
## What's new?
23+
24+
`typescript-operations` and `client-preset` v6.0 come with a major overhaul of type generation and config to improve developer experience.
25+
26+
1. Type generation and usage changes
27+
2. Configuration and dependency changes
28+
3. Other bug fixes and quality of life improvements
29+
30+
For the most important changes, read the [Breaking changes](#breaking-changes) section.
31+
32+
For a full list of changes, see the CHANGELOG.
33+
34+
## Installation
35+
36+
Install the latest versions of the official plugins in your dependencies:
37+
38+
```sh npm2yarn
39+
npm i -D @graphql-codegen/cli@latest @graphql-codegen/typescript-operations@latest @graphql-codegen/client-preset@latest
40+
```
41+
42+
<Callout type="warning">
43+
GraphQL Codegen packages share a lot of code internally, so if you have explicitly installed other official packages
44+
(such as `@graphql-codegen/visitor-plugin-common` or `@graphql-codegen/typescript-resolvers`) in the same repo, be
45+
sure to update them at the same time to avoid unexpected issues.
46+
</Callout>
47+
48+
## Migration
49+
50+
### `client-preset`
51+
52+
`client-preset` already applies the recommended setup, so you won't have to make any changes to the default config:
53+
54+
```typescript filename="codegen.ts"
55+
const config: CodegenConfig = {
56+
// ...
57+
generates: {
58+
'src/gql/': {
59+
preset: 'client'
60+
}
61+
}
62+
}
63+
```
64+
65+
### `typescript-operations`
66+
67+
`typescript-operations` can be used in a variety of custom setups. This section explains the changes in the most popular setup.
68+
69+
#### One-file setup
70+
71+
Previously, this setup required the `typescript` plugin and generated all schema and Operation types into a single file.
72+
73+
Now, you can remove the `typescript` plugin, as `typescript-operations` works by itself. It also only generates Input, Enum, and Operation types that are actually used.
74+
75+
```typescript filename="codegen.ts" {5}#lineremoved {6}
76+
const config: CodegenConfig = {
77+
// ...
78+
generates: {
79+
'src/graphql/types.generated.ts': {
80+
plugins: ['typescript', 'typescript-operations'],
81+
plugins: ['typescript-operations']
82+
}
83+
}
84+
}
85+
```
86+
87+
#### Multi-file setup
88+
89+
Some repos may have multiple Codegen projects, each generating types for operations within its scope. In such cases, users may want to reuse the base Input and Enum types generated by the `typescript` plugin with the [import-types preset](https://the-guild.dev/graphql/codegen/plugins/presets/import-types-preset):
90+
91+
```typescript filename="codegen.ts"
92+
const config: CodegenConfig = {
93+
// ...
94+
generates: {
95+
'src/shared/base-types.generated.ts': {
96+
plugins: ['typescript']
97+
},
98+
'src/project-1/types.generated.ts': {
99+
documents: 'src/project-1/**/*.graphql.ts',
100+
preset: 'import-types',
101+
plugins: ['typescript-operations'],
102+
presetConfig: {
103+
typesPath: '../shared/base-types.generated.ts'
104+
}
105+
},
106+
'src/project-2/types.generated.ts': {
107+
documents: 'src/project-2/**/*.graphql.ts',
108+
preset: 'import-types',
109+
plugins: ['typescript-operations'],
110+
presetConfig: {
111+
typesPath: '../shared/base-types.generated.ts'
112+
}
113+
}
114+
}
115+
}
116+
```
117+
118+
Now, it is possible to do this with just `typescript-operations`, as it supports this approach using its own `generateOperationTypes` and `importSchemaTypesFrom` options:
119+
120+
```typescript filename="codegen.ts" {8} {15} {22}
121+
const config: CodegenConfig = {
122+
// ...
123+
generates: {
124+
'src/shared/base-types.generated.ts': {
125+
documents: 'src/**/*.graphql.ts' // Parses all files with GraphQL documents to generate Enum and Input types that are used by every project
126+
plugins: ['typescript-operations'],
127+
config: {
128+
generateOperationTypes: false, // `generateOperationTypes:false` means only Input, Enum and shared utility types are generated
129+
}
130+
},
131+
'src/project-1/types.generated.ts': {
132+
documents: 'src/project-1/**/*.graphql.ts', // Only parses GraphQL documents within project-1's scope
133+
plugins: ['typescript-operations'],
134+
config: {
135+
importSchemaTypesFrom: 'src/shared/base-types.generated.ts', // this path is relative to Codegen config location (unlike `typesPath` in the old setup)
136+
}
137+
},
138+
'src/project-2/types.generated.ts': {
139+
documents: 'src/project-2/**/*.graphql.ts', // Only parses GraphQL documents within project-2's scope
140+
plugins: ['typescript-operations'],
141+
config: {
142+
importSchemaTypesFrom: 'src/shared/base-types.generated.ts',
143+
},
144+
}
145+
}
146+
}
147+
```
148+
149+
## Breaking changes
150+
151+
1. Object types are no longer generated
152+
153+
Previously, Object types from the schema were generated via the `typescript` plugin, for example:
154+
155+
```ts
156+
// Example of a schema User Object type being previously generated
157+
export type User = {
158+
__typename?: 'User'
159+
id: Scalars['ID']['output']
160+
name: Scalars['String']['output']
161+
}
162+
```
163+
164+
These types contain _all_ the fields from the schema. However, GraphQL operations are _not_ expected to fetch all fields, so Object types should never be used. Instead, Operation types (Variables and Result) are generated based on the fields in the documents so these should be used.
165+
166+
In reality, generated Object types were often used (intentionally or accidentally) in application code because they were generated.
167+
168+
Now, Object types are no longer generated. This prevents accidental misuse of schema types in client code and ensures all types accurately reflect actual query selections.
169+
170+
If you need schema types for any reason, please generate them using the `typescript` plugin in a separate file.
171+
172+
2. Args types are no longer generated
173+
174+
Args types are only used for server use cases, so they are no longer generated for client use cases.
175+
176+
3. Scalar types are no longer generated as a reusable type
177+
178+
Previously, Scalar types from the schema were generated into an object and reused in Variables types:
179+
180+
```typescript
181+
// All native and custom scalars found in the schema were previously generated
182+
export type Scalars = {
183+
ID: { input: string | number; output: string }
184+
String: { input: string; output: string }
185+
Boolean: { input: boolean; output: boolean }
186+
Int: { input: number; output: number }
187+
Float: { input: number; output: number }
188+
}
189+
```
190+
191+
Now, scalars in Input and Variables types are consistently inlined (similar to Result types) to avoid the `Scalar` utility type:
192+
193+
```typescript filename="types.generated.ts" {1-7}#lineremoved {10}#lineremoved {11} {15}#lineremoved {16}
194+
export type Scalars = {
195+
ID: { input: string | number; output: string }
196+
String: { input: string; output: string }
197+
Boolean: { input: boolean; output: boolean }
198+
Int: { input: number; output: number }
199+
Float: { input: number; output: number }
200+
}
201+
202+
export type UserInput = {
203+
id: Scalars['ID']['input']
204+
id: string | number
205+
}
206+
207+
export type UserVariables = Exact<{
208+
id: Scalars['ID']['input']
209+
id: string | number
210+
}>
211+
```
212+
213+
4. Input and Enum types are only generated when used
214+
215+
Previously, all Input and Enum types were generated, even if they were not used. This could increase bundle size when Enums that incur runtime cost (e.g. native TypeScript enum or const enum) are used.
216+
217+
Now, only Input and Enum types used in operations are generated.
218+
219+
5. `__typename` is only generated when used
220+
221+
Previously, `__typename` fields in Result types are generated as optional by default, even when they were not requested:
222+
223+
```graphql
224+
query User {
225+
user {
226+
# Note: __typename is not in the selection set
227+
id
228+
}
229+
}
230+
```
231+
232+
Previously, the above operation resulted in a type with optional `__typename`:
233+
234+
```typescript {3}
235+
export type UserQuery = {
236+
user: {
237+
__typename?: 'User'
238+
id: string
239+
}
240+
}
241+
```
242+
243+
Now, `__typename` is not generated by default when it is not in the selection set:
244+
245+
```typescript {3}#lineremoved
246+
export type UserQuery = {
247+
user: {
248+
__typename?: 'User'
249+
id: string
250+
}
251+
}
252+
```
253+
254+
<Callout>
255+
Some clients, such as Apollo Client, automatically request `__typename`. To achieve the same behaviour, you can
256+
continue to use `skipTypeNameForRoot` and `nonOptionalTypename` options to configure expected type behaviours.
257+
</Callout>
258+
259+
6. Document field types are generated to correctly match runtime expectations
260+
261+
Previously, nullable fields in Result types were generated as optional by default:
262+
263+
```typescript {3}
264+
export type UserQuery = {
265+
user: {
266+
age?: string | null
267+
}
268+
}
269+
```
270+
271+
Now, nullable fields in Result types are never optional (except in some cases e.g. when `@defer`, `@skip`, or `@include` are used)
272+
273+
```typescript {3}#lineremoved {4}
274+
export type UserQuery = {
275+
user: {
276+
age?: string | null
277+
age: string | null
278+
}
279+
}
280+
```
281+
282+
7. Enum config options are consolidated and the default value is changed
283+
284+
Previously, there were 4 boolean options to set which Enum variant to generate. When combined, these options overrode one another, leading to unexpected and confusing behaviour.
285+
286+
Now, `enumType` is the only config option to use. The default has also been changed to `string-literal`, as it is the only option that does not incur runtime cost.
287+
288+
| Enum type | Examples | Previous config | New config |
289+
| -------------- | ---------------------------------------------------------------------------- | ---------------------------- | ------------------------------------- |
290+
| String literal | `type UserRole = 'Admin' \| 'Customer'` | `{enumsAsTypes:true}` | `{}` or `{enumType:'string-literal'}` |
291+
| Const | `export const UserRole = { Admin: 'ADMIN', Customer: 'CUSTOMER' } as const;` | `{enumsAsConst:true}` | `{enumType:'const'}` |
292+
| Native | `export enum UserRole { Admin = 'ADMIN', Customer = 'CUSTOMER' };` | `{}` or `{constEnums:false}` | `{enumType:'native'}` |
293+
| Native const | `export const enum UserRole { Admin = 'ADMIN', Customer = 'CUSTOMER' };` | `{constEnums:true}` | `{enumType:'native-const'}` |
294+
| Native numeric | `export enum UserRole { Admin = 0, Customer = 1 }` | `{numericEnums:true}` | `{enumType:'native-numeric'}` |
295+
296+
8. `avoidOptionals` option is updated to only handle Operation types
297+
298+
Previously, `avoidOptionals` was shared with the [typescript plugin](https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#avoidoptionals). As a result, some inner options did not affect Operation types (such as `avoidOptionals.resolvers`, `avoidOptionals.query`, `avoidOptionals.mutation`, `avoidOptionals.subscription`).
299+
300+
Now, there are only 3 inner options, and when enabled, each forces the respective use case to pass non-optional values.
301+
302+
- `avoidOptionals.variableValue`
303+
- `avoidOptionals.inputValue`
304+
- `avoidOptionals.defaultValue`
305+
306+
Note that the default is `false`, and you can still use `avoidOptionals:true` to turn on all options, without having to set each one individually.
307+
308+
9. `preResolveTypes` option is removed
309+
310+
The `preResolveTypes` option was used to generate Result types inline (`preResolveTypes:false`) or use the ones generated by the `typescript` plugin (`preResolveTypes:true`). This approach had several drawbacks:
311+
312+
1. it added dependency to the `typescript` plugin
313+
2. `true` and `false` had no functional difference for users
314+
3. keeping this option doubled the maintenance overhead with no real benefits
315+
316+
`preResolveTypes:true` has been the default (and very stable) for a long time. It should be used by the majority of users by now. So, removing this option is expected to have zero or minimal impact on users and reduce the maintenance burden.
317+
318+
If you are seeing problems, please create an issue [here](https://github.com/dotansimha/graphql-code-generator/issues).
319+
320+
10. Legacy utility types are removed
321+
322+
The following utility types have been removed:
323+
324+
- `Maybe`: used to handle nullability types of fields in Result types. However, field types have been pre-resolved and inlined for a long time, so this type is no longer needed.
325+
- `InputMaybe`: used to handle nullability types of Input. Input types are now inlined, so this type is no longer needed.
326+
- `MakeOptional`, `MakeMaybe` and `MakeEmpty`: used to handle `preResolveTypes:false`. However, `preResolveTypes` has been removed, so these types are no longer needed.
327+
328+
11. Make `unknown` the default type for custom scalars instead of `any`
329+
330+
Previously, the default custom Scalars type was `any`, which bypassed typechecking.
331+
332+
Now, the default type is `unknown` to ensure data is handled carefully by users.
333+
334+
12. Make `string | number` the default type for the native `ID` scalar
335+
336+
Previously, one set of default Scalar types was shared between client and server plugins via the `typescript` plugin dependency. This meant it was not possible to set the default for client, as it would complicate the server config, and vice versa. See this [PR for more details](https://github.com/dotansimha/graphql-code-generator/pull/9497).
337+
338+
Now, the `typescript` plugin is no longer a dependency. So, we can set the default type as `string | number`, which is the correct type for client use cases. For more details on how Scalar coercion works here, please read [The Complete GraphQL Scalar Guide](https://the-guild.dev/graphql/hive/blog/the-complete-graphql-scalar-guide#scalar-value-coercion).

0 commit comments

Comments
 (0)