Skip to content

Commit 25b249e

Browse files
authored
Add Client preset example to codegen docs home page (#9376)
1 parent 774167a commit 25b249e

File tree

9 files changed

+120
-52
lines changed

9 files changed

+120
-52
lines changed

website/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@graphql-codegen/typescript-vue-apollo-smart-ops": "2.3.6",
6666
"@graphql-codegen/typescript-vue-urql": "2.3.6",
6767
"@graphql-codegen/urql-introspection": "2.2.1",
68+
"@graphql-codegen/client-preset": "3.0.1",
6869
"@monaco-editor/react": "4.5.0",
6970
"@theguild/components": "4.5.10",
7071
"classnames": "2.3.2",

website/src/components/live-demo/Editor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ export function Editor({
99
onEdit,
1010
}: {
1111
lang: string;
12-
value: string;
12+
value: string | undefined;
1313
readOnly?: boolean;
14-
onEdit?: (value?: string) => void;
14+
onEdit?: (value: string | undefined) => void;
1515
}): ReactElement {
1616
const { resolvedTheme } = useTheme();
1717
return (

website/src/components/live-demo/LiveDemo.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ const groupedExamples = Object.entries(EXAMPLES).map(([catName, category]) => ({
1111
options: category.map((t, index) => ({ ...t, selectId: `${catName}__${index}` })),
1212
}));
1313

14-
function useCodegen(config: string, schema: string, documents?: string, templateName: string) {
15-
const [error, setError] = useState(null);
16-
const [output, setOutput] = useState(null);
14+
function useCodegen(config: string | undefined, schema: string | undefined, documents: string | undefined, templateName: string, operationsFileName?: string) {
15+
const [error, setError] = useState<string | null>(null);
16+
const [output, setOutput] = useState<{filename: string, content: string}[] | null>(null);
1717

1818
useEffect(() => {
19-
generate(config, schema, documents).then(result => {
19+
if (!config || !schema) return;
20+
generate(config, schema, documents, operationsFileName).then(result => {
2021
if (typeof result === 'string') {
2122
setOutput(null);
2223
setError(result);
@@ -39,16 +40,19 @@ export default function LiveDemo(): ReactElement {
3940
const { resolvedTheme } = useTheme();
4041
const isDarkTheme = resolvedTheme === 'dark';
4142
const [template, setTemplate] = useState(`${DEFAULT_EXAMPLE.catName}__${DEFAULT_EXAMPLE.index}`);
42-
const [schema, setSchema] = useState(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].schema);
43-
const [documents, setDocuments] = useState(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].documents);
44-
const [config, setConfig] = useState(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].config);
45-
const { output, error } = useCodegen(config, schema, documents, template);
43+
const [schema, setSchema] = useState<string | undefined>(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].schema);
44+
const [documents, setDocuments] = useState<string | undefined>(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].documents);
45+
const [operationsFile, setOperationsFile] = useState<{filename: string, content: string, language: string} | undefined>(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].operationsFile);
46+
const [config, setConfig] = useState<string | undefined>(EXAMPLES[DEFAULT_EXAMPLE.catName][DEFAULT_EXAMPLE.index].config);
47+
const { output, error } = useCodegen(config, schema, documents, template, operationsFile?.filename);
4648

47-
const changeTemplate = (value: string) => {
49+
const changeTemplate = (value: string | undefined) => {
50+
if (!value) return;
4851
const [catName, index] = value.split('__');
49-
setSchema(EXAMPLES[catName][index].schema);
50-
setDocuments(EXAMPLES[catName][index].documents);
51-
setConfig(EXAMPLES[catName][index].config);
52+
setSchema(EXAMPLES[catName][Number(index)].schema);
53+
setDocuments(EXAMPLES[catName][Number(index)].documents);
54+
setOperationsFile(EXAMPLES[catName][Number(index)].operationsFile);
55+
setConfig(EXAMPLES[catName][Number(index)].config);
5256
setTemplate(value);
5357
};
5458

@@ -72,23 +76,24 @@ export default function LiveDemo(): ReactElement {
7276
}}
7377
isMulti={false}
7478
isClearable={false}
75-
onChange={e => changeTemplate(e.selectId)}
79+
onChange={e => changeTemplate(e?.selectId)}
7680
getOptionValue={o => o.selectId}
7781
getOptionLabel={o => (
7882
<div className="flex items-center justify-end gap-1.5">
7983
<span className="mr-auto">{o.name}</span>
8084
{o.tags?.map(t => {
81-
const icon = icons[t];
85+
const icon = icons[t as keyof typeof icons];
8286
return icon ? (
83-
<Image key={t} src={icon} placeholder="empty" loading="eager" className="max-h-[20px] w-auto" />
87+
<Image alt='Icon' key={t} src={icon} placeholder="empty" loading="eager" className="max-h-[20px] w-auto" />
8488
) : (
8589
<span key={t} className="rounded-lg bg-gray-200 px-2 text-xs text-gray-800">
8690
{t}
8791
</span>
8892
);
8993
})}
9094
</div>
91-
)}
95+
// fix react-select types
96+
) as any as string}
9297
defaultValue={groupedExamples[0].options[0]}
9398
options={groupedExamples}
9499
/>
@@ -98,6 +103,7 @@ export default function LiveDemo(): ReactElement {
98103
schema={schema}
99104
setDocuments={setDocuments}
100105
documents={documents}
106+
operationsFile={operationsFile}
101107
setConfig={setConfig}
102108
config={config}
103109
error={error}

website/src/components/live-demo/LiveDemoEditors.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ReactElement, useEffect, useState } from 'react';
22
import { Image } from '@theguild/components';
33
import { load } from 'js-yaml';
44
import { Editor } from './Editor';
5-
import { getMode } from './formatter';
5+
import { Config, getMode } from './formatter';
66
import codegenLogo from '../../../public/assets/img/gql-codegen-icon.svg';
77
import graphqlLogo from '../../../public/assets/img/GraphQL_Logo.svg';
88
import classnames from 'classnames';
@@ -15,21 +15,34 @@ const classes = {
1515

1616
const READ_ONLY_DOCUMENTS_TEXT = `# This example isn't\n# using GraphQL operations`;
1717

18+
export interface LiveDemoEditorsProps {
19+
setSchema: (newText: string | undefined) => void;
20+
schema: string | undefined;
21+
setDocuments: (newText: string | undefined) => void;
22+
documents: string | undefined;
23+
operationsFile?: { filename: string; content: string; language: string };
24+
setConfig: (newText: string | undefined) => void;
25+
config: string | undefined;
26+
error: string | null;
27+
output: { filename: string; content: string }[] | null;
28+
}
29+
1830
export function LiveDemoEditors({
1931
setSchema,
2032
schema,
2133
setDocuments,
2234
documents,
35+
operationsFile,
2336
setConfig,
2437
config,
2538
error,
2639
output,
27-
}): ReactElement {
40+
}: LiveDemoEditorsProps): ReactElement {
2841
const [index, setIndex] = useState(0);
2942
let mode: ReturnType<typeof getMode> = 'javascript';
3043

3144
try {
32-
const parsedConfig = load(config || '');
45+
const parsedConfig = load(config || '') as Config;
3346
mode = getMode(parsedConfig);
3447
} catch (e) {
3548
console.error(e);
@@ -51,15 +64,15 @@ export function LiveDemoEditors({
5164
<div className={classes.column}>
5265
<div className={classes.title}>
5366
<Image alt="GraphQL logo" src={graphqlLogo} placeholder="empty" loading="eager" className="h-7 w-7" />
54-
operation.graphql
67+
{operationsFile?.filename ?? 'operation.graphql'}
5568
</div>
5669
<Editor
57-
lang="graphql"
70+
lang={operationsFile?.language ?? 'graphql'}
5871
onEdit={newText => {
5972
setDocuments(newText !== READ_ONLY_DOCUMENTS_TEXT ? newText : undefined);
6073
}}
61-
value={documents === undefined ? READ_ONLY_DOCUMENTS_TEXT : documents}
62-
readOnly={documents === undefined}
74+
value={documents === undefined ? READ_ONLY_DOCUMENTS_TEXT : operationsFile?.content || documents}
75+
readOnly={documents === undefined || !!operationsFile}
6376
/>
6477
</div>
6578
<div className={classes.column}>
@@ -76,15 +89,15 @@ export function LiveDemoEditors({
7689
onClick={() => setIndex(i)}
7790
key={outputItem.filename}
7891
className={classnames(
79-
'h-2/3 min-w-[30%] rounded-t-md px-2 text-center font-mono text-xs font-bold',
92+
'h-2/3 min-w-[15%] rounded-t-md px-2 text-center font-mono text-xs font-bold',
8093
index === i && 'bg-neutral-800 text-white'
8194
)}
8295
>
8396
{basename(outputItem.filename)}
8497
</button>
8598
))}
8699
</div>
87-
<Editor readOnly lang={mode} value={error || output?.[index].content} />
100+
<Editor readOnly lang={mode} value={error || output?.[index]?.content || ''} />
88101
</div>
89102
</div>
90103
);

website/src/components/live-demo/examples.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ const TS_QUERY = dedent(/* GraphQL */ `
6262
}
6363
`);
6464

65+
export const APP_TSX = `\
66+
import { useQuery } from '@apollo/client';
67+
68+
import { graphql } from './gql/gql';
69+
70+
const findUserQuery = graphql(\`${TS_QUERY}\`);
71+
72+
function App() {
73+
const { data } = useQuery(findUserQuery, { variables: { userId: 10 } });
74+
return (
75+
<div className="App">
76+
{data?.user?.username}
77+
</div>
78+
);
79+
}
80+
81+
export default App;
82+
`;
83+
6584
export const EXAMPLES: Record<
6685
string,
6786
{
@@ -71,9 +90,29 @@ export const EXAMPLES: Record<
7190
config: string;
7291
schema: string;
7392
documents?: string;
93+
operationsFile?: {
94+
filename: string;
95+
content: string;
96+
language: string;
97+
};
7498
}[]
7599
> = {
76100
TypeScript: [
101+
{
102+
name: 'Client preset',
103+
description: `This is an example of using a Client preset (recommended).`,
104+
tags: ['typescript', 'frontend'],
105+
config: `generates:
106+
gql/:
107+
preset: client`,
108+
schema: TS_SCHEMA,
109+
documents: TS_QUERY,
110+
operationsFile: {
111+
filename: 'App.tsx',
112+
content: APP_TSX,
113+
language: 'typescript',
114+
},
115+
},
77116
{
78117
name: 'Schema types',
79118
description: `This is the simplest example of generating output based on a GraphQL Schema. Codegen will generate the compatible base type, based on your schema. These type declarations are 1:1 to your schema, and it will be used as base types for other Codegen plugins (such as \`typescript-operations\`), while combined into the same file.`,

website/src/components/live-demo/formatter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { extname } from 'path';
22

3-
type Config = {
3+
export type Config = {
44
generates: Record<
55
string,
66
{
@@ -9,6 +9,7 @@ type Config = {
99
presetConfig?: {
1010
baseTypesPath: string;
1111
extension: string;
12+
typesPath: string;
1213
};
1314
}
1415
>;

website/src/components/live-demo/generate.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,27 @@
11
import { codegen } from '@graphql-codegen/core';
2-
import { GraphQLError, parse } from 'graphql';
2+
import { parse } from 'graphql';
33
import { load } from 'js-yaml';
44
import { pluginLoaderMap, presetLoaderMap } from './plugins';
55
import { normalizeConfig } from './utils';
6+
import { Config } from './formatter';
67

78
if (typeof window !== 'undefined') {
9+
// @ts-ignore
810
process.hrtime = () => [0, 0]; // Fix error - TypeError: process.hrtime is not a function
911
window.global = window; // type-graphql error - global is not defined
1012
}
1113

12-
export async function generate(config: string, schema: string, documents?: string) {
14+
export async function generate(config: string, schema: string, documents?: string, documentsLocation?: string) {
1315
try {
1416
const outputs = [];
1517
const cleanTabs = config.replace(/\t/g, ' ');
16-
const { generates, ...otherFields } = load(cleanTabs);
18+
const { generates, ...otherFields } = load(cleanTabs) as Record<string, Config>;
1719
const runConfigurations = [];
1820

1921
for (const [filename, outputOptions] of Object.entries(generates)) {
20-
const hasPreset = !!outputOptions.preset;
2122
const plugins = normalizeConfig(outputOptions.plugins || outputOptions);
2223
const outputConfig = outputOptions.config;
23-
const pluginMap = {};
24-
25-
await Promise.all(
26-
plugins.map(async pluginElement => {
27-
const [pluginName] = Object.keys(pluginElement);
28-
try {
29-
pluginMap[pluginName] = await pluginLoaderMap[pluginName]();
30-
} catch (e) {
31-
console.error(e);
32-
}
33-
})
34-
);
24+
const pluginMap: Record<string, any> = {};
3525

3626
const props = {
3727
plugins,
@@ -40,8 +30,9 @@ export async function generate(config: string, schema: string, documents?: strin
4030
documents: documents
4131
? [
4232
{
43-
location: 'operation.graphql',
33+
location: documentsLocation || 'operation.graphql',
4434
document: parse(documents),
35+
rawSDL: documents,
4536
},
4637
]
4738
: [],
@@ -51,19 +42,30 @@ export async function generate(config: string, schema: string, documents?: strin
5142
},
5243
};
5344

54-
if (!hasPreset) {
45+
await Promise.all(
46+
plugins?.map(async pluginElement => {
47+
const [pluginName] = Object.keys(pluginElement);
48+
try {
49+
pluginMap[pluginName as string] = await pluginLoaderMap[pluginName as keyof typeof pluginLoaderMap]();
50+
} catch (e) {
51+
console.error(e);
52+
}
53+
})
54+
);
55+
56+
if (!outputOptions.preset) {
5557
runConfigurations.push({
5658
filename,
5759
...props,
5860
});
5961
} else {
60-
const presetExport = await presetLoaderMap[outputOptions.preset]();
62+
const presetExport = await presetLoaderMap[outputOptions.preset as unknown as keyof typeof presetLoaderMap]();
6163
const presetFn = typeof presetExport === 'function' ? presetExport : presetExport.preset;
6264

6365
runConfigurations.push(
6466
...(await presetFn.buildGeneratesSection({
6567
baseOutputDir: filename,
66-
presetConfig: outputOptions.presetConfig || {},
68+
presetConfig: { ...outputOptions.presetConfig, typesPath: 'graphql.ts', baseTypesPath: 'graphql.ts' },
6769
...props,
6870
}))
6971
);
@@ -78,7 +80,7 @@ export async function generate(config: string, schema: string, documents?: strin
7880
}
7981

8082
return outputs;
81-
} catch (error: GraphQLError) {
83+
} catch (error: any) {
8284
console.error(error);
8385

8486
if (error.details) {
@@ -92,7 +94,7 @@ export async function generate(config: string, schema: string, documents?: strin
9294
if (error.errors) {
9395
return error.errors
9496
.map(
95-
subError => `${subError.message}:
97+
(subError: any) => `${subError.message}:
9698
${subError.details}`
9799
)
98100
.join('\n');

website/src/components/live-demo/plugins.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const presetLoaderMap = {
44
'import-types': () => import('@graphql-codegen/import-types-preset'),
55
'java-apollo-android': () => import('@graphql-codegen/java-apollo-android'),
66
'near-operation-file': () => import('@graphql-codegen/near-operation-file-preset'),
7+
client: () => import('@graphql-codegen/client-preset'),
78
};
89

910
export const pluginLoaderMap = {
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
export function normalizeConfig(config) {
1+
import { Config } from './formatter';
2+
3+
export function normalizeConfig(config: Config['generates'][0]) {
24
if (typeof config === 'string') {
35
return [{ [config]: {} }];
46
}
57
if (Array.isArray(config)) {
68
return config.map(plugin => (typeof plugin === 'string' ? { [plugin]: {} } : plugin));
79
}
810
if (typeof config === 'object') {
9-
return Object.keys(config).reduce((prev, pluginName) => [...prev, { [pluginName]: config[pluginName] }], []);
11+
return Object.keys(config).reduce<Record<string, any>[]>((prev, pluginName) => {
12+
if (pluginName === 'preset') return prev;
13+
return [...prev, { [pluginName]: config[pluginName] }];
14+
}, []);
1015
}
1116
return [];
1217
}

0 commit comments

Comments
 (0)