graphql-code-generator icon indicating copy to clipboard operation
graphql-code-generator copied to clipboard

GraphQL Code Generator v5 Roadmap

Open theguild-bot opened this issue 1 year ago • 62 comments

This page is synced automatically from The Guild's Notion Notion page URL: https://www.notion.so/GraphQL-Code-Generator-v4-Roadmap-91923bfb2dee48eaa0d6a77666429968

At The Guild, we’ve decided to work as much as possible in public; that’s why we are opening the roadmaps for all of our projects.

The goals for this are:

  1. So you will know what we are working on, what we see as a higher priority, and know what to expect from our projects in the future
  2. So you can share your opinions and thoughts about what we do and influence our decisions
  3. So you can join us and contribute to our efforts!

Before laying down the roadmap of GraphQL Code Generator v3, we would like to thank all of you for being so many who use codegen daily and for contributing to making it such a complete project! 🚀

While some people judge that GraphQL is difficult, GraphQL Code Generator v3 aims to change that perspective by providing a unified configuration along with a smaller and simpler generated code.

By providing a unified package and configuration for all client-side use cases, all existing and future plugin alternatives will be moved to community repos.

Let’s now cover these changes in detail.

A unified configuration and package for all GraphQL clients

Most of the existing client-side plugins (typescript-react-apollo, typescript-react-query, etc) rely on the generation of hooks or SDKs that wrap the underlying GraphQL Client in a type-safe way.

However, the generation of hooks or SDK code brings many downsides:

  • an unnecessary increase of the final bundle size
  • misalignment between the generated hooks signature and the underlying GraphQL Client
  • inconsistencies of configuration options and preset compatibility across packages (ex: near-operation-file compatibility)

To make GraphQL code generation great and simple, the v3 version will introduce two major changes:

  • a new unique preset for all GraphQL clients, which include better developer experience, smaller bundle size, stronger typings, and easier-to-follow best practices
  • a TypeScript-first configuration file that will allow configuration autocompletion

Here is how you can already configure codegen for all GraphQL Clients:

import { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  schema: 'http://localhost:4000/graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: []
    }
  }
}

export default config

The client preset comes with a simple opinionated configuration and a lightweight types-only generation.

To try the new client preset, please install the following dependencies:

yarn add graphql
yarn add -D typescript
yarn add -D @graphql-codegen/cli
yarn add -D @graphql-codegen/client-preset

First, start GraphQL Code Generator in watch mode:

yarn graphql-codegen --watch

Using GraphQL Code Generator will type your GraphQL Query and Mutations as you write them ⚡️

Now, each query or mutation written with the generated graphql() function will be automatically typed!

For example, with Apollo Client (React):

import React from 'react';
import { useQuery } from '@apollo/client';
import { graphql } from './gql/gql';

import Film from './Film';

// here, `allFilmsWithVariablesQueryDocument` is fully typed!
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
  query allFilmsWithVariablesQuery($first: Int!) {
    allFilms(first: $first) {
      edges {
        node {
          ...FilmItem
        }
      }
    }
  }
