Skip to content

Commit

Permalink
typename selection set transform in client preset (#9562)
Browse files Browse the repository at this point in the history
  • Loading branch information
n1ru4l authored Jul 25, 2023
1 parent 7ba9d0e commit 5beee97
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 7 deletions.
32 changes: 32 additions & 0 deletions .changeset/lucky-boats-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
'@graphql-codegen/client-preset': minor
---

Add the `addTypenameSelectionDocumentTransform` for automatically adding `__typename` selections to all objct type selection sets.

This is useful for GraphQL Clients such as Apollo Client or urql that need typename information for their cache to function.

**Example Usage**

```
import { addTypenameSelectionDocumentTransform } from '@graphql-codegen/client-preset';
import { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "YOUR_GRAPHQL_ENDPOINT",
documents: ["./**/*.{ts,tsx}"],
ignoreNoDocuments: true,
generates: {
"./gql/": {
preset: "client",
plugins: [],
presetConfig: {
persistedDocuments: true,
},
documentTransforms: [addTypenameSelectionDocumentTransform],
},
},
};
export default config;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Kind, visit } from 'graphql';
import { Types } from '@graphql-codegen/plugin-helpers';

/**
* Automatically adds `__typename` selections to every object type in your GraphQL document.
* This is useful for GraphQL Clients such as Apollo Client or urql that need typename information for their cache to function.
*/
export const addTypenameSelectionDocumentTransform: Types.DocumentTransformObject = {
transform({ documents }) {
return documents.map(document => ({
...document,
document: document.document
? visit(document.document, {
SelectionSet(node) {
if (
!node.selections.find(selection => selection.kind === 'Field' && selection.name.value === '__typename')
) {
return {
...node,
selections: [
{
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: '__typename',
},
},
...node.selections,
],
};
}
return undefined;
},
})
: undefined,
}));
},
};
2 changes: 2 additions & 0 deletions packages/presets/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,5 @@ function createDeferred<T = void>(): Deferred<T> {
});
return d;
}

export { addTypenameSelectionDocumentTransform } from './add-typename-selection-document-transform.js';
64 changes: 57 additions & 7 deletions website/src/pages/plugins/presets/preset-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ When dealing with nested Fragments, the `useFragment()` should also be used in a

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).

### Fragment Masking with @defer directive
### Fragment Masking with @defer Directive

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.
The `isFragmentReady` function takes three arguments: the query document, the fragment definition, and the data returned by the
Expand Down Expand Up @@ -388,16 +388,18 @@ const config: CodegenConfig = {
export default config
```

## Persisted documents
## Persisted Documents

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.
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.
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.
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).

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

### Enable Persisted Documents

Persisted documents can be enabled by setting the `persistedDocuments` option to `true`:

```ts filename="codegen.ts" {9-11}
Expand Down Expand Up @@ -488,15 +490,63 @@ console.log(response.status)
console.log(await response.json())
```

## Reducing bundle size: Babel plugin
### Normalized Caches (urql and Apollo Client)

Urql is a popular GraphQL client that utilizes a normalized cache.
Because the client utilizes the `__typename` fields to normalize the cache, it is important that the `__typename` field is included in the persisted documents.
The `addTypenameSelectionDocumentTransform` document transform can be used for achieving this.

```ts filename="codegen.ts" {1,15}
import { type CodegenConfig } from '@graphql-codegen/cli'
import { addTypenameDocumentTransform } from '@graphql-codegen/client-preset'

const config: CodegenConfig = {
schema: './**/*.graphqls',
documents: ['./**/*.{ts,tsx}'],
ignoreNoDocuments: true,
generates: {
'./gql/': {
preset: 'client',
plugins: [],
presetConfig: {
persistedDocuments: true
},
documentTransforms: [addTypenameDocumentTransform]
}
}
}

export default config
```

Afterwards, you can send the hashes to the server.

```ts filename="Example with urql" {2,8-13}
import { createClient, cacheExchange } from '@urql/core'
import { persistedExchange } from '@urql/exchange-persisted'

const client = new createClient({
url: 'YOUR_GRAPHQL_ENDPOINT',
exchanges: [
cacheExchange,
persistedExchange({
enforcePersistedQueries: true,
enableForMutation: true,
generateHash: (_, document) => Promise.resolve(document['__meta__']['hash'])
})
]
})
```

## Reducing Bundle Size

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

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

### Babel plugin
### Babel Plugin

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

Expand All @@ -509,7 +559,7 @@ module.exports = {
}
```

### SWC plugin
### SWC Plugin

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

0 comments on commit 5beee97

Please sign in to comment.