facebook / facebook/relay

Improve Typescript DX

Open
#4,235 9 comments 9 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
19k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

The current implementation of the `graphql` function in Relay only provides weak typings for GraphQL queries and fragments:

```ts
export function graphql(strings: unknown): GraphQLTaggedNode;
```

This can make it harder to infer the types of queries and fragments. For example, functions like `usePaginationFragment` can't infer the type from the query, and it's not possible to use `typeof` together with the query to connect props directly to a query.

Here is an example how latest client preset by ["the guild"](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client) allows to use typings based on the query:

```tsx
import { FragmentType, gql, unmaskFragment } from "../lib/graphql";

const Brand_ShopProduct = gql(`
fragment Brand_ShopProduct on ShopProduct {
brandName
brandId
}
`);

type BrandProps = {
brand: FragmentType;
};

export const Brand = ({ brand: brandFragment }: BrandProps) => {
const brand = unmaskFragment(Brand_ShopProduct, brandFragment);
return (
<>

BrandId: {brand.brandId}

{brand.brandName &&
Brand: {brand.brandName}
}

);
};
```

The code which the generator by the-guild creates looks like this: ⬇️

```tsx
import * as types from "./graphql";
import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";

/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel-plugin for production.
*/
const documents = {
"\n fragment Brand_ShopProduct on ShopProduct {\n brandName\n brandId\n }\n":
types.Brand_ShopProductFragmentDoc,
};

/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = gql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function gql(source: string): unknown;

/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
source: "\n fragment Brand_ShopProduct on ShopProduct {\n brandName\n brandId\n }\n"
): (typeof documents)["\n fragment Brand_ShopProduct on ShopProduct {\n brandName\n brandId\n }\n"];

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

export type DocumentType> =
TDocumentNode extends DocumentNode ? TType : never;
```

As you can see this approach allows you to have type safe queries without any specific query types imports.
It also allows to use type helpers like `FragmentType` (be it from the library or from the user land).

Would you be open to extend the relay compiler?
Maybe such a feature could even be implemented as plugin

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.