`);

function App() {
	// Most GraphQL Clients know how to deal with typed GraphQL documents,
	//   providing typed data and typed variables
  const { data } = useQuery(allFilmsWithVariablesQueryDocument, { variables: { first: 10 } });
  return (
    <div className="App">
      {data && <ul>{data.allFilms?.edges?.map((e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />)}</ul>}
    </div>
  );
}

export default App;

Thanks to work made to integrate TypeDocumentNode (the underlying plugin used by preset: client) with most of the popular GraphQL clients, you no longer need hooks or SDK, simple GraphQL documents works!

We believe that the preset: client approach is the way to get the best of TypeScript and GraphQL by:

  • reducing the size of the generated bundle
  • only the graphql() function needs to be imported (no type, hooks, document imports)
  • removing layers between your application and your chosen GraphQL Client
  • providing stronger typings that will stay aligned with your chosen GraphQL Client
  • offering you the best component isolation design by leveraging Fragment Masking

Finally, this new preset: client has been properly tested on all popular GraphQL clients across most frameworks:

  • React
    • @apollo/client (since 3.2.0, not when using React Components (<Query>))
    • @urql/core (since 1.15.0)
    • @urql/preact (since 1.4.0)
    • urql (since 1.11.0)
    • graphql-request (since 5.0.0)
    • react-query (with [email protected])
    • swr (with [email protected])
    • @urql/exchange-graphcache (since 3.1.11)
  • Svelte
    • @urql/svelte (since 1.1.3)
  • Vue
    • @vue/apollo-composable (since 4.0.0-alpha.13)
    • villus (since 1.0.0-beta.8)
    • @urql/vue (since 1.11.0)
  • Others
    • graphql-js (since 15.2.0)
    • graphql-request (since 5.0.0)

You will find demos and code examples for each of them in the examples/front-end/ folder of the codegen repository.

You will also find a complete guide for React and Vue in codegen documentation.

We aim for GraphQL Code Generator 3.0’s client preset to become the official way to generate GraphQL Types for front-end use cases, replacing all existing hook and SDK-based plugins.

For this reason, we encourage you to already give a try at the codegen v3 client preset (@graphql-codegen/client-presec) and provide feedback on this issue.

The v3 stable release will be shipped once sufficient feedback is posted.

Finally, while the GraphQL Code Generator 3.0 milestone aims to provide a unified front-end experience through the preset: client, the 3.x versions aim to fully rewrite the core packages of codegen.

Some core parts of codegen are more than 6 years old and need to be rewritten (optimized, simplified, and more).

We plan to incorporate the pending issues related to the core packages in this gradual 3.x milestones.

Introduction of the “community plugins”

Historically, all plugins were pushed to the https://github.com/dotansimha/graphql-code-generator repository, making it hard for us to review all contributions in a reasonable timeframe and to enforce consistency across all the options introduced in the different packages.

We believe that the best way to keep codegen extensible and improve the contribution experience at scale is to introduce the concept of community plugins.

A community plugin offers a feature-set that diverges from the preset: client or a plugin created by the community.

Soon, all the existing plugins part of the list below and all the future plugins created by the community will live in their dedicated repository:

  • @graphql-codegen/typescript-react-apollo
  • @graphql-codegen/typescript-graphql-request
  • @graphql-codegen/typescript-apollo-angular
  • @graphql-codegen/typescript-apollo-client-helpers
  • @graphql-codegen/typescript-react-query
  • @graphql-codegen/typescript-urql
  • @graphql-codegen/named-operations-object
  • @graphql-codegen/urql-introspection
  • @graphql-codegen/flow-resolvers
  • @graphql-codegen/typescript-vue-apollo
  • @graphql-codegen/typescript-rtk-query
  • @graphql-codegen/flow-operations
  • @graphql-codegen/typescript-msw
  • @graphql-codegen/typescript-mongodb
  • @graphql-codegen/typescript-type-graphql
  • @graphql-codegen/jsdoc
  • @graphql-codegen/typescript-vue-urql
  • @graphql-codegen/kotlin
  • @graphql-codegen/typescript-vue-apollo-smart-ops
  • @graphql-codegen/java
  • @graphql-codegen/c-sharp-operations
  • @graphql-codegen/hasura-allow-list
  • @graphql-codegen/typescript-stencil-apollo
  • @graphql-codegen/relay-operation-optimizer
  • @graphql-codegen/typescript-oclif
  • @graphql-codegen/java-resolvers
  • @graphql-codegen/java-apollo-android

All the above plugins will be eligible for repository ownership transfer based on relevant past contributions.

Of course, such a change will come with help from our side:

  • We will create a new “Create a plugin” guide that will provide complete information and guidelines (ex: publishing, codegen APIs, adding your plugin to the codegen hub)
  • Since each community plugin will live in its own repository, we will provide a proper Github repository template with building and publishing CI tools configured.

What about server-side plugins?

The 3.x milestones include some work on server-side plugins such as typescript-resolvers (ex: improving Federation support).


Milestones

Below are the details of the aforementioned plans for the 3.0 and 3.x milestones.

3.0

3.x

preset: client improvements

Future of codegen CLI

Back-end code generation issues

We will go over the following typescript-resolvers and graphql-modules pending plugins issues:

theguild-bot avatar Aug 29 '22 10:08 theguild-bot

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide for example useFragment etc.

huv1k avatar Aug 30 '22 06:08 huv1k

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide, for example, useFragment etc.

Hi @huv1k! We are planning to remove the support for module augmentation since it is not working without the associated babel plugin properly set up. Since not all front-end setups can provide custom babel configuration, we were planning to remove the augmentedModuleName options and also to simplify the installation flow.

However, this being an RFC, we will take your feedback into account! 👀

charlypoly avatar Aug 30 '22 09:08 charlypoly

Based on your code example, does that mean that the preferred way to consume GraphQL operations is co-location within TS/JS files?

I just migrated a large codebase from Apollo CLI to graphql-codegen v2 and refactored it to move all GraphQL operation definitions to .graphql files (importing only typed documents to pass to Apollo Client), I wouldn't want to have to undo that all over again..

franky47 avatar Aug 31 '22 01:08 franky47

Based on your code example, does that mean that the preferred way to consume GraphQL operations is co-location within TS/JS files?

I just migrated a large codebase from Apollo CLI to graphql-codegen v2 and refactored it to move all GraphQL operation definitions to .graphql files (importing only typed documents to pass to Apollo Client), I wouldn't want to have to undo that all over again..

Hi @franky47,

We are exploring different migration path options; providing a codemod could be one; however, we have to look at the different setups to migrate from. What plugins are you currently using?

charlypoly avatar Aug 31 '22 09:08 charlypoly

Hi @charlypoly, here's my codegen.yml:

overwrite: true
schema: "../backend/schema.graphql"
documents: "src/**/*.graphql"
config: # The following configuration will adjust output to the defaults of Apollo-Codegen, and should be compatible with most use-cases.
  preResolveTypes: true # Simplifies the generated types
  namingConvention: keep # Keeps naming as-is
  avoidOptionals: # Avoids optionals on the level of the field
    field: true
  nonOptionalTypename: true # Forces `__typename` on all selection sets
  skipTypeNameForRoot: true # Don't generate __typename for root types
generates:
  src/modules/graphql.types.ts:
    plugins:
      - typescript
      - typescript-operations
      - typed-document-node
    hooks:
      afterOneFileWrite:
        - prettier --write

I have GraphQL code into .graphql files, which are parsed by the generator to give me the full schema types (I use them to type props in a React/Next.js app), and typed schemas to pass to Apollo Client, which provides type inference (variables & output), so that TypeScript has my back when matching the data & view layers.

I migrated from a setup where the Apollo CLI generated the TS types from gql tags co-located with their components in .ts(x) files, hence my remark that having to go back would be quite annoying.

I really like the typed document plugin, it removed the need to explicitly pass generic arguments to useQuery and useMutation, so most of my query/mutation hooks are now one-liners. The only thing that required a bit of refactoring to use those was that their naming convention does not seem to be configurable, so I had to rename all FOO_{QUERY|MUTATION} to FooDocument, not a big deal but the name can be confusing sometimes (especially when dealing with domain objects also named "Document").

Hope this helps you see a bit better about my particular use-case.

franky47 avatar Aug 31 '22 10:08 franky47

@franky47, thank you for sharing it!

We are also exploring options to still support .graphql files, we will keep you updated here.

charlypoly avatar Aug 31 '22 10:08 charlypoly

I think there are some problems with this:

  1. The generation time has gone way up. It has doubled up infact.
  2. And I see that there have been duplicate types being generated. The size of the file(s) have also gone way up.

nandita1 avatar Sep 02 '22 16:09 nandita1

vue 3 and vue graphql v4 problem

image

but it works when these lines are added

productdevbook avatar Sep 05 '22 05:09 productdevbook

If it is going full TypeDocumentNode way (which I like), is it not better to go with a builder pattern approach. The builder can be full type safe and be generated from the introspection query. Additionally you need to generate the builder only initially and on schema change and not when adding new documents. Some open source projects which apply this principle:

  • https://github.com/typed-graphql-builder/typed-graphql-builder (my favorite)
  • https://zeus.graphqleditor.com/page/plugins/typedDocumentNode.html
  • https://github.com/timkendall/tql

In my opinion this provides best of the worlds. Small bundle size, no watchers necessary on frontend and much faster dev cycle. Additionally the builder can be scoped per context (think about hasura roles or public/private introspection results)

ilijaNL avatar Sep 09 '22 19:09 ilijaNL

Will there be support for module augmentation still? 🤔 I really enjoy this feature, but there are some shortcomings like some frameworks don't provide, for example, useFragment etc.

Hi @huv1k! We are planning to remove the support for module augmentation since it is not working without the associated babel plugin properly set up. Since not all front-end setups can provide custom babel configuration, we were planning to remove the augmentedModuleName options and also to simplify the installation flow.

However, this being an RFC, we will take your feedback into account! 👀

Augmented module is a nice feature for people who don't like having their own source code polluted by/relying on generated code, why not keeping it for those willing to make advanced configuration? (babel plugin is just a one line configuration).

Inspired by what graphql-let does, I personally came up with some kind of hack that generates everything to a fake node_modules/@types of a monorepo:

schema: path/to/schema.json
documents:
  - src/**/*.ts
  - src/**/*.tsx
generates:
  # app operations are generated inside a fake @types module
  ../../node_modules/@types/myProject__graphql-client:
    preset: gql-tag-operations-preset
    presetConfig:
      augmentedModuleName: '@myProject/graphql-client'
      fragmentMasking:
        augmentedModuleName: '@myProject/graphql-client'
hooks:
  # here is the ugliest part of the "hack": generating a package.json
  afterAllFileWrite:
    - "printf '{ \"name\": \"@types/myProject__graphql-client\", \"types\": \"gql.d.ts\" }' > '../../node_modules/@types/myProject__graphql-client/package.json'"

This allows for everything within an app to be transparently imported from a @myProject/graphql-client (a preconfigured apollo-client module in the monorepo) without ever seeing/dealing with any ugly generated code 😅

Big advantage: I can remove codegen at any time without breaking the code, only losing types!

import { gql, useQuery } from '@myProject/graphql-client'

const MyQuery = gql(`
  query MyQuery {
    ...
  }
