Skip to content
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

Support annotation suffixes for codegen #1635

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/cli/src/lib/codegen.ts
Expand Up @@ -194,6 +194,24 @@ async function generateDefaultConfig(
const defaultGlob = '*!(*.d).{ts,tsx,js,jsx}'; // No d.ts files
const appDirRelative = relativePath(rootDirectory, appDirectory);

const caapiSchema = getSchema('customer-account');
const caapiProject = findGqlProject(caapiSchema, gqlConfig);

const customerAccountAPIConfig = caapiProject?.documents
? {
['customer-accountapi.generated.d.ts']: {
preset,
schema: caapiSchema,
documents: caapiProject?.documents,
presetConfig: {
gqlSuffix:
caapiProject?.extensions?.languageService?.gqlTagOptions
?.annotationSuffix,
},
},
}
: undefined;

return {
filepath: 'virtual:codegen',
config: {
Expand All @@ -207,6 +225,11 @@ async function generateDefaultConfig(
defaultGlob, // E.g. ./server.(t|j)s
joinPath(appDirRelative, '**', defaultGlob), // E.g. app/routes/_index.(t|j)sx
],
presetConfig: {
gqlSuffix:
sfapiProject?.extensions?.languageService?.gqlTagOptions
?.annotationSuffix,
},

...(!!forceSfapiVersion && {
presetConfig: {importTypes: false},
Expand All @@ -228,6 +251,7 @@ async function generateDefaultConfig(
},
}),
},
...customerAccountAPIConfig,
},
},
};
Expand Down
2 changes: 1 addition & 1 deletion packages/hydrogen-codegen/src/pluck.ts
Expand Up @@ -11,7 +11,7 @@ export const pluckConfig = {
// Check for internal gql comment: const QUERY = `#graphql ...`
const hasInternalGqlComment =
node.type === 'TemplateLiteral' &&
/\s*#graphql\s*\n/i.test(node.quasis[0]?.value?.raw || '');
/\s*#graphql(:[\w.\-]+)?\s*\n/i.test(node.quasis[0]?.value?.raw || '');

if (hasInternalGqlComment) return true;

Expand Down
21 changes: 18 additions & 3 deletions packages/hydrogen-codegen/src/preset.ts
Expand Up @@ -2,7 +2,7 @@ import type {Types} from '@graphql-codegen/plugin-helpers';
import * as addPlugin from '@graphql-codegen/add';
import * as typescriptPlugin from '@graphql-codegen/typescript';
import * as typescriptOperationPlugin from '@graphql-codegen/typescript-operations';
import {processSources} from './sources.js';
import {processSources, type BuildTypeName} from './sources.js';
import {getDefaultOptions} from './defaults.js';
import {
plugin as dtsPlugin,
Expand Down Expand Up @@ -38,6 +38,19 @@ export type HydrogenPresetConfig = {
queryType: string;
mutationType: string;
}) => string;
/**
* Suffix to filter query documents: `#graphql:<suffix>`.
* Documents that don't match the prefix are excluded. Passing
* `undefined` or `''` matches only plain comments: `#graphl`
*/
gqlSuffix?: string;
/**
* Override the way that type names are created from queries.
* The names must match the ones generated in typescript-operations.
* Use this option to keep the names in sync if you are customizing them in typescript-operations.
* https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-operations
*/
buildTypeName?: BuildTypeName;
};

export const preset: Types.OutputPreset<HydrogenPresetConfig> = {
Expand All @@ -55,8 +68,10 @@ export const preset: Types.OutputPreset<HydrogenPresetConfig> = {
'[hydrogen-preset] providing additional typescript-based `plugins` leads to duplicated generated types',
);
}

const sourcesWithOperations = processSources(options.documents);
const sourcesWithOperations = processSources(
options.documents,
options.presetConfig,
);
const sources = sourcesWithOperations.map(({source}) => source);

const defaultOptions = getDefaultOptions(options.baseOutputDir);
Expand Down
31 changes: 19 additions & 12 deletions packages/hydrogen-codegen/src/sources.ts
Expand Up @@ -17,15 +17,26 @@ const capitalizeQueries = (
return capitalize(node.name!.value) + capitalize(node.operation);
};

export type BuildTypeName = typeof capitalizeQueries;

