GraphQL Code Generator is around for almost 5 years, and it's improving the developer experience of many developers (>3M downloads a month on NPM!). Like all of The Guild's projects, throughout all these years, the library continuously evolved based on the experience and feedback from the community and our clients - every day codegen gets better and better, so keep your feedback coming and keep your dependencies up to date :) But now, it's time for a big release!
We recently decided to revisit and improve some parts of codegen, and we're happy to announce that we're releasing a new version of the tool (v2
)!
This release is all about improving the reliability and readability of the generated code, and adding new features (and a new plugin/preset that might revolutionize how we generate code on our frontends!).
v2
?Until now GraphQL Codegen has evolved, without the need for (major) breaking changes, but there were a couple of things we wanted to do for a better and easier codegen experience that required some small breaking changes.
We are currently working on adding ESM support for all The Guild's tools.
Unfortunately, in Codegen, we are not there yet (you can track the progress here, because we need to make sure that plugins, presets, schemas and documents are still being loaded correctly. But it's coming soon!
But other tools, which we use as a dependency, like graphql-tools
(v8) already supports ESM, and dropped support for Node 10, so we are aligning to that.
We've updated to the latest version of graphql-tools
(v8) and graphql-config
(v4) to get some upstream bug fixes.
A few years ago, we changed the output of codegen to use Pick
in order to build the operation types based on the schema types:
// Base types generated based on the schema, from `typescript` plugin:
type Query = {
user: User
}
type User = {
id: Scalars['ID']
name: Scalars['String']
}
// Types generate based on operations, from `typescript-plugins`:
type MyQuery = {
user: Pick<User, 'id' | 'name'>
}
Some time after, we added preResolveTypes
configuration flag as experimental, in order to generate more readable types with primitive types resolved directly on the generate types:
type MyQuery = {
user: {
id: string
name: string
}
}
We are happy to announce that v2 of typescript-operations
now uses preResolveTypes: true
by default, so all generated types are more readable.
As a user of Codegen this is not a breaking change (aside that the generated code is slightly different), and all your types are still fully compatible with
v1
of the tool. You can setpreResolveTypes: false
if you prefer to keep the old behavior.
In addition to more readable types, we also improved the accuracy of the generated types.
A few months ago, chrbala
found a bug in the typescript-operations
plugin, which was causing the generated types to be incorrect in cases where you use nested and complex fragments.
This bug was caused by the fact that we were combining the fragment types using the &
operator of TypeScript, and this operator doesn't apply deep merging for nested sub-types. So when using multiple fragments (MyFirstFragment & MySecondArgument
), the nested fields are being overwritten, instead of being merged.
n1ru4l
picked that up and fixed the bug and added support for better handling of these kind of cases.
The new configuration flag (inlineFragmentTypes: inline
) is now used by default, and generates more accurate types, without introducing breaking changes!
Instead of combining the fragment types they are merged and inlined.
export type FeedQuery = { __typename?: 'Query' } & {
- currentUser?: Maybe<{ __typename?: 'User' } & Pick<User, 'login'>>;
- feed?: Maybe<Array<Maybe<{ __typename?: 'Entry' } & FeedEntryFragment>>>;
+ currentUser?: Maybe<{ __typename?: 'User'; login: string }>;
+ feed?: Maybe<
+ Array<
+ Maybe<{
+ __typename?: 'Entry';
+ id: number;
+ commentCount: number;
+ score: number;
+ createdAt: number;
+ repository: {
+ __typename?: 'Repository';
+ full_name: string;
+ html_url: string;
+ description?: Maybe<string>;
+ stargazers_count: number;
+ open_issues_count?: Maybe<number>;
+ owner?: Maybe<{ __typename?: 'User'; avatar_url: string }>;
+ };
+ vote: { __typename?: 'Vote'; vote_value: number };
+ postedBy: { __typename?: 'User'; html_url: string; login: string };
+ }>
+ >
+ >;
};
No worries, fragment types are still exported separately!
All your types are still fully compatible with
v1
of the tool. You can setinlineFragmentTypes: combine
if you still prefer to keep the old behavior.
We also used the need for a major version in order to remove a few deprecations from the codebase.
typescript-resolvers
In typescript-resolvers
, we had a generated signature for IResolvers
and IDirectiveResolvers
(which were deprecated 2 years ago), v2
of this plugin is removing these proxy types.
Also, the noSchemaStitching
flag is now set to true
by default, so the generated resolvers signature is simpler and matches the needs of most projects.
If you are using Schema Stitching, you can set
noSchemaStitching: false
to keep the old behavior.
typescript-compatibility
The typescript-compatibility
plugin was created to make it easier to migrate from v0 to v1 of codegen, and hasn't been actively developed for a few years.
With this release, we no longer support or maintain the typescript-compatibility
plugin. If you are still using it, please consider migrating to the new type format.
Some time ago we started advocating the near-operation-file
preset, for generating GraphQL client code next to the .graphql
operation file.
A month ago, Maël Nison
reached out to us with a new concept for matching your actual GraphQL operation string and the generated TypeScript types. Without having to add clumsy import statements for each GraphQL operation.
It allowed writing the operations in the following way:
import { gql } from './gql'
const { GetTweets, CreateTweet } = gql(`#graphql
query GetTweets {
Tweets {
id
}
}
mutation CreateTweet {
CreateTweet(body: "Hello") {
id
}
}
`)
The README on his repository contained a list of limitations. We took that that feedback and fixed some upstream bugs within graphql-tools and improved graphql-codegen according to that.
With a few adjustments, n1ru4l
managed to turn that amazing idea into a plugin and a preset that extends the behavior of typescript-operation
and TypedDocumentNode
, and allow you to generate TypeScript types that magically matches your GraphQL query strings (this also required a few fixes in graphql-tools
, so now the Loaders in graphql-tools are better!).
Today, GraphQL Codegen scans your codebase and looks for all the operations that are being used by your components (or, from .graphql
files), then it generates types and wraps TypedDocumentNode
for you. Those are either generated to a single file, from which you import from anywhere within the app or next to the .graphql
when using the near-operation-file-preset
.
Folder structure (near-operation-file-preset
)
Project
- src
- UserQuery.graphql
- UserQuer.graphql.tsx
With this new plugin+preset, you can generate typings for your inline gql
function usages, without having to manually specify import statements for the documents.
All you need to do is import your gql
function and run codegen in watch mode.
import { gql, FragmentType } from '@app/gql'
// TweetFragment is a fully typed document node
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
const TweetsQueryWithFragment = gql(/* GraphQL */ `
query TweetsWithFragmentQuery {
Tweets {
id
...TweetFragment
}
}
`)
const TweetComponent = (props: {
tweet: FragmentType<typeof TweetFragment>
}) => {
return <Tweet>{props.tweet.body}</Tweet>
}
That way we don't need to switch context between .graphql
and our .ts(x)
files and have less files within our repository (compared to the near-operation-file
preset)!
You can find the complete documentation, examples and API reference in codegen website.
We want to hear your thoughts on this way of using Codegen on the frontend.
Based on your feedback we might wanna make this the recommended way of using Codegen on the frontend in the future.
Huge thanks to Maël Nison, who conceptualized the foundation for this preset over here. Please keep pushing the boundaries!
Like we said at the beginning, we continuously keep improving Codegen, so this release is not the end but just a start. Here are some sneak peeks on things we are currently working on:
ESM support is coming soon (and will probably result in another major version)
We are experimenting with a better way to link typeDefs
with the resolvers signature generated by typescript-resolvers
in a similar way to the new gql-tag-operations
plugin.
One of the goal of this preset is to make sure that every resolver that should be defined and implemented is actually implemented.
import { typeDefs } from 'tsgql'
const sdl = typeDefs(/* GraphQL */ `
type Query {
foo: String!
}
`)
export const resolvers: typeof sdl = {
Query: {
// If this resolver would not be defined it will raise a TypeScript error.
foo: () => 'test'
}
}
Today the Relay framework development flow allows hiding (masking) properties from operation results objects that are added via fragments. Those properties are only accessible from within the component that consumes the fragment:
import { gql, FragmentProperty } from '@app/gql'
// TweetFragment is a fully typed document node
const TweetFragment = gql(/* GraphQL */ `
fragment TweetFragment on Tweet {
id
body
}
`)
const TweetsQueryWithFragment = gql(/* GraphQL */ `
query TweetsWithFragmentQuery {
Tweets {
id
...TweetFragment
}
}
`)
const AppComponent = () => {
const { data } = useQuery({ query: TweetsQueryWithFragment })
// accessing this here is a TypeScript error.
data.Tweets[0].body
return (
<>
{data.Tweets.map((tweet) => (
<TweetComponent key={tweet.id} tweet={tweet} />
))}
</>
)
}
const TweetComponent = (props: {
tweet: FragmentProperty<typeof TweetFragment>
}) => {
// accessing props.tweet.body here is legit.
return <Tweet>{props.tweet.body}</Tweet>
}
This allows building scalable components as each component only receives the data it should have access to and furthermore components higher up in the tree can not access data it did not explicitly request. Removing a component will thus not result in surprising behaviors.
Do you think something else is missing or could be improved? Reach out to us via the chat on this page, a GitHub discussion or on the GraphQL Discord.
Want to hear from us when there's something new? Sign up and stay up to date!