`)

const MyComponent = () => {
  const { loading, data, error } = useQuery(MyQuery)

  return <>...</>
}

It would be nice to keep this ability to hide the generated code somewhere.

hrougier avatar Sep 14 '22 23:09 hrougier

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

config:

overwrite: true
schema: 'http://localhost:8080/v1/graphql'
documents:
  - './src/**/*.graphql'
  - './src/**/*.svelte'
generates:
  src/lib/graphql/new/:
    preset: client
    config:
      strictScalars: true
      scalars:
        numeric: number
        jsonb: Array<any>
        bigint: number
        uuid: string
        timestamptz: string
        timestamp: string

generated scalars:

export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
  bigint: any;
  jsonb: any;
  numeric: any;
  timestamp: any;
  timestamptz: any;
  uuid: any;
};

grischaerbe avatar Sep 24 '22 09:09 grischaerbe

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

Hi @grischaerbe,

We restored some config options in the client preset, from @graphql-codegen/[email protected]: more details in the changelog: https://github.com/dotansimha/graphql-code-generator/releases/tag/release-1665062191991

charlypoly avatar Oct 06 '22 13:10 charlypoly

I really like the new approach with preset: client. It seems like a big step towards a simpler toolchain with less dependencies. I guess currently the configuration for a custom scalar types is not considered:

Hi @grischaerbe,

We restored some config options in the client preset, from @graphql-codegen/[email protected]: more details in the changelog: release-1665062191991 (release)

@charlypoly I use [email protected] with

        presetConfig:
          augmentedModuleName: "@apollo/client"
          namingConvention:
            typeNames: change-case#pascalCase

but the generated still gives Claim_Cases_Bool_Exp instead of ClaimCasesBoolExp

deathemperor avatar Oct 08 '22 16:10 deathemperor

Hi What is the difference between preset: client and preset: gql-tag-operations-preset ?

https://the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen https://www.the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset

sonatard avatar Oct 09 '22 06:10 sonatard

Hi What is the difference between preset: client and preset: gql-tag-operations-preset ?

the-guild.dev/blog/unleash-the-power-of-fragments-with-graphql-codegen the-guild.dev/graphql/codegen/plugins/presets/gql-tag-operations-preset

Hi @sonatard, The client preset is a wrapper on top of gql-tag-operations-preset. It's going to deprecate gql-tag-operations-preset soon, and introduce better integration with GraphQL clients.

dotansimha avatar Oct 09 '22 07:10 dotansimha

React-query example... Isn't strongly typed. data is any etc.

Faithfinder avatar Oct 18 '22 15:10 Faithfinder

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder,

Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

charlypoly avatar Oct 20 '22 09:10 charlypoly

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder,

Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

Just checking out an example you've supplied: https://github.com/dotansimha/graphql-code-generator/tree/v3-preset-front-end/examples/front-end/react/tanstack-react-query

Using twoslash here to demostrate that data is any, and on request to demonstrate that yarn install has been run and types are in place, generally. image

Faithfinder avatar Oct 20 '22 13:10 Faithfinder

Hi @charlypoly / @dotansimha. I'm trying out the client preset and so far it's going well but I'm missing some config options that I could previously set: useTypeImports, arrayInputCoercion, immutableTypes, nonOptionalTypename. I understand that the preset is being strict about which options are passed along but I'm wondering why that is? Are plugin configs also deliberately disabled?

joelmukuthu avatar Oct 21 '22 14:10 joelmukuthu

React-query example... Isn't strongly typed. data is any etc.

Hi @Faithfinder, Could you provide more context about which example you are referring to? The client-preset provides typed GraphQL operations when React Query is used in combination with a supported GraphQL client (ex: graphql-request@^5) or when the fetcher is properly typed (as documented in our guide).

Just checking out an example you've supplied: v3-preset-front-end/examples/front-end/react/tanstack-react-query

Using twoslash here to demostrate that data is any, and on request to demonstrate that yarn install has been run and types are in place, generally. image

Hi @Faithfinder,

I've moved your discussion to a dedicated bug issue: https://github.com/dotansimha/graphql-code-generator/issues/8516 I couldn't reproduce your issue; let's move forward on a reproduction here.

charlypoly avatar Oct 24 '22 09:10 charlypoly

Hi @charlypoly / @dotansimha. I'm trying out the client preset and so far it's going well but I'm missing some config options that I could previously set: useTypeImports, arrayInputCoercion, immutableTypes, nonOptionalTypename. I understand that the preset is being strict about which options are passed along but I'm wondering why that is? Are plugin configs also deliberately disabled?

Hi @joelmukuthu,

useTypeImports, arrayInputCoercion are still available to use, as documented here: https://www.the-guild.dev/graphql/codegen/docs/guides/react-vue#config-api

The new preset: "client" reduces the number of available options to recommend best practices of codegen configuration. We will work this week on refining the list of available options and also, the default values of some.

charlypoly avatar Oct 24 '22 09:10 charlypoly

Love the idea of how the client-preset works in terms of TypedDocumentNode. I'm running into issues integrating into my codegen due to the lack of plugin support along with the preset. I use the add plugin to import my scalars from a different file (as well as to add a comment to my generated files).

      - add:
          content: "// This file is automatically generated. See gqlcodegen.yml for details"
      - add:
          content: "import * as CustomScalars from \"@src/types/scalars\";"
config:
  scalars:
    Any: CustomScalars.GQLAny
    JSONObject: CustomScalars.GQLJSONObject

with the preset i get the following error:

✖ [client-preset] providing plugins with `preset: "client" leads to duplicated generated types

