Skip to content

Commit 5beee97

Browse files
authored
typename selection set transform in client preset (#9562)
1 parent 7ba9d0e commit 5beee97

File tree

4 files changed

+129
-7
lines changed

4 files changed

+129
-7
lines changed

.changeset/lucky-boats-chew.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@graphql-codegen/client-preset': minor
3+
---
4+
5+
Add the `addTypenameSelectionDocumentTransform` for automatically adding `__typename` selections to all objct type selection sets.
6+
7+
This is useful for GraphQL Clients such as Apollo Client or urql that need typename information for their cache to function.
8+
9+
**Example Usage**
10+
11+
```
12+
import { addTypenameSelectionDocumentTransform } from '@graphql-codegen/client-preset';
13+
import { CodegenConfig } from "@graphql-codegen/cli";
14+
15+
const config: CodegenConfig = {
16+
schema: "YOUR_GRAPHQL_ENDPOINT",
17+
documents: ["./**/*.{ts,tsx}"],
18+
ignoreNoDocuments: true,
19+
generates: {
20+
"./gql/": {
21+
preset: "client",
22+
plugins: [],
23+
presetConfig: {
24+
persistedDocuments: true,
25+
},
26+
documentTransforms: [addTypenameSelectionDocumentTransform],
27+
},
28+
},
29+
};
30+
31+
export default config;
32+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Kind, visit } from 'graphql';
2+
import { Types } from '@graphql-codegen/plugin-helpers';
3+
4+
/**
5+
* Automatically adds `__typename` selections to every object type in your GraphQL document.
6+
* This is useful for GraphQL Clients such as Apollo Client or urql that need typename information for their cache to function.
7+
*/
8+
export const addTypenameSelectionDocumentTransform: Types.DocumentTransformObject = {
9+
transform({ documents }) {
10+
return documents.map(document => ({
11+
...document,
12+
document: document.document
13+
? visit(document.document, {
14+
SelectionSet(node) {
15+
if (
16+
!node.selections.find(selection => selection.kind === 'Field' && selection.name.value === '__typename')
17+
) {
18+
return {
19+
...node,
20+
selections: [
21+
{
22+
kind: Kind.FIELD,
23+
name: {
24+
kind: Kind.NAME,
25+
value: '__typename',
26+
},
27+
},
28+
...node.selections,
29+
],
30+
};
31+
}
32+
return undefined;
33+
},
34+
})
35+
: undefined,
36+
}));
37+
},
38+
};

packages/presets/client/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,5 @@ function createDeferred<T = void>(): Deferred<T> {
360360
});
361361
return d;
362362
}
363+
364+
export { addTypenameSelectionDocumentTransform } from './add-typename-selection-document-transform.js';

website/src/pages/plugins/presets/preset-client.mdx

+57-7
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ When dealing with nested Fragments, the `useFragment()` should also be used in a
256256

257257
You can find a complete working example here: [Nested Fragment example on GitHub](https://github.com/charlypoly/codegen-repros/blob/master/client-preset-nested-fragments-interface/src/App.tsx).
258258

259-
### Fragment Masking with @defer directive
259+
### Fragment Masking with @defer Directive
260260

261261
If you use the `@defer` directive and have a Fragment Masking setup, you can use an `isFragmentReady` helper to check if the deferred fragment data is already resolved.
262262
The `isFragmentReady` function takes three arguments: the query document, the fragment definition, and the data returned by the
@@ -388,16 +388,18 @@ const config: CodegenConfig = {
388388
export default config
389389
```
390390

391-
## Persisted documents
391+
## Persisted Documents
392392

393-
Persisted documents (often also referred to as persisted queries or persisted documents) is a technique for reducing client to server upstream traffic by sending a unique identifier instead of the full GraphQL document.
394-
It is also commonly used to reduce the size of the client bundle as well as to improve security by preventing the client from sending and executing arbitrary GraphQL operations.
393+
Persisted documents (often also referred to as persisted queries or persisted operations) is a technique for reducing client to server upstream traffic by sending a unique identifier instead of the full GraphQL document.
394+
It is also commonly used to reduce the size of the client bundle as well as to improve security by preventing the client from sending and executing arbitrary GraphQL operations (and thus reducing attack surface).
395395

396396
<Callout type="info">
397397
You can find [a functional example using GraphQL Yoga within our Codegen Examples on
398398
GitHub](https://github.com/dotansimha/graphql-code-generator/tree/master/examples/persisted-documents).
399399
</Callout>
400400

401+
### Enable Persisted Documents
402+
401403
Persisted documents can be enabled by setting the `persistedDocuments` option to `true`:
402404

403405
```ts filename="codegen.ts" {9-11}
@@ -488,15 +490,63 @@ console.log(response.status)
488490
console.log(await response.json())
489491
```
490492

491-
## Reducing bundle size: Babel plugin
493+
### Normalized Caches (urql and Apollo Client)
494+
495+
Urql is a popular GraphQL client that utilizes a normalized cache.
496+
Because the client utilizes the `__typename` fields to normalize the cache, it is important that the `__typename` field is included in the persisted documents.
497+
The `addTypenameSelectionDocumentTransform` document transform can be used for achieving this.
498+
499+
```ts filename="codegen.ts" {1,15}
500+
import { type CodegenConfig } from '@graphql-codegen/cli'
501+
import { addTypenameDocumentTransform } from '@graphql-codegen/client-preset'
502+
503+
const config: CodegenConfig = {
504+
schema: './**/*.graphqls',
505+
documents: ['./**/*.{ts,tsx}'],
506+
ignoreNoDocuments: true,
507+
generates: {
508+
'./gql/': {
509+
preset: 'client',
510+
plugins: [],
511+
presetConfig: {
512+
persistedDocuments: true
513+
},
514+
documentTransforms: [addTypenameDocumentTransform]
515+
}
516+
}
517+
}
518+
519+
export default config
520+
```
521+
522+
Afterwards, you can send the hashes to the server.
523+
524+
```ts filename="Example with urql" {2,8-13}
525+
import { createClient, cacheExchange } from '@urql/core'
526+
import { persistedExchange } from '@urql/exchange-persisted'
527+
528+
const client = new createClient({
529+
url: 'YOUR_GRAPHQL_ENDPOINT',
530+
exchanges: [
531+
cacheExchange,
532+
persistedExchange({
533+
enforcePersistedQueries: true,
534+
enableForMutation: true,
535+
generateHash: (_, document) => Promise.resolve(document['__meta__']['hash'])
536+
})
537+
]
538+
})
539+
```
540+
541+
## Reducing Bundle Size
492542

493543
Large scale projects might want to enable code splitting or tree shaking on the `client-preset` generated files.
494544
This is because instead of using the map which contains all GraphQL operations in the project,
495545
we can use the specific generated document types.
496546

497547
The `client-preset` comes with a Babel and a swc plugin that enables it.
498548

499-
### Babel plugin
549+
### Babel Plugin
500550

501551
To configure the Babel plugin, update (or create) your `.babelrc.js` as follow:
502552

@@ -509,7 +559,7 @@ module.exports = {
509559
}
510560
```
511561

512-
### SWC plugin
562+
### SWC Plugin
513563

514564
<Callout type="warning">
515565
As of 2023/03/11, SWC's custom plugins is still an experimental feature, that means unexpected breaking changes that

0 commit comments

Comments
 (0)