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
query
Artifact size
5.0 KB
Operation text
391 B
What the browser sends
45 B + vars

1 · You write

src/components/advanced/DeferDemo.tsx
query DeferDemoOnQuery($id: ID!) {
  post(id: $id) {
    ...DeferDemo_post
    ...DeferDemoInsights_post @defer(label: "insights")
  }
}

2 · Types you import

DeferDemoOnQuery.graphql.ts
export type DeferDemoOnQuery$variables = {
  id: string;
};
export type DeferDemoOnQuery$data = {
  readonly post: {
    readonly " $fragmentSpreads": FragmentRefs<"DeferDemoInsights_post" | "DeferDemo_post">;
  } | null | undefined;
};
export type DeferDemoOnQuery = {
  response: DeferDemoOnQuery$data;
  variables: DeferDemoOnQuery$variables;
};

3 · Operation text, persisted at build time

md5 4db99989312c32dbc8590e193a9f136c

Fragments from every component are inlined into one document. It lives in persisted_queries.json on the server; the client only knows the hash.

query DeferDemoOnQuery(
  $id: ID!
) {
  post(id: $id) {
    ...DeferDemo_post
    ...DeferDemoInsights_post @defer(label: "DeferDemoOnQuery$defer$insights")
    id
  }
}

fragment DeferDemoInsights_post on Post {
  insights {
    readMinutes
    wordCount
    sentiment
    related
  }
}

fragment DeferDemo_post on Post {
  title
  excerpt
  author {
    name
    avatarColor
    id
  }
}

4 · 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<<18d9f6b237b2053265da8aae40dcdbbb>>
 * @relayHash 4db99989312c32dbc8590e193a9f136c
 * @lightSyntaxTransform
 */

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

// @relayRequestID 4db99989312c32dbc8590e193a9f136c

import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type DeferDemoOnQuery$variables = {
  id: string;
};
export type DeferDemoOnQuery$data = {
  readonly post: {
    readonly " $fragmentSpreads": FragmentRefs<"DeferDemoInsights_post" | "DeferDemo_post">;
  } | null | undefined;
};
export type DeferDemoOnQuery = {
  response: DeferDemoOnQuery$data;
  variables: DeferDemoOnQuery$variables;
};

const node: ConcreteRequest = (function(){
var v0 = [
  {
    "defaultValue": null,
    "kind": "LocalArgument",
    "name": "id"
  }
],
v1 = [
  {
    "kind": "Variable",
    "name": "id",
    "variableName": "id"
  }
],
v2 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "id",
  "storageKey": null
};
return {
  "fragment": {
    "argumentDefinitions": (v0/*:: as any*/),
    "kind": "Fragment",
    "metadata": null,
    "name": "DeferDemoOnQuery",
    "selections": [
      {
        "alias": null,
        "args": (v1/*:: as any*/),
        "concreteType": "Post",
        "kind": "LinkedField",
        "name": "post",
        "plural": false,
        "selections": [
          {
            "args": null,
            "kind": "FragmentSpread",
            "name": "DeferDemo_post"
          },
          {
            "kind": "Defer",
            "selections": [
              {
                "args": null,
                "kind": "FragmentSpread",
                "name": "DeferDemoInsights_post"
              }
            ]
          }
        ],
        "storageKey": null
      }
    ],
    "type": "Query",
    "abstractKey": null
  },
  "kind": "Request",
  "operation": {
    "argumentDefinitions": (v0/*:: as any*/),
    "kind": "Operation",
    "name": "DeferDemoOnQuery",
    "selections": [
      {
        "alias": null,
        "args": (v1/*:: as any*/),
        "concreteType": "Post",
        "kind": "LinkedField",
        "name": "post",
        "plural": false,
        "selections": [
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "title",
            "storageKey": null
          },
          {
            "alias": null,
            "args": null,
            "kind": "ScalarField",
            "name": "excerpt",
            "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
              },
              (v2/*:: as any*/)
            ],
            "storageKey": null
          },
          {
            "if": null,
            "kind": "Defer",
            "label": "DeferDemoOnQuery$defer$insights",
            "selections": [
              {
                "alias": null,
                "args": null,
                "concreteType": "PostInsights",
                "kind": "LinkedField",
                "name": "insights",
                "plural": false,
                "selections": [
                  {
                    "alias": null,
                    "args": null,
                    "kind": "ScalarField",
                    "name": "readMinutes",
                    "storageKey": null
                  },
                  {
                    "alias": null,
                    "args": null,
                    "kind": "ScalarField",
                    "name": "wordCount",
                    "storageKey": null
                  },
                  {
                    "alias": null,
                    "args": null,
                    "kind": "ScalarField",
                    "name": "sentiment",
                    "storageKey": null
                  },
                  {
                    "alias": null,
                    "args": null,
                    "kind": "ScalarField",
                    "name": "related",
                    "storageKey": null
                  }
                ],
                "storageKey": null
              }
            ]
          },
          (v2/*:: as any*/)
        ],
        "storageKey": null
      }
    ]
  },
  "params": {
    "id": "4db99989312c32dbc8590e193a9f136c",
    "metadata": {},
    "name": "DeferDemoOnQuery",
    "operationKind": "query",
    "text": null
  }
};
})();

(node as any).hash = "73da92d4f796dc97a268bbd5e2ed01c6";

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.