Is there a workaround here? I understand wanting to disable plugins due to the one-size-fits-all nature of the preset, but an escape hatch would be nice

awinograd avatar Oct 24 '22 20:10 awinograd

Is there a workaround here? I understand wanting to disable plugins due to the one-size-fits-all nature of the preset, but an escape hatch would be nice

Hi @awinograd,

Thank you for bringing this up! I'll remove the add from the disallow plugin list.

charlypoly avatar Oct 25 '22 07:10 charlypoly

Hi @awinograd,

Thank you for bringing this up! I'll remove the add from the disallow plugin list.

@aradalvand is has been fixed in @graphql-codegen/[email protected] 📦

charlypoly avatar Oct 25 '22 10:10 charlypoly

@charlypoly I think you meant to mention @awinograd and not me :)

aradalvand avatar Oct 25 '22 11:10 aradalvand

Hi @charlypoly I think it's great with the type inference from the query string so that we don't have to import the type def. I have always not liked the importing of hooks with custom names, so I think this is a great way forwards. I've run into a few problems while testing this out in our codebase.

  1. Sometimes you are just using a fragment to reduce duplicated definitions, not to pass data to a child component. In some cases this fragment lives in the same string as the parent query/fragment. But the generated types masks the selected data from this fragment. I would expect it to be available without using the hook.
  2. Sometimes, we have a component where we used the Fragment type for a prop, but we use it in a place where the data doesn't come from graphql. What is the suggested solution to pass the data? Currently I just did a type cast to the FragmentType which works but seems a bit hacky.
  3. If I postfix fragment names with Fragment (like in the docs) the type names don't seem to be deduplicated by default (they will have FragmentFragment as postfix). That caused an issue because I had added dedupeOperationSuffix: true to my config (just copy pasted from prev. config), because the gql.ts file was deduped but not the graphql.ts file. Also when importing the fragment type, if I want to use it somewhere, I would prefer that it's deduped.

