06

The compiler does the work before your app runs

relay-compiler reads every graphql tag in the project, validates it against schema.graphql, and writes an artifact per fragment and operation: a precomputed runtime AST, TypeScript types, and (here) a persisted query id. Pick any artifact this app generated.
Kind
fragment
Artifact size
5.4 KB
Operation text
—
What the browser sends
—

1 · You write

src/components/pagination/PaginationDemo.tsx
fragment PaginationList_query on Query
@argumentDefinitions(count: { type: "Int", defaultValue: 3 }, cursor: { type: "String" })
@refetchable(queryName: "PaginationListRefetchQuery") {
  feed(first: $count, after: $cursor) @connection(key: "PaginationList_feed") {
    totalCount
    edges {
      cursor
      node {
        id
        title
        likeCount
        author {
          name
          avatarColor
        }
      }
    }
  }
}

2 · Types you import

PaginationList_query.graphql.ts
export type PaginationList_query$data = {
  readonly feed: {
    readonly edges: ReadonlyArray<{
      readonly cursor: string;
      readonly node: {
        readonly author: {
          readonly avatarColor: string;
          readonly name: string;
        };
        readonly id: string;
        readonly likeCount: number;
        readonly title: string;
      } | null | undefined;
    } | null | undefined> | null | undefined;
    readonly totalCount: number;
  };
  readonly " $fragmentType": "PaginationList_query";
};
export type PaginationList_query$key = {
  readonly " $data"?: PaginationList_query$data;
  readonly " $fragmentSpreads": FragmentRefs<"PaginationList_query">;
};

3 · Runtime artifact

imported in place of the graphql`` tag

A preprocessed AST, so there's no GraphQL parsing in the browser. The reader part tells useFragment what to read; the normalization part tells the store how to write.

/**
 * @generated SignedSource<<7dcbf2387bd13218f519ff07b0052e77>>
 * @lightSyntaxTransform
 */

/* tslint:disable */
/* eslint-disable */
// @ts-nocheck

import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PaginationList_query$data = {
  readonly feed: {
    readonly edges: ReadonlyArray<{
      readonly cursor: string;
      readonly node: {
        readonly author: {
          readonly avatarColor: string;
          readonly name: string;
        };
        readonly id: string;
        readonly likeCount: number;
        readonly title: string;
      } | null | undefined;
    } | null | undefined> | null | undefined;
    readonly totalCount: number;
  };
  readonly " $fragmentType": "PaginationList_query";
};
export type PaginationList_query$key = {
  readonly " $data"?: PaginationList_query$data;
  readonly " $fragmentSpreads": FragmentRefs<"PaginationList_query">;
};

import PaginationListRefetchQuery_graphql from './PaginationListRefetchQuery.graphql';

const node: ReaderFragment = (function(){
var v0 = [
  "feed"
];
return {
  "argumentDefinitions": [
    {
      "defaultValue": 3,
      "kind": "LocalArgument",
      "name": "count"
    },
    {
      "defaultValue": null,
      "kind": "LocalArgument",
      "name": "cursor"
    }
  ],
  "kind": "Fragment",
  "metadata": {
    "connection": [
      {
        "count": "count",
        "cursor": "cursor",
        "direction": "forward",
        "path": (v0/*:: as any*/)
      }
    ],
    "refetch": {
      "connection": {
        "forward": {
          "count": "count",
          "cursor": "cursor"
        },
        "backward": null,
        "path": (v0/*:: as any*/)
      },
      "fragmentPathInResult": [],
      "operation": PaginationListRefetchQuery_graphql
    }
  },
  "name": "PaginationList_query",
  "selections": [
    {
      "alias": "feed",
      "args": null,
      "concreteType": "PostConnection",
      "kind": "LinkedField",
      "name": "__PaginationList_feed_connection",
      "plural": false,
      "selections": [
        {
          "alias": null,
          "args": null,
          "kind": "ScalarField",
          "name": "totalCount",
          "storageKey": null
        },
        {
          "alias": null,
          "args": null,
          "concreteType": "PostEdge",
          "kind": "LinkedField",
          "name": "edges",
          "plural": true,
          "selections": [
            {
              "alias": null,
              "args": null,
              "kind": "ScalarField",
              "name": "cursor",
              "storageKey": null
            },
            {
              "alias": null,
              "args": null,
              "concreteType": "Post",
              "kind": "LinkedField",
              "name": "node",
              "plural": false,
              "selections": [
                {
                  "alias": null,
                  "args": null,
                  "kind": "ScalarField",
                  "name": "id",
                  "storageKey": null
                },
                {
                  "alias": null,
                  "args": null,
                  "kind": "ScalarField",
                  "name": "title",
                  "storageKey": null
                },
                {
                  "alias": null,
                  "args": null,
                  "kind": "ScalarField",
                  "name": "likeCount",
                  "storageKey": null
                },
                {
                  "alias": null,
                  "args": null,
                  "concreteType": "User",
                  "kind": "LinkedField",
                  "name": "author",
                  "plural": false,
                  "selections": [
                    {
                      "alias": null,
                      "args": null,
                      "kind": "ScalarField",
                      "name": "name",
                      "storageKey": null
                    },
                    {
                      "alias": null,
                      "args": null,
                      "kind": "ScalarField",
                      "name": "avatarColor",
                      "storageKey": null
                    }
                  ],
                  "storageKey": null
                },
                {
                  "alias": null,
                  "args": null,
                  "kind": "ScalarField",
                  "name": "__typename",
                  "storageKey": null
                }
              ],
              "storageKey": null
            }
          ],
          "storageKey": null
        },
        {
          "alias": null,
          "args": null,
          "concreteType": "PageInfo",
          "kind": "LinkedField",
          "name": "pageInfo",
          "plural": false,
          "selections": [
            {
              "alias": null,
              "args": null,
              "kind": "ScalarField",
              "name": "endCursor",
              "storageKey": null
            },
            {
              "alias": null,
              "args": null,
              "kind": "ScalarField",
              "name": "hasNextPage",
              "storageKey": null
            }
          ],
          "storageKey": null
        }
      ],
      "storageKey": null
    }
  ],
  "type": "Query",
  "abstractKey": null
};
})();

(node as any).hash = "378ed8fa004ed0b884b1551a96cea8c1";

export default node;
  • why it mattersA typo in a field name fails the build, not the user's session. Every fragment is checked against the schema on every save.
  • why it mattersTypes come from the fragment rather than the whole schema, so a component's data type has exactly the fields it asked for.
  • why it mattersPersisted queries ship for free: smaller requests, cacheable GETs, and a server that can refuse any query it didn't compile.