diff --git a/.changeset/shy-poems-cheat.md b/.changeset/shy-poems-cheat.md new file mode 100644 index 0000000000..2310c6160c --- /dev/null +++ b/.changeset/shy-poems-cheat.md @@ -0,0 +1,5 @@ +--- +'@shopify/hydrogen-codegen': minor +--- + +Add default values to support the Customer Account API without configuration. diff --git a/packages/hydrogen-codegen/README.md b/packages/hydrogen-codegen/README.md new file mode 100644 index 0000000000..15c450455a --- /dev/null +++ b/packages/hydrogen-codegen/README.md @@ -0,0 +1,53 @@ +# Hydrogen Codegen + +A codegen plugin and preset for generating TypeScript types from GraphQL queries in a `d.ts` file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle). + +```ts +const {shop} = await client.query(`#graphql + query { + shop { + name + } + } +`); +``` + +The GraphQL client must use TypeScript interfaces that are extended in the generated `d.ts` file. See an example in [Hydrogen's Storefront client](https://github.com/Shopify/hydrogen/blob/081b41e0d43c9e1090933e908362625b9dfe7166/packages/hydrogen/src/storefront.ts#L58-L143). + +## Usage + +When using Hydrogen CLI, this package is already included and configured for you to generate types for the Shopify Storefront API. However, if you want to use it standalone with the GraphQL CLI or just want to add other APIs to Hydrogen, you can use the following example configuration: + +```ts +// /codegen.ts + +import type {CodegenConfig} from '@graphql-codegen/cli'; +import {pluckConfig, preset, getSchema} from '@shopify/hydrogen-codegen'; + +export default { + overwrite: true, + pluckConfig, + generates: { + 'storefrontapi.generated.d.ts': { + preset, + schema: getSchema('storefront'), + documents: [ + './*.{ts,tsx,js,jsx}', + './app/**/*.{ts,tsx,js,jsx}', + '!./app/graphql/customer/*.{ts,tsx,js,jsx}', + '!./app/graphql/my-cms/*.{ts,tsx,js,jsx}', + ], + }, + 'customerapi.generated.d.ts': { + preset, + schema: getSchema('customer'), + documents: ['./app/graphql/customer/*.{ts,tsx,js,jsx}'], + }, + 'mycms.generated.d.ts': { + preset, + schema: './my-cms.json', + documents: ['./app/graphql/my-cms/*.{ts,tsx,js,jsx}'], + }, + }, +} as CodegenConfig; +``` diff --git a/packages/hydrogen-codegen/src/defaults.ts b/packages/hydrogen-codegen/src/defaults.ts new file mode 100644 index 0000000000..2a14594bff --- /dev/null +++ b/packages/hydrogen-codegen/src/defaults.ts @@ -0,0 +1,40 @@ +import { + GENERATED_MUTATION_INTERFACE_NAME, + GENERATED_QUERY_INTERFACE_NAME, +} from './plugin.js'; + +const sfapiDefaultInterfaceExtensionCode = ` +declare module '@shopify/hydrogen' { + interface StorefrontQueries extends ${GENERATED_QUERY_INTERFACE_NAME} {} + interface StorefrontMutations extends ${GENERATED_MUTATION_INTERFACE_NAME} {} +}`; + +const caapiDefaultInterfaceExtensionCode = ` +declare module '@shopify/hydrogen' { + interface CustomerAccountQueries extends ${GENERATED_QUERY_INTERFACE_NAME} {} + interface CustomerAccountMutations extends ${GENERATED_MUTATION_INTERFACE_NAME} {} +}`; + +type DefaultValues = { + importTypesFrom: string; + namespacedImportName: string; + interfaceExtensionCode: string; +}; + +const sfapiDefaultValues: DefaultValues = { + importTypesFrom: '@shopify/hydrogen/storefront-api-types', + namespacedImportName: 'StorefrontAPI', + interfaceExtensionCode: sfapiDefaultInterfaceExtensionCode, +}; + +const caapiDefaultValues: DefaultValues = { + importTypesFrom: '@shopify/hydrogen/customer-account-api-types', + namespacedImportName: 'CustomerAccountAPI', + interfaceExtensionCode: caapiDefaultInterfaceExtensionCode, +}; + +export function getDefaultOptions(outputFile = '') { + return /^(customer|caapi\.)/i.test(outputFile) + ? caapiDefaultValues + : sfapiDefaultValues; +} diff --git a/packages/hydrogen-codegen/src/preset.ts b/packages/hydrogen-codegen/src/preset.ts index 6b7d9bf4ab..553b0aad73 100644 --- a/packages/hydrogen-codegen/src/preset.ts +++ b/packages/hydrogen-codegen/src/preset.ts @@ -3,6 +3,7 @@ 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 {getDefaultOptions} from './defaults.js'; import { plugin as dtsPlugin, GENERATED_MUTATION_INTERFACE_NAME, @@ -39,12 +40,6 @@ export type HydrogenPresetConfig = { }) => string; }; -export const defaultInterfaceExtensionCode = ` -declare module '@shopify/hydrogen' { - interface StorefrontQueries extends ${GENERATED_QUERY_INTERFACE_NAME} {} - interface StorefrontMutations extends ${GENERATED_MUTATION_INTERFACE_NAME} {} -}`; - export const preset: Types.OutputPreset = { buildGeneratesSection: (options) => { if (!options.baseOutputDir.endsWith('.d.ts')) { @@ -63,18 +58,20 @@ export const preset: Types.OutputPreset = { const sourcesWithOperations = processSources(options.documents); const sources = sourcesWithOperations.map(({source}) => source); + const defaultOptions = getDefaultOptions(options.baseOutputDir); + const importTypes = options.presetConfig.importTypes ?? true; const namespacedImportName = - options.presetConfig.namespacedImportName ?? 'StorefrontAPI'; + options.presetConfig.namespacedImportName ?? + defaultOptions.namespacedImportName; const importTypesFrom = - options.presetConfig.importTypesFrom ?? - '@shopify/hydrogen/storefront-api-types'; + options.presetConfig.importTypesFrom ?? defaultOptions.importTypesFrom; const interfaceExtensionCode = options.presetConfig.interfaceExtension?.({ queryType: GENERATED_QUERY_INTERFACE_NAME, mutationType: GENERATED_MUTATION_INTERFACE_NAME, - }) ?? defaultInterfaceExtensionCode; + }) ?? defaultOptions.interfaceExtensionCode; const pluginMap = { ...options.pluginMap, diff --git a/packages/hydrogen-codegen/src/schema.ts b/packages/hydrogen-codegen/src/schema.ts index 41580eeadd..8b9e0a6b99 100644 --- a/packages/hydrogen-codegen/src/schema.ts +++ b/packages/hydrogen-codegen/src/schema.ts @@ -1,16 +1,33 @@ // This comment is used during ESM build: //! import {createRequire} from 'module'; const require = createRequire(import.meta.url); -export const getSchema = () => - require.resolve('@shopify/hydrogen-react/storefront.schema.json'); -let staticSchema = ''; +/** + * Resolves a schema path for the provided API type. Only the API types currently + * bundled in Hydrogen are allowed: "storefront" and "customer". + * @param api + * @returns + */ +export const getSchema = (api = 'storefront' as 'storefront' | 'customer') => { + if (api !== 'storefront' && api !== 'customer') { + throw new Error( + `The provided API type "${api}" is unknown. Please use "storefront" or "customer".`, + ); + } + + return require.resolve(`@shopify/hydrogen-react/${api}.schema.json`); +}; + +let staticSFAPISchema = ''; try { - staticSchema = getSchema(); + staticSFAPISchema = getSchema('storefront'); } catch (error) { // This can happen at build time or when '@shopify/hydrogen-react' is not found. // Generally this shouldn't be an issue in real apps so let's ignore the error. // Also, this package could be used in non-Hydrogen apps. } -export const schema = staticSchema; +/** + * The resolved schema path for the Storefront API. + */ +export const schema = staticSFAPISchema; diff --git a/packages/hydrogen-codegen/tests/codegen.test.ts b/packages/hydrogen-codegen/tests/codegen.test.ts index 5b137e6e64..b7af915f4c 100644 --- a/packages/hydrogen-codegen/tests/codegen.test.ts +++ b/packages/hydrogen-codegen/tests/codegen.test.ts @@ -5,7 +5,7 @@ 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 {defaultInterfaceExtensionCode} = await import('../src/preset.js'); + const {getDefaultOptions} = await import('../src/defaults.js'); const {executeCodegen} = await import('@graphql-codegen/cli'); const getCodegenOptions = (fixture: string, output = 'out.d.ts') => ({ @@ -64,7 +64,7 @@ describe('Hydrogen Codegen', async () => { ); // Augments query/mutation types - expect(generatedCode).toMatch(defaultInterfaceExtensionCode); + expect(generatedCode).toMatch(getDefaultOptions().interfaceExtensionCode); expect(generatedCode).toMatchInlineSnapshot(` "/* eslint-disable eslint-comments/disable-enable-pair */