These issues leads me to think that it might be useful to have an option to disable the query masking. Masking seems a bit unnecessary to me when you are using TypeScript anyways, as it doesn't force you to remove data when you don't use it anymore. It basically just checks that you have selected the data in the components fragment. But is there actually any danger in using data that's selected by parent/child components? TypeScript would catch it if we were to remove one of those components and we no longer query the data.

marco2216 avatar Oct 27 '22 11:10 marco2216

Hi @marco2216,

Thank you for your feedback; we appreciate it! Let me answer your questions.

  • Sometimes you are just using a fragment to reduce duplicated definitions, not to pass data to a child component. In some cases this fragment lives in the same string as the parent query/fragment. But the generated types masks the selected data from this fragment. I would expect it to be available without using the hook.

You can disable Fragment Masking by passing the following presetConfig:

import { CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
  documents: ['src/**/*.tsx'],
  ignoreNoDocuments: true, // for better experience with the watcher
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
            fragmentMasking: false,
      },
      plugins: []
    }
  }
}
 
export default config

2. Sometimes, we have a component where we used the Fragment type for a prop, but we use it in a place where the data doesn't come from graphql. What is the suggested solution to pass the data? Currently I just did a type cast to the FragmentType which works but seems a bit hacky.

Could you provide an example? Isn't FragmentType<typeof fragmentDocument> solving this issue?

