Skip to content

Commit

Permalink
Enable static type checking of Apollo client calls (#3012)
Browse files Browse the repository at this point in the history
* Configure graphql codegen to work with Apollo client and enable static checking

* Update queries to use new codegen function

* Fix anonymous mutation

* Add updated codegen files

* Add comment

* Add codegen babel plugin to remove generated query map from production bundle

* Add generated fragment maskings

* update lockfile

* remove unnecessary mock

* style
  • Loading branch information
cesarvarela authored Aug 8, 2024
1 parent e8599b4 commit a00a3ba
Show file tree
Hide file tree
Showing 22 changed files with 1,145 additions and 6,114 deletions.
12 changes: 10 additions & 2 deletions site/gatsby-site/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ const config: CodegenConfig = {
overwrite: true,
schema: ["./server/schema.ts"],
require: ['ts-node/register', 'dotenv/config'],
// TODO: documents option should be 'src/**/!(*.d).{ts,tsx,js}' instead so it parses the entire project but as far as I'm aware there is no way to parse some queries while ignoring others (we have to ignore gatsby's queries because they use a different schema)
documents: 'src/graphql/**/!(*.d).{ts,tsx,js}',
pluckConfig: {
globalIdentifier: 'gql',
},
generates: {
"server/generated/graphql.ts": {
plugins: ["typescript", "typescript-resolvers", "typescript-mongodb"]
"server/generated/": {
preset: 'client',
presetConfig: {
gqlTagName: "gql",
}
}
}
};
Expand Down
8 changes: 8 additions & 0 deletions site/gatsby-site/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ exports.onCreateBabelConfig = ({ actions }) => {
name: '@babel/plugin-proposal-export-default-from',
});

actions.setBabelPlugin({
name: 'graphql-codegen-babel',
options: {
artifactDirectory: './server/generated/',
gqlTagName: 'gql',
},
});

if (process.env.INSTRUMENT) {
actions.setBabelPlugin({
name: 'babel-plugin-istanbul',
Expand Down
129 changes: 81 additions & 48 deletions site/gatsby-site/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion site/gatsby-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"gatsby-source-prismic": "^5.3.1",
"gatsby-transformer-sharp": "^5.7.0",
"graphql": "^16.8.1",
"graphql-codegen-babel": "file:plugins/graphql-codegen-babel",
"graphql-http": "^1.22.1",
"graphql-middleware": "^6.1.35",
"graphql-scalars": "^1.23.0",
Expand Down Expand Up @@ -140,6 +141,7 @@
"@babel/plugin-proposal-export-default-from": "^7.23.3",
"@cypress/code-coverage": "^3.12.15",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/client-preset": "^4.3.3",
"@graphql-codegen/typescript": "^4.0.6",
"@graphql-codegen/typescript-mongodb": "^3.0.0",
"@graphql-codegen/typescript-resolvers": "^4.0.6",
Expand Down Expand Up @@ -203,4 +205,4 @@
"optionalDependencies": {
"@parcel/watcher-linux-x64-glibc": "^2.4.0"
}
}
}
11 changes: 0 additions & 11 deletions site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { test } from '../utils';
import incident10 from '../fixtures/incidents/incident10.json';
import { conditionalIntercept, waitForRequest } from '../utils';
import config from '../config';
import { expect } from '@playwright/test';
import { init, seedCollection } from '../memory-mongo';
Expand Down Expand Up @@ -86,16 +84,7 @@ test.describe('Dynamic Cite pages', () => {
};
});

await conditionalIntercept(
page,
'**/graphql',
(req) => req.postDataJSON().operationName == 'FindIncident',
incident10,
'findIncident'
);

await page.locator('[data-cy="toogle-live-data"]').click();
await waitForRequest('findIncident');

const consoleErrors = await page.evaluate(() => {
const errors: string[] = [];
Expand Down
5 changes: 5 additions & 0 deletions site/gatsby-site/plugins/graphql-codegen-babel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Gatsby doesn't support importing babel plugins without default export so we need to export it as default

const { babelOptimizerPlugin } = require('@graphql-codegen/client-preset')

module.exports = babelOptimizerPlugin;
4 changes: 4 additions & 0 deletions site/gatsby-site/plugins/graphql-codegen-babel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "graphql-codegen-babel",
"main": "index.js"
}
67 changes: 67 additions & 0 deletions site/gatsby-site/server/generated/fragment-masking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable */
import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
import { FragmentDefinitionNode } from 'graphql';
import { Incremental } from './graphql';


export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
infer TType,
any
>
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never;

// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any;
}


export function makeFragmentData<
F extends DocumentTypeDecoration<any, any>,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>;
}
export function isFragmentReady<TQuery, TFrag>(
queryNode: DocumentTypeDecoration<TQuery, any>,
fragmentNode: TypedDocumentNode<TFrag>,
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
): data is FragmentType<typeof fragmentNode> {
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__meta__
?.deferredFields;

if (!deferredFields) return true;

const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined;
const fragName = fragDef?.name?.value;

const fields = (fragName && deferredFields[fragName]) || [];
return fields.length > 0 && fields.every(field => data && field in data);
}
Loading

0 comments on commit a00a3ba

Please sign in to comment.