-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TypedDocumentNode string alternative #9137
Changes from 4 commits
4a7b339
504367a
c7105db
13e48a9
02ed2b3
2d5a4e7
590b2e2
8e4e93f
7a5d42d
3892ba2
ef28588
961251f
dbd2053
8005ea3
2c6ee68
7b073f3
db98f71
346e1e5
4e732da
82b75f0
56170f6
e3120b3
8b5ad73
f960ccf
ed34330
0283daf
cfd9380
a97276b
2e99784
bdc1e7d
e954bea
0f155b4
5771e6d
5ce6b4f
a86f4bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@graphql-codegen/client-preset": patch | ||
--- | ||
dependencies updates: | ||
- Updated dependency [`@graphql-typed-document-node/[email protected]` ↗︎](https://www.npmjs.com/package/@graphql-typed-document-node/core/v/3.2.0) (from `3.1.2`, in `dependencies`) | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we want a new example or just modify an existing one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think urql is not a good example because it actually requires the AST at runtime anyways 🤔 . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does it, though? It accepts both string and AST and seems to work fine (I modified the original urql example). Unless I'm missing something. Regarding the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, urql uses graphql internally and thus will just parse the string to the ast at runtime There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, I am fine with replacing executor HTTP in react-query! |
||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Using GraphQL Code Generator with URQL and React | ||
|
||
This example illustrates using GraphQL Code Generator in a React application using the URQL GraphQL Client. | ||
|
||
You will find the TypeScript-based codegen configuration in [`codegen.ts`](./codegen.ts). | ||
|
||
This simple codegen configuration generates types and helpers in the [`src/gql`](./src/gql/) folder that help you to get typed GraphQL Queries and Mutations seamlessly ⚡️ | ||
|
||
<br /> | ||
|
||
For a step-by-step implementation tutorial, please refer to the related guide: | ||
|
||
https://www.the-guild.dev/graphql/codegen/docs/guides/react-vue-angular | ||
|
||
-- | ||
|
||
Please note that the `client` preset used in this example is compatible with `@urql/core` (since `1.15.0`), `@urql/preact` (since `1.4.0`) and `urql` (since `1.11.0`). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { type CodegenConfig } from '@graphql-codegen/cli'; | ||
|
||
const config: CodegenConfig = { | ||
schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index', | ||
documents: ['src/**/*.tsx', '!src/gql/**/*'], | ||
generates: { | ||
'./src/gql/': { | ||
preset: 'client', | ||
config: { | ||
documentMode: 'string', | ||
}, | ||
}, | ||
}, | ||
hooks: { afterAllFileWrite: ['prettier --write'] }, | ||
}; | ||
|
||
export default config; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import { defineConfig } from 'cypress'; | ||
|
||
export default defineConfig({ | ||
e2e: { | ||
setupNodeEvents(_on, _config) { | ||
// implement node event listeners here | ||
}, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
describe('template spec', () => { | ||
it('renders everything correctly', () => { | ||
cy.visit('http://localhost:3000'); | ||
cy.get('h3').should('contain', 'A New Hope'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/// <reference types="cypress" /> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import './commands'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Vite App</title> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "example-react-urql-string-document-node", | ||
"version": "0.1.0", | ||
"private": true, | ||
"dependencies": { | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"urql": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.17", | ||
"@types/react-dom": "^18.0.10", | ||
"@graphql-codegen/cli": "^3.2.2", | ||
"@graphql-codegen/client-preset": "^2.1.1", | ||
"@vitejs/plugin-react": "^3.1.0", | ||
"typescript": "4.9.5", | ||
"serve": "14.2.0", | ||
"cypress": "12.6.0", | ||
"start-server-and-test": "2.0.0", | ||
"vite": "^4.1.0" | ||
}, | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "vite build", | ||
"start": "serve -s dist", | ||
"test": "cypress run", | ||
"test:end2end": "start-server-and-test start http://localhost:3000 test", | ||
"codegen": "graphql-codegen --config codegen.ts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
.App { | ||
text-align: center; | ||
} | ||
|
||
.App-logo { | ||
height: 40vmin; | ||
pointer-events: none; | ||
} | ||
|
||
@media (prefers-reduced-motion: no-preference) { | ||
.App-logo { | ||
animation: App-logo-spin infinite 20s linear; | ||
} | ||
} | ||
|
||
.App-header { | ||
background-color: #282c34; | ||
min-height: 100vh; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
font-size: calc(10px + 2vmin); | ||
color: white; | ||
} | ||
|
||
.App-link { | ||
color: #61dafb; | ||
} | ||
|
||
@keyframes App-logo-spin { | ||
from { | ||
transform: rotate(0deg); | ||
} | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { TypedDocumentString } from '@graphql-typed-document-node/core'; | ||
|
||
import './App.css'; | ||
import Film from './Film'; | ||
import { graphql } from './gql'; | ||
import { AnyVariables, OperationContext, RequestPolicy, TypedDocumentNode, useQuery, UseQueryResponse } from 'urql'; | ||
import type { DocumentNode } from 'graphql'; | ||
|
||
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ ` | ||
query allFilmsWithVariablesQuery($first: Int!) { | ||
allFilms(first: $first) { | ||
edges { | ||
node { | ||
...FilmItem | ||
} | ||
} | ||
} | ||
} | ||
`); | ||
|
||
declare module 'urql' { | ||
// @ts-expect-error this is just temporary until we update types in urql | ||
beerose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
export type UseQueryArgs<Variables extends AnyVariables = AnyVariables, Data = any> = { | ||
query: string | DocumentNode | TypedDocumentNode<Data, Variables> | TypedDocumentString<Data, Variables>; | ||
requestPolicy?: RequestPolicy; | ||
context?: Partial<OperationContext>; | ||
pause?: boolean; | ||
} & (Variables extends void | ||
? { | ||
variables?: Variables; | ||
} | ||
: Variables extends { | ||
[P in keyof Variables]: Variables[P] | null; | ||
} | ||
? { | ||
variables?: Variables; | ||
} | ||
: { | ||
variables: Variables; | ||
}); | ||
|
||
export function useQuery<Data = any, Variables extends AnyVariables = AnyVariables>( | ||
args: UseQueryArgs<Variables, Data> | ||
): UseQueryResponse<Data, Variables>; | ||
} | ||
|
||
function App() { | ||
const [{ data }] = useQuery({ | ||
query: allFilmsWithVariablesQueryDocument, | ||
variables: { | ||
first: 10, | ||
}, | ||
}); | ||
|
||
return ( | ||
<div className="App"> | ||
{data && <ul>{data.allFilms?.edges?.map((e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />)}</ul>} | ||
</div> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FragmentType, useFragment } from './gql/fragment-masking'; | ||
import { graphql } from './gql'; | ||
|
||
export const FilmFragment = graphql(/* GraphQL */ ` | ||
fragment FilmItem on Film { | ||
id | ||
title | ||
releaseDate | ||
producers | ||
} | ||
`); | ||
|
||
const Film = (props: { | ||
/* tweet property has the correct type 🎉 */ | ||
film: FragmentType<typeof FilmFragment>; | ||
}) => { | ||
const film = useFragment(FilmFragment, props.film); | ||
return ( | ||
<div> | ||
<h3>{film.title}</h3> | ||
<p>{film.releaseDate}</p> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Film; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { ResultOf, DocumentTypeDecoration } from '@graphql-typed-document-node/core'; | ||
|
||
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>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* eslint-disable */ | ||
import * as types from './graphql'; | ||
|
||
/** | ||
* Map of all GraphQL operations in the project. | ||
* | ||
* This map has several performance disadvantages: | ||
* 1. It is not tree-shakeable, so it will include all operations in the project. | ||
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. | ||
* 3. It does not support dead code elimination, so it will add unused operations. | ||
* | ||
* Therefore it is highly recommended to use the babel or swc plugin for production. | ||
*/ | ||
const documents = { | ||
'\n query allFilmsWithVariablesQuery($first: Int!) {\n allFilms(first: $first) {\n edges {\n node {\n ...FilmItem\n }\n }\n }\n }\n': | ||
types.AllFilmsWithVariablesQueryDocument, | ||
'\n fragment FilmItem on Film {\n id\n title\n releaseDate\n producers\n }\n': | ||
types.FilmItemFragmentDoc, | ||
}; | ||
|
||
/** | ||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. | ||
*/ | ||
export function graphql( | ||
source: '\n query allFilmsWithVariablesQuery($first: Int!) {\n allFilms(first: $first) {\n edges {\n node {\n ...FilmItem\n }\n }\n }\n }\n' | ||
): typeof import('./graphql').AllFilmsWithVariablesQueryDocument; | ||
/** | ||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. | ||
*/ | ||
export function graphql( | ||
source: '\n fragment FilmItem on Film {\n id\n title\n releaseDate\n producers\n }\n' | ||
): typeof import('./graphql').FilmItemFragmentDoc; | ||
|
||
export function graphql(source: string) { | ||
return (documents as any)[source] ?? {}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: update with the latest version when a PR to
@graphql-typed-document-node/core
is accepted and released