3. If I postfix fragment names with Fragment (like in the docs) the type names don't seem to be deduplicated by default (they will have FragmentFragment as postfix). That caused an issue because I had added dedupeOperationSuffix: true to my config (just copy pasted from prev. config), because the gql.ts file was deduped but not the graphql.ts file. Also when importing the fragment type, if I want to use it somewhere, I would prefer that it's deduped.

The dedupeOperationSuffix: true should not affect the output since it is not part of the config API option supported by the client-preset. Could you share your configuration?

charlypoly avatar Oct 27 '22 12:10 charlypoly

@charlypoly

  1. Good to know, thanks. But what if I want masking enabled in general but just not for fragments that are defined in the same fragment/query string? Maybe it's possible to parse and check that if it's local to the component, don't mask? Or maybe with a special directive or comment?
  2. I can cast it to FragmentType<typeof fragmentDocument>, but then I lose the type safety on the data that's passed in. I can type cast an empty object to FragmentType<typeof fragmentDocument>.
  3. Used client preset v1.1.1 with this config image

marco2216 avatar Oct 27 '22 12:10 marco2216

First off thanks for keeping it up, guys! We have a large codebase with files in .graphql so that support would be great! Looking at generated files I can say this: it is expensive to have an operation string as a key in documents object in gql.ts. Moreover the generated document is suboptimal:

