Shopify / hydrogen

Hydrogen lets you build faster headless storefronts in less time, on Shopify.
https://hydrogen.shop
MIT License
1.24k stars 246 forks source link

Add experimental codegen support to CLI #707

Closed frandiox closed 1 year ago

frandiox commented 1 year ago

Closes #568 Related #887

Updated description: https://github.com/Shopify/hydrogen/pull/707#issuecomment-1547741906

This PR uses codegen to generate types for every GraphQL operation in the user app and augment the storefront.query / storefront.mutate signatures accordingly.

Without codegen

So far we needed to specify the expected return type in a generic, and then we get all the properties from that type back:

image

With codegen

The returned type is inferred and it only contains the requested properties:

image

This also works with the types for the {variables: {...}} parameter:

image

Drawbacks

Unfortunately, to implement all of this we need 2 changes in the @graphql-tools/graphql-tag-pluck package:

All these changes are included in a vendor folder so that we can use the code locally, but if we want to support these 2 features properly we should talk with the @graphql-tool folks to add a couple of hooks.

Asking here: https://github.com/ardatan/graphql-tools/issues/5127


Tophat

🎩 To test this locally:

  1. npm i at the root
  2. npm run build:pkg
  3. cd templates/hello-world && h2 dev --codegen-unstable
  4. Modify the query in root.tsx or other files.

Feedback

What kind of feedback I'd like to have:

frandiox commented 1 year ago

@frehner I've simplified some of the types and added comments. Can you check if it's more readable now? πŸ™

frehner commented 1 year ago

Random thought: is the #graphql comment preserved and sent over the network to the SFAPI? It's a very small thing, but I wonder if that's why it's not as common to use it that way?

frandiox commented 1 year ago

@frehner

Random thought: is the #graphql comment preserved and sent over the network to the SFAPI? It's a very small thing, but I wonder if that's why it's not as common to use it that way?

We don't send it over the network because we minify the query here. We could do this at build time if we get access to build plugins at some point.

frandiox commented 1 year ago

Features:

Changes:

Gotchas:

Other thoughts / questions

Todos:

github-actions[bot] commented 1 year ago

We detected some changes in packages/*/package.json or packages/*/src, and there are no updates in the .changeset. If the changes are user-facing and should cause a version bump, run npm run changeset add to track your changes and include them in the next release CHANGELOG. If you are making simple updates to examples or documentation, you do not need to add a changeset.

duncan-fairley commented 1 year ago

@frandiox- Sorry for the delay on getting back to you while I was wrapped in Remix conf. Very excited about this and it seems like it will fit in well with (and greatly simplify) our existing projects. Think this will be the most impactful improvement to DX since launch.

The prettier after-write hook is key, great addition.

It sounds from your description like this will support queries and mutations placed in any location in the app (vs only inline in loaders etc)? That's key for us.

frandiox commented 1 year ago

@duncan-fairley Thanks!

It sounds from your description like this will support queries and mutations placed in any location in the app (vs only inline in loaders etc)? That's key for us.

Yeah it works out of the box with any ts/tsx file, and variables don't need to be inlined in the storefront.query(...) function. They can be variables in other files as long as they can be found in ts/tsx files. You might need to add as const to them when using string interpolation, though, but maybe we can add some sort of automatic format/lint for that in the future.

davidhousedev commented 1 year ago

@frandiox Does this handle situations where multiple GraphQL APIs are queried within the same codebase? In our own graphql codegen implementation, we needed to namespace the documents config so it was mutually exclusive between Storefront API and Contentful's GraphQL API

frandiox commented 1 year ago

Does this handle situations where multiple GraphQL APIs are queried within the same codebase? In our own graphql codegen implementation, we needed to namespace the documents config so it was mutually exclusive between Storefront API and Contentful's GraphQL API

I think Codegen will complain when finding queries that don't match the schema so you probably need to keep doing the same: split queries in different files and pass the corresponding globs to each preset. As mentioned at the end of the changeset docs, you can create a <root>/codegen.ts file to do this:

import type {CodegenConfig} from '@graphql-codegen/cli';
import {preset, pluckConfig, schema} from '@shopify/hydrogen-codegen';

export default <CodegenConfig>{
  overwrite: true,
  pluckConfig,
  generates: {
    ['storefrontapi.d.ts']: {
      schema,
      preset,
      documents: ['app/**/*!(.cms).{ts,tsx}'],
    },
    // Your CMS codegen config:
    ['....d.ts']: {
      schema: '...',
      plugins: ['...'],
      documents: ['app/**/*.cms.{ts,tsx}'], // Different documents
    },
  },
};

I'm not sure if there's a better way to do this, open to ideas.

frandiox commented 1 year ago

@davidhousedev

I was thinking about this again. What would you think about something like this?

image

Basically, this would allow mixing queries from different schemas and we can filter them later checking #graphql:sfapi vs #graphql:<name> πŸ€” Not sure if this is desirable or it's better to place them in different files.

davidhousedev commented 1 year ago

Hey @frandiox sorry I missed your response!

There are two considerations here:

  1. Codegen of graphql clients for multiple graphql servers
  2. IDE feedback and auto-completion for graphql queries and mutations

We're able to get the best case scenario working today by using a single graphql codegen file that uses directory namespacing as you describe in your first suggestion. In case you weren't aware, the codegen config can be referenced within a more standard graphql config file. We use the multi-project config mode to get things working.

I would be surprised if your second suggestion would work with IDE tooling; would non-Hydrogen tools be able to differentiate between graphql queries to Shopify vs other APIs?

frandiox commented 1 year ago

This would work by using Codegen's documentTransform so that should be supported when using the Codegen CLI directly. Syntax highlight works in VSCode at least but not sure about other features. I don't have many other integrations set up for GraphQL tbh.

Feel free to try it with something like:

['...']: {
  preset,
  schema,
  documents: ['app/**/*.{ts,tsx}'],
  documentTransforms: [
     {transform: ({documents}) => documents.filter(document => document.rawSDL?.includes('#graphql:<name>')}
  ]
}
davidhousedev commented 1 year ago

@frandiox it's tough to say. If VS Code auto completion works with the documentTransform solution for multiple graphql servers then it's an unclear judgement call. We'd probably prefer to use directory namespacing given that's how we've set up everything on our end, but I'll bring this back to our team to see if anyone has another suggestion.