export function processSources(
sources: Array<Source>,
buildName = capitalizeQueries,
{
gqlSuffix = '',
buildTypeName = capitalizeQueries,
}: {gqlSuffix?: string; buildTypeName?: BuildTypeName} = {},
) {
const gqlComment = '#graphql';
const suffixedGqlComment = `#graphql${gqlSuffix ? `:${gqlSuffix}` : ''}\n`;
const sourcesWithOperations: Array<SourceWithOperations> = [];

for (const originalSource of sources) {
const source = fixLinebreaks(originalSource);
const {document} = source;
for (const {rawSDL, document, ...rest} of sources) {
if (rawSDL) {
const hasGqlComment = rawSDL.startsWith(gqlComment);
const hasGqlSuffix = rawSDL.startsWith(suffixedGqlComment);
if (!hasGqlSuffix && (gqlSuffix || hasGqlComment)) continue;
}

const operations: Array<OperationOrFragment> = [];

for (const definition of document?.definitions ?? []) {
Expand All @@ -38,15 +49,15 @@ export function processSources(
if (definition.name?.kind !== `Name`) continue;

operations.push({
initialName: buildName(definition),
initialName: buildTypeName(definition),
definition,
});
}

if (operations.length === 0) continue;

sourcesWithOperations.push({
source,
source: {rawSDL: fixLinebreaks(rawSDL), document, ...rest},
operations,
});
}
Expand Down Expand Up @@ -97,10 +108,6 @@ export function processSources(
*
* @param source
*/
function fixLinebreaks(source: Source) {
const fixedSource = {...source};

fixedSource.rawSDL = source.rawSDL?.replace(/\r\n/g, '\n');

return fixedSource;
function fixLinebreaks(rawSDL: Source['rawSDL']) {
return rawSDL?.replace(/\r\n/g, '\n');
}
90 changes: 87 additions & 3 deletions packages/hydrogen-codegen/tests/codegen.test.ts
Expand Up @@ -4,17 +4,18 @@ import path from 'node:path';
describe('Hydrogen Codegen', async () => {
// Patch dependency before importing the Codegen CLI
await import('../src/patch.js');
const {preset, schema, pluckConfig} = await import('../src/index.js');
const {preset, getSchema, pluckConfig} = await import('../src/index.js');
const {getDefaultOptions} = await import('../src/defaults.js');
const {executeCodegen} = await import('@graphql-codegen/cli');

const getCodegenOptions = (fixture: string, output = 'out.d.ts') => ({
pluckConfig: pluckConfig as any,
documents: path.join(__dirname, `fixtures/${fixture}`),
generates: {
[output]: {
preset,
schema,
documents: path.join(__dirname, `fixtures/${fixture}`),
schema: getSchema('storefront'),
presetConfig: {},
},
},
});
Expand Down Expand Up @@ -206,4 +207,87 @@ describe('Hydrogen Codegen', async () => {
"
`);
});

it('generates types for mixed queries using suffixes', async () => {
const codegenConfig = getCodegenOptions(
'mixed-operations.ts',
'storefront.out.d.ts',
);

codegenConfig.generates['customer-account.out.d.ts'] = {
preset,
schema: getSchema('customer-account'),
presetConfig: {
gqlSuffix: 'customer',
},
};

const result = await executeCodegen(codegenConfig);

expect(result).toHaveLength(2);

const generatedStorefrontCode = result.find(
(file) => file.filename === 'storefront.out.d.ts',
)!.content;

const generatedCustomerAccountCode = result.find(
(file) => file.filename === 'customer-account.out.d.ts',
)!.content;

expect(generatedStorefrontCode).toMatchInlineSnapshot(`
"/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable eslint-comments/no-unlimited-disable */
/* eslint-disable */
import * as StorefrontAPI from '@shopify/hydrogen/storefront-api-types';

export type ShopIdQueryVariables = StorefrontAPI.Exact<{ [key: string]: never; }>;


export type ShopIdQuery = { shop: Pick<StorefrontAPI.Shop, 'id'> };

export type ShopNameQueryVariables = StorefrontAPI.Exact<{ [key: string]: never; }>;


export type ShopNameQuery = { shop: Pick<StorefrontAPI.Shop, 'name'> };

interface GeneratedQueryTypes {
"#graphql\\n query ShopId {\\n shop {\\n id\\n }\\n }\\n": {return: ShopIdQuery, variables: ShopIdQueryVariables},
"\\n query ShopName {\\n shop {\\n name\\n }\\n }\\n": {return: ShopNameQuery, variables: ShopNameQueryVariables},
}

interface GeneratedMutationTypes {
}

declare module '@shopify/hydrogen' {
interface StorefrontQueries extends GeneratedQueryTypes {}
interface StorefrontMutations extends GeneratedMutationTypes {}
}
"
`);

expect(generatedCustomerAccountCode).toMatchInlineSnapshot(`
"/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable eslint-comments/no-unlimited-disable */
/* eslint-disable */
import * as CustomerAccountAPI from '@shopify/hydrogen/customer-account-api-types';

export type CustomerNameQueryVariables = CustomerAccountAPI.Exact<{ [key: string]: never; }>;


export type CustomerNameQuery = { customer: Pick<CustomerAccountAPI.Customer, 'firstName'> };

interface GeneratedQueryTypes {
"#graphql:customer\\n query CustomerName {\\n customer {\\n firstName\\n }\\n }\\n": {return: CustomerNameQuery, variables: CustomerNameQueryVariables},
}

interface GeneratedMutationTypes {
}

declare module '@shopify/hydrogen' {
interface CustomerAccountQueries extends GeneratedQueryTypes {}
interface CustomerAccountMutations extends GeneratedMutationTypes {}
}
"
`);
});
});
23 changes: 23 additions & 0 deletions packages/hydrogen-codegen/tests/fixtures/mixed-operations.ts
@@ -0,0 +1,23 @@
export const A = `#graphql
query ShopId {
shop {
id
}
}
`;

export const B = /* GraphQL */ `
query ShopName {
shop {
name
}
}
`;

export const C = `#graphql:customer
query CustomerName {
customer {
firstName
}
}
`;
16 changes: 13 additions & 3 deletions templates/skeleton/.graphqlrc.yml
Expand Up @@ -2,6 +2,16 @@ projects:
default:
schema: 'node_modules/@shopify/hydrogen/storefront.schema.json'
documents:
- '!*.d.ts'
- '*.{ts,tsx,js,jsx}'
- 'app/**/*.{ts,tsx,js,jsx}'
- '!*.d.ts'
- '*.{ts,tsx,js,jsx}'
- 'app/**/*.{ts,tsx,js,jsx}'
customer-account:
schema: 'node_modules/@shopify/hydrogen/customer-account.schema.json'
documents:
- '!*.d.ts'
- '*.{ts,tsx,js,jsx}'
- 'app/**/*.{ts,tsx,js,jsx}'
extensions:
languageService:
gqlTagOptions:
annotationSuffix: customer