Skip to content

Add Client preset example to codegen docs home page #9376

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

Merged
merged 5 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@graphql-codegen/typescript-vue-apollo-smart-ops": "2.3.6",
"@graphql-codegen/typescript-vue-urql": "2.3.6",
"@graphql-codegen/urql-introspection": "2.2.1",
"@graphql-codegen/client-preset": "3.0.1",
"@monaco-editor/react": "4.5.0",
"@theguild/components": "4.5.10",
"classnames": "2.3.2",
Expand Down
4 changes: 2 additions & 2 deletions website/src/components/live-demo/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export function Editor({
onEdit,
}: {
lang: string;
value: string;
value: string | undefined;
readOnly?: boolean;
onEdit?: (value?: string) => void;
onEdit?: (value: string | undefined) => void;
}): ReactElement {
const { resolvedTheme } = useTheme();
return (
Expand Down
38 changes: 22 additions & 16 deletions website/src/components/live-demo/LiveDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const groupedExamples = Object.entries(EXAMPLES).map(([catName, category]) => ({
options: category.map((t, index) => ({ ...t, selectId: `${catName}__${index}` })),
}));

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

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

const changeTemplate = (value: string) => {
const changeTemplate = (value: string | undefined) => {
if (!value) return;
const [catName, index] = value.split('__');
setSchema(EXAMPLES[catName][index].schema);
setDocuments(EXAMPLES[catName][index].documents);
setConfig(EXAMPLES[catName][index].config);
setSchema(EXAMPLES[catName][Number(index)].schema);
setDocuments(EXAMPLES[catName][Number(index)].documents);
setOperationsFile(EXAMPLES[catName][Number(index)].operationsFile);
setConfig(EXAMPLES[catName][Number(index)].config);
setTemplate(value);
};

Expand All @@ -72,23 +76,24 @@ export default function LiveDemo(): ReactElement {
}}
isMulti={false}
isClearable={false}
onChange={e => changeTemplate(e.selectId)}
onChange={e => changeTemplate(e?.selectId)}
getOptionValue={o => o.selectId}
getOptionLabel={o => (
<div className="flex items-center justify-end gap-1.5">
<span className="mr-auto">{o.name}</span>
{o.tags?.map(t => {
const icon = icons[t];
const icon = icons[t as keyof typeof icons];
return icon ? (
<Image key={t} src={icon} placeholder="empty" loading="eager" className="max-h-[20px] w-auto" />
<Image alt='Icon' key={t} src={icon} placeholder="empty" loading="eager" className="max-h-[20px] w-auto" />
) : (
<span key={t} className="rounded-lg bg-gray-200 px-2 text-xs text-gray-800">
{t}
</span>
);
})}
</div>
)}
// fix react-select types
) as any as string}
defaultValue={groupedExamples[0].options[0]}
options={groupedExamples}
/>
Expand All @@ -98,6 +103,7 @@ export default function LiveDemo(): ReactElement {
schema={schema}
setDocuments={setDocuments}
documents={documents}
operationsFile={operationsFile}
setConfig={setConfig}
config={config}
error={error}
Expand Down
31 changes: 22 additions & 9 deletions website/src/components/live-demo/LiveDemoEditors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactElement, useEffect, useState } from 'react';
import { Image } from '@theguild/components';
import { load } from 'js-yaml';
import { Editor } from './Editor';
import { getMode } from './formatter';
import { Config, getMode } from './formatter';
import codegenLogo from '../../../public/assets/img/gql-codegen-icon.svg';
import graphqlLogo from '../../../public/assets/img/GraphQL_Logo.svg';
import classnames from 'classnames';
Expand All @@ -15,21 +15,34 @@ const classes = {

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

export interface LiveDemoEditorsProps {
setSchema: (newText: string | undefined) => void;
schema: string | undefined;
setDocuments: (newText: string | undefined) => void;
documents: string | undefined;
operationsFile?: { filename: string; content: string; language: string };
setConfig: (newText: string | undefined) => void;
config: string | undefined;
error: string | null;
output: { filename: string; content: string }[] | null;
}

export function LiveDemoEditors({
setSchema,
schema,
setDocuments,
documents,
operationsFile,
setConfig,
config,
error,
output,
}): ReactElement {
}: LiveDemoEditorsProps): ReactElement {
const [index, setIndex] = useState(0);
let mode: ReturnType<typeof getMode> = 'javascript';

try {
const parsedConfig = load(config || '');
const parsedConfig = load(config || '') as Config;
mode = getMode(parsedConfig);
} catch (e) {
console.error(e);
Expand All @@ -51,15 +64,15 @@ export function LiveDemoEditors({
<div className={classes.column}>
<div className={classes.title}>
<Image alt="GraphQL logo" src={graphqlLogo} placeholder="empty" loading="eager" className="h-7 w-7" />
operation.graphql
{operationsFile?.filename ?? 'operation.graphql'}
</div>
<Editor
lang="graphql"
lang={operationsFile?.language ?? 'graphql'}
onEdit={newText => {
setDocuments(newText !== READ_ONLY_DOCUMENTS_TEXT ? newText : undefined);
}}
value={documents === undefined ? READ_ONLY_DOCUMENTS_TEXT : documents}
readOnly={documents === undefined}
value={documents === undefined ? READ_ONLY_DOCUMENTS_TEXT : operationsFile?.content || documents}
readOnly={documents === undefined || !!operationsFile}
/>
</div>
<div className={classes.column}>
Expand All @@ -76,15 +89,15 @@ export function LiveDemoEditors({
onClick={() => setIndex(i)}
key={outputItem.filename}
className={classnames(
'h-2/3 min-w-[30%] rounded-t-md px-2 text-center font-mono text-xs font-bold',
'h-2/3 min-w-[15%] rounded-t-md px-2 text-center font-mono text-xs font-bold',
index === i && 'bg-neutral-800 text-white'
)}
>
{basename(outputItem.filename)}
</button>
))}
</div>
<Editor readOnly lang={mode} value={error || output?.[index].content} />
<Editor readOnly lang={mode} value={error || output?.[index]?.content || ''} />
</div>
</div>
);
Expand Down
39 changes: 39 additions & 0 deletions website/src/components/live-demo/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ const TS_QUERY = dedent(/* GraphQL */ `
}
`);

export const APP_TSX = `\
import { useQuery } from '@apollo/client';

import { graphql } from './gql/gql';

const findUserQuery = graphql(\`${TS_QUERY}\`);

function App() {
const { data } = useQuery(findUserQuery, { variables: { userId: 10 } });
return (
<div className="App">
{data?.user?.username}
</div>
);
}

export default App;
`;

export const EXAMPLES: Record<
string,
{
Expand All @@ -71,9 +90,29 @@ export const EXAMPLES: Record<
config: string;
schema: string;
documents?: string;
operationsFile?: {
filename: string;
content: string;
language: string;
};
}[]
> = {
TypeScript: [
{
name: 'Client preset',
description: `This is an example of using a Client preset (recommended).`,
tags: ['typescript', 'frontend'],
config: `generates:
gql/:
preset: client`,
schema: TS_SCHEMA,
documents: TS_QUERY,
operationsFile: {
filename: 'App.tsx',
content: APP_TSX,
language: 'typescript',
},
},
{
name: 'Schema types',
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.`,
Expand Down
3 changes: 2 additions & 1 deletion website/src/components/live-demo/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { extname } from 'path';

type Config = {
export type Config = {
generates: Record<
string,
{
Expand All @@ -9,6 +9,7 @@ type Config = {
presetConfig?: {
baseTypesPath: string;
extension: string;
typesPath: string;
};
}
>;
Expand Down
46 changes: 24 additions & 22 deletions website/src/components/live-demo/generate.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
import { codegen } from '@graphql-codegen/core';
import { GraphQLError, parse } from 'graphql';
import { parse } from 'graphql';
import { load } from 'js-yaml';
import { pluginLoaderMap, presetLoaderMap } from './plugins';
import { normalizeConfig } from './utils';
import { Config } from './formatter';

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

export async function generate(config: string, schema: string, documents?: string) {
export async function generate(config: string, schema: string, documents?: string, documentsLocation?: string) {
try {
const outputs = [];
const cleanTabs = config.replace(/\t/g, ' ');
const { generates, ...otherFields } = load(cleanTabs);
const { generates, ...otherFields } = load(cleanTabs) as Record<string, Config>;
const runConfigurations = [];

for (const [filename, outputOptions] of Object.entries(generates)) {
const hasPreset = !!outputOptions.preset;
const plugins = normalizeConfig(outputOptions.plugins || outputOptions);
const outputConfig = outputOptions.config;
const pluginMap = {};

await Promise.all(
plugins.map(async pluginElement => {
const [pluginName] = Object.keys(pluginElement);
try {
pluginMap[pluginName] = await pluginLoaderMap[pluginName]();
} catch (e) {
console.error(e);
}
})
);
const pluginMap: Record<string, any> = {};

const props = {
plugins,
Expand All @@ -40,8 +30,9 @@ export async function generate(config: string, schema: string, documents?: strin
documents: documents
? [
{
location: 'operation.graphql',
location: documentsLocation || 'operation.graphql',
document: parse(documents),
rawSDL: documents,
},
]
: [],
Expand All @@ -51,19 +42,30 @@ export async function generate(config: string, schema: string, documents?: strin
},
};

if (!hasPreset) {
await Promise.all(
plugins?.map(async pluginElement => {
const [pluginName] = Object.keys(pluginElement);
try {
pluginMap[pluginName as string] = await pluginLoaderMap[pluginName as keyof typeof pluginLoaderMap]();
} catch (e) {
console.error(e);
}
})
);

if (!outputOptions.preset) {
runConfigurations.push({
filename,
...props,
});
} else {
const presetExport = await presetLoaderMap[outputOptions.preset]();
const presetExport = await presetLoaderMap[outputOptions.preset as unknown as keyof typeof presetLoaderMap]();
const presetFn = typeof presetExport === 'function' ? presetExport : presetExport.preset;

runConfigurations.push(
...(await presetFn.buildGeneratesSection({
baseOutputDir: filename,
presetConfig: outputOptions.presetConfig || {},
presetConfig: { ...outputOptions.presetConfig, typesPath: 'graphql.ts', baseTypesPath: 'graphql.ts' },
...props,
}))
);
Expand All @@ -78,7 +80,7 @@ export async function generate(config: string, schema: string, documents?: strin
}

return outputs;
} catch (error: GraphQLError) {
} catch (error: any) {
console.error(error);

if (error.details) {
Expand All @@ -92,7 +94,7 @@ export async function generate(config: string, schema: string, documents?: strin
if (error.errors) {
return error.errors
.map(
subError => `${subError.message}:
(subError: any) => `${subError.message}:
${subError.details}`
)
.join('\n');
Expand Down
1 change: 1 addition & 0 deletions website/src/components/live-demo/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const presetLoaderMap = {
'import-types': () => import('@graphql-codegen/import-types-preset'),
'java-apollo-android': () => import('@graphql-codegen/java-apollo-android'),
'near-operation-file': () => import('@graphql-codegen/near-operation-file-preset'),
client: () => import('@graphql-codegen/client-preset'),
};

export const pluginLoaderMap = {
Expand Down
9 changes: 7 additions & 2 deletions website/src/components/live-demo/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
export function normalizeConfig(config) {
import { Config } from './formatter';

export function normalizeConfig(config: Config['generates'][0]) {
if (typeof config === 'string') {
return [{ [config]: {} }];
}
if (Array.isArray(config)) {
return config.map(plugin => (typeof plugin === 'string' ? { [plugin]: {} } : plugin));
}
if (typeof config === 'object') {
return Object.keys(config).reduce((prev, pluginName) => [...prev, { [pluginName]: config[pluginName] }], []);
return Object.keys(config).reduce<Record<string, any>[]>((prev, pluginName) => {
if (pluginName === 'preset') return prev;
return [...prev, { [pluginName]: config[pluginName] }];
}, []);
}
return [];
}
Loading