diff --git a/.changeset/swift-olives-beam.md b/.changeset/swift-olives-beam.md new file mode 100644 index 00000000..24690491 --- /dev/null +++ b/.changeset/swift-olives-beam.md @@ -0,0 +1,26 @@ +--- +'@shopify/hydrogen-react': patch +--- + +Provide a mapping of Storefront API's custom scalars to their actual types, for use with GraphQL CodeGen. + +For example: + +```ts +import {storefrontApiCustomScalars} from '@shopify/hydrogen-react'; + +const config: CodegenConfig = { + // Use the schema that's bundled with @shopify/hydrogen-react + schema: './node_modules/@shopify/hydrogen-react/storefront.schema.json', + generates: { + './gql/': { + preset: 'client', + plugins: [], + config: { + // Use the custom scalar definitions that @shopify/hydrogen-react provides to improve the custom scalar types + scalars: storefrontApiCustomScalars, + }, + }, + }, +}; +``` diff --git a/apps/nextjs/codegen.ts b/apps/nextjs/codegen.ts index 407b7084..272b8db0 100644 --- a/apps/nextjs/codegen.ts +++ b/apps/nextjs/codegen.ts @@ -1,16 +1,28 @@ import type {CodegenConfig} from '@graphql-codegen/cli'; +// Due to issues with TurboRepo and dev dependencies, we can't use this in the monorepo. But if we could, then it would look like this import, and then used below in "scalars" +// import {storefrontApiCustomScalars} from '@shopify/hydrogen-react'; const config: CodegenConfig = { overwrite: true, - // a normal app would only need `./node_modules/...` but we're in a monorepo + // A normal app would only need `./node_modules/...` but we're in a monorepo schema: '../../node_modules/@shopify/hydrogen-react/storefront.schema.json', documents: 'pages/**/*.tsx', - // @TODO a chance here to provide the settings we use to generate our Types for the schema here as well, - // so that the Custom Scalar definitions we have in hydrogen-ui can be used here as well. generates: { './gql/': { preset: 'client', plugins: [], + config: { + // scalars: storefrontApiCustomScalars, + scalars: { + // Because of the limitations outlined above, these need to be kept in sync with the original definitions in the @shopify/hydrogen-react repo! + DateTime: 'string', + Decimal: 'string', + HTML: 'string', + URL: 'string', + Color: 'string', + UnsignedInt64: 'string', + }, + }, }, }, }; diff --git a/apps/nextjs/gql/graphql.ts b/apps/nextjs/gql/graphql.ts index 4fe39c58..6deda5d1 100644 --- a/apps/nextjs/gql/graphql.ts +++ b/apps/nextjs/gql/graphql.ts @@ -18,21 +18,21 @@ export type Scalars = { * For example, "#6A8D48". * */ - Color: any; + Color: string; /** * Represents an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)-encoded date and time string. * For example, 3:50 pm on September 7, 2019 in the time zone of UTC (Coordinated Universal Time) is * represented as `"2019-09-07T15:50:00Z`". * */ - DateTime: any; + DateTime: string; /** * A signed decimal number, which supports arbitrary precision and is serialized as a string. * * Example values: `"29.99"`, `"29.999"`. * */ - Decimal: any; + Decimal: string; /** * A string containing HTML code. Refer to the [HTML spec](https://html.spec.whatwg.org/#elements-3) for a * complete list of HTML elements. @@ -40,7 +40,7 @@ export type Scalars = { * Example value: `"

Grey cotton knit sweater.

"`. * */ - HTML: any; + HTML: string; /** * A [JSON](https://www.json.org/json-en.html) object. * @@ -66,14 +66,14 @@ export type Scalars = { * (`johns-apparel.myshopify.com`). * */ - URL: any; + URL: string; /** * An unsigned 64-bit integer. Represents whole numeric values between 0 and 2^64 - 1 encoded as a string of base-10 digits. * * Example value: `"50"`. * */ - UnsignedInt64: any; + UnsignedInt64: string; }; /** @@ -6845,7 +6845,7 @@ export enum WeightUnit { export type IndexQueryQueryVariables = Exact<{ [key: string]: never; }>; -export type IndexQueryQuery = { __typename?: 'QueryRoot', shop: { __typename?: 'Shop', name: string }, products: { __typename?: 'ProductConnection', nodes: Array<{ __typename?: 'Product', id: string, title: string, publishedAt: any, handle: string, variants: { __typename?: 'ProductVariantConnection', nodes: Array<{ __typename?: 'ProductVariant', id: string, image?: { __typename?: 'Image', url: any, altText?: string | null, width?: number | null, height?: number | null } | null }> } }> } }; +export type IndexQueryQuery = { __typename?: 'QueryRoot', shop: { __typename?: 'Shop', name: string }, products: { __typename?: 'ProductConnection', nodes: Array<{ __typename?: 'Product', id: string, title: string, publishedAt: string, handle: string, variants: { __typename?: 'ProductVariantConnection', nodes: Array<{ __typename?: 'ProductVariant', id: string, image?: { __typename?: 'Image', url: string, altText?: string | null, width?: number | null, height?: number | null } | null }> } }> } }; export const IndexQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"IndexQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"shop"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"products"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"publishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"variants"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"image"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"altText"}},{"kind":"Field","name":{"kind":"Name","value":"width"}},{"kind":"Field","name":{"kind":"Name","value":"height"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/react/README.md b/packages/react/README.md index 2229d24c..f55b055c 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -154,6 +154,29 @@ If you're having trouble getting it to work, then consult our [troubleshooting s Improve your development experience by adding strong typing to Storefront API responses. The following are some options for doing this. +## GraphQL CodeGen + +To use GraphQL CodeGen, follow [their guide](https://the-guild.dev/graphql/codegen/docs/getting-started/installation) to get started. Then, when you have a `codegen.ts` file, you can modify the following lines in the codegen object to improve the CodgeGen experience: + +```ts +import {storefrontApiCustomScalars} from '@shopify/hydrogen-react'; + +const config: CodegenConfig = { + // Use the schema that's bundled with @shopify/hydrogen-react + schema: './node_modules/@shopify/hydrogen-react/storefront.schema.json', + generates: { + './gql/': { + preset: 'client', + plugins: [], + config: { + // Use the custom scalar definitions that @shopify/hydrogen-react provides to improve the types + scalars: storefrontApiCustomScalars, + }, + }, + }, +}; +``` + ### Use the `StorefrontApiResponseError` and `StorefrontApiResponseOk` helpers The following is an example: diff --git a/packages/react/codegen.ts b/packages/react/codegen.ts new file mode 100644 index 00000000..68cd4949 --- /dev/null +++ b/packages/react/codegen.ts @@ -0,0 +1,58 @@ +import {CodegenConfig} from '@graphql-codegen/cli'; +// Because this file is processed only in TypeScript, we can make one exception for not using an extension here. +// eslint-disable-next-line import/extensions +import {storefrontApiCustomScalars} from './src/codegen.helpers'; + +const config: CodegenConfig = { + overwrite: true, + schema: { + 'https://hydrogen-preview.myshopify.com/api/2022-10/graphql.json': { + headers: { + 'X-Shopify-Storefront-Access-Token': '3b580e70970c4528da70c98e097c2fa0', + 'content-type': 'application/json', + }, + }, + }, + generates: { + // The generated base types + 'src/storefront-api-types.d.ts': { + plugins: [ + { + add: { + content: ` + /** + * THIS FILE IS AUTO-GENERATED, DO NOT EDIT + * Based on Storefront API 2022-10 + * If changes need to happen to the types defined in this file, then generally the Storefront API needs to update. After it's updated, you can run \`yarn graphql-types\`. + * Except custom Scalars, which are defined in the \`codegen.ts\` file + */ + /* eslint-disable */`, + }, + }, + { + typescript: { + useTypeImports: true, + // If a default type for a scalar isn't set, then instead of 'any' we set to 'unknown' for better type safety. + defaultScalarType: 'unknown', + useImplementingTypes: true, + enumsAsTypes: true, + // Define how the Storefront API's custom scalars map to TypeScript types + scalars: storefrontApiCustomScalars, + }, + }, + ], + }, + // The schema file, which is the local representation of the GraphQL endpoint + './storefront.schema.json': { + plugins: [ + { + introspection: { + minify: true, + }, + }, + ], + }, + }, +}; + +export default config; diff --git a/packages/react/codegen.yml b/packages/react/codegen.yml deleted file mode 100644 index 1ae65595..00000000 --- a/packages/react/codegen.yml +++ /dev/null @@ -1,38 +0,0 @@ -overwrite: true -schema: - - https://hydrogen-preview.myshopify.com/api/2022-10/graphql.json: - headers: - X-Shopify-Storefront-Access-Token: 3b580e70970c4528da70c98e097c2fa0 - content-type: application/json -generates: - # the base types that are generated - src/storefront-api-types.d.ts: - plugins: - - add: - content: - - '/** ' - - ' * THIS FILE IS AUTO-GENERATED, DO NOT EDIT' - - ' * Based on Storefront API 2022-10' - - ' * If changes need to happen to the types defined in this file, then generally the Storefront API needs to update, and then you can run `yarn graphql-types`' - - ' * Except custom Scalars, which are defined in the `codegen.yml` file' - - ' */' - - '/* eslint-disable */' - - typescript: - useTypeImports: true - # If a default type for a scalar isn't set, then instead of 'any' let's set to 'unknown' for better type safety. - defaultScalarType: unknown - useImplementingTypes: true - enumsAsTypes: true - # Define how the SFAPI's custom scalars map to TS types - scalars: - DateTime: string - Decimal: string - HTML: string - URL: string - Color: string - UnsignedInt64: string - # the schema file, which is the local representation of the graphql endpoint - ./storefront.schema.json: - plugins: - - 'introspection': - minify: true diff --git a/packages/react/package.json b/packages/react/package.json index b661ea58..8d0e89a7 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -91,7 +91,7 @@ "build:tsc:es": "tsc --emitDeclarationOnly --project tsconfig.typeoutput.json", "copy-storefront-types": "cpy ./src/storefront-api-types.d.ts ./dist/types/ --flat", "format": "prettier --write \"src/**/*\"", - "graphql-types": "graphql-codegen --config codegen.yml && yarn format", + "graphql-types": "graphql-codegen --config codegen.ts && yarn format", "prepack": "yarn build", "test": "vitest", "test:ci": "vitest run --coverage", diff --git a/packages/react/src/AddToCartButton.tsx b/packages/react/src/AddToCartButton.tsx index 1b777945..49de32e3 100644 --- a/packages/react/src/AddToCartButton.tsx +++ b/packages/react/src/AddToCartButton.tsx @@ -4,7 +4,7 @@ import {useCart} from './CartProvider.js'; import {useProduct} from './ProductProvider.js'; import {BaseButton, BaseButtonProps} from './BaseButton.js'; -interface AddToCartButtonProps { +export interface AddToCartButtonProps { /** An array of cart line attributes that belong to the item being added to the cart. */ attributes?: { key: string; diff --git a/packages/react/src/codegen.helpers.ts b/packages/react/src/codegen.helpers.ts new file mode 100644 index 00000000..0e7f7da1 --- /dev/null +++ b/packages/react/src/codegen.helpers.ts @@ -0,0 +1,10 @@ +/** Meant to be used with GraphQL CodeGen to type the Storefront API's custom scalars correctly */ +export const storefrontApiCustomScalars = { + // Keep in sync with the definitions in the app/nextjs/codegen.ts! + DateTime: 'string', + Decimal: 'string', + HTML: 'string', + URL: 'string', + Color: 'string', + UnsignedInt64: 'string', +}; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 6e8815fa..64cb4ff2 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -9,6 +9,7 @@ export type { } from './cart-types.js'; export {CartCheckoutButton} from './CartCheckoutButton.js'; export {CartProvider, useCart} from './CartProvider.js'; +export {storefrontApiCustomScalars} from './codegen.helpers.js'; export {ExternalVideo} from './ExternalVideo.js'; export {flattenConnection} from './flatten-connection.js'; export {Image} from './Image.js'; diff --git a/packages/react/src/storefront-api-types.d.ts b/packages/react/src/storefront-api-types.d.ts index 7846baf1..9bc73c1e 100644 --- a/packages/react/src/storefront-api-types.d.ts +++ b/packages/react/src/storefront-api-types.d.ts @@ -2,7 +2,7 @@ * THIS FILE IS AUTO-GENERATED, DO NOT EDIT * Based on Storefront API 2022-10 * If changes need to happen to the types defined in this file, then generally the Storefront API needs to update, and then you can run `yarn graphql-types` - * Except custom Scalars, which are defined in the `codegen.yml` file + * Except custom Scalars, which are defined in the `codegen.ts` file */ /* eslint-disable */ export type Maybe = T | null;