From 2fc20068206065fd81c26da876bc45f979af927a Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 24 Jul 2023 13:59:49 +0200 Subject: [PATCH] persisted operations --- .changeset/lucky-boats-chew.md | 32 ++++++++++ ...d-typename-selection-document-transform.ts | 38 +++++++++++ packages/presets/client/src/index.ts | 2 + .../pages/plugins/presets/preset-client.mdx | 64 +++++++++++++++++-- 4 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 .changeset/lucky-boats-chew.md create mode 100644 packages/presets/client/src/add-typename-selection-document-transform.ts diff --git a/.changeset/lucky-boats-chew.md b/.changeset/lucky-boats-chew.md new file mode 100644 index 00000000000..59d81c63ade --- /dev/null +++ b/.changeset/lucky-boats-chew.md @@ -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; +``` diff --git a/packages/presets/client/src/add-typename-selection-document-transform.ts b/packages/presets/client/src/add-typename-selection-document-transform.ts new file mode 100644 index 00000000000..319ed40029d --- /dev/null +++ b/packages/presets/client/src/add-typename-selection-document-transform.ts @@ -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, + })); + }, +}; diff --git a/packages/presets/client/src/index.ts b/packages/presets/client/src/index.ts index 60a450a8950..1eb9259c794 100644 --- a/packages/presets/client/src/index.ts +++ b/packages/presets/client/src/index.ts @@ -360,3 +360,5 @@ function createDeferred(): Deferred { }); return d; } + +export { addTypenameSelectionDocumentTransform } from './add-typename-selection-document-transform.js'; diff --git a/website/src/pages/plugins/presets/preset-client.mdx b/website/src/pages/plugins/presets/preset-client.mdx index 78e88cc3dac..49b26bf217b 100644 --- a/website/src/pages/plugins/presets/preset-client.mdx +++ b/website/src/pages/plugins/presets/preset-client.mdx @@ -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 @@ -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). 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). +### Enable Persisted Documents + Persisted documents can be enabled by setting the `persistedDocuments` option to `true`: ```ts filename="codegen.ts" {9-11} @@ -488,7 +490,55 @@ 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, @@ -496,7 +546,7 @@ 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: @@ -509,7 +559,7 @@ module.exports = { } ``` -### SWC plugin +### SWC Plugin As of 2023/03/11, SWC's custom plugins is still an experimental feature, that means unexpected breaking changes that