/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

const documents = {
    "\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n": types.WorkplaceRegFragmentDoc,
    "\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n": types.EmployeeQuantsDocument,
};

export function graphql(source: "\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n"): (typeof documents)["\n  fragment WorkplaceReg on ManufactureEmployeeReg {\n    id\n    workplace {\n      id\n      name\n      quant {\n        id\n        picking\n        name\n      }\n    }\n    checkIn {\n      id\n      createdAt\n    }\n  }\n"];
export function graphql(source: "\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n"): (typeof documents)["\n  query employeeQuants($id: ID!) {\n    employee(id: $id) {\n      id\n      workplaceRegs {\n        id\n        workplace {\n          id\n          name\n          quant {\n            id\n            name\n          }\n        }\n        ...WorkplaceReg\n      }\n      storageWorkerClasses {\n        id\n      }\n    }\n  }\n"];

export function graphql(source: string): unknown;
export function graphql(source: string) {
  return (documents as any)[source] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<  infer TType,  any>  ? TType  : never;

What can be improved: every source is triplicated (repeated in 3 places): in document key and graphql(source: __here__)(typeof documents):[__here__]

Assign it to a constant with the name of the Fragment of Query etc and you get a reduced file (bundle) size.

You may easily extract the query/mutation/fragment name and use it as an inferred type and the key in documents, like so without any need of overloading graphql function type:

import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';

/* eslint-disable */
import * as types from './graphql';

const documents = {
  'WorkplaceReg': types.WorkplaceRegFragmentDoc,
  'employeeQuants': types.EmployeeQuantsDocument,
};

type DocumentKey = keyof typeof documents;
type OperationType = 'query' | 'fragment' | 'mutation' | 'subscription';
type GenericDeclaration<T extends string, V extends OperationType> = V extends 'fragment'? `\n  ${V} ${T} on ${string}`:`\n  ${V} ${T}${'(' | ' on' | ' {'}${string}`;

export function graphql<T extends string, V extends OperationType>(
  source: GenericDeclaration<T, V>,
): T extends DocumentKey ? typeof documents[T] : never {
// use a different approach (regex?) as to extract the name that corresponds to T 
const extractedName:T = pseudoFn()
  return (documents)[extractedName] ?? {};
}

export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode<
  infer TType,
  any
>
  ? TType
  : never;

Secondly you stated that this approach is better than plugins with sdk generators. Well, it is not! you have the documents constants which is no different than what the typescript-generic-sdk plugin we wrote does - it stores all document nodes json in one big object which cannot be tree shaken. If I'm wrong, I'm happy to be corrected. Actually if you look up the plugin in your repo - we took the approach of using operation names instead of operation string code. It's up to the dev to make sure his methods are unique in naming. We have a huge graphql scheme and didn't have any naming collisions so far.

jahglow avatar Oct 27 '22 13:10 jahglow

Yeah and lastly fragmentMasking: false, is not listed in the link you provided above

jahglow avatar Oct 27 '22 14:10 jahglow