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
3.5 KB
Operation text
189 B
What the browser sends
45 B + vars

1 · You write

src/components/advanced/StreamDemo.tsx
query StreamDemoOnQuery {
  activity @stream(initialCount: 1, label: "activity") {
    id
    message
    actor {
      name
      avatarColor
    }
  }
}

2 · Types you import

StreamDemoOnQuery.graphql.ts
export type StreamDemoOnQuery$variables = Record<PropertyKey, never>;
export type StreamDemoOnQuery$data = {
  readonly activity: ReadonlyArray<{
    readonly actor: {
      readonly avatarColor: string;
      readonly name: string;
    };
    readonly id: string;
    readonly message: string;
  }>;
};
export type StreamDemoOnQuery = {
  response: StreamDemoOnQuery$data;
  variables: StreamDemoOnQuery$variables;
};

3 · Operation text, persisted at build time

md5 b5dd6473cd00a2070e81f1a515341253

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 StreamDemoOnQuery {
  activity @stream(label: "StreamDemoOnQuery$stream$activity", initialCount: 1) {
    id
    message
    actor {
      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<<1723046ea8b34be65d632df9e96c4e55>>
 * @relayHash b5dd6473cd00a2070e81f1a515341253
 * @lightSyntaxTransform
 */

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

// @relayRequestID b5dd6473cd00a2070e81f1a515341253

import { ConcreteRequest } from 'relay-runtime';
export type StreamDemoOnQuery$variables = Record<PropertyKey, never>;
export type StreamDemoOnQuery$data = {
  readonly activity: ReadonlyArray<{
    readonly actor: {
      readonly avatarColor: string;
      readonly name: string;
    };
    readonly id: string;
    readonly message: string;
  }>;
};
export type StreamDemoOnQuery = {
  response: StreamDemoOnQuery$data;
  variables: StreamDemoOnQuery$variables;
};

const node: ConcreteRequest = (function(){
var v0 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "id",
  "storageKey": null
},
v1 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "message",
  "storageKey": null
},
v2 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "name",
  "storageKey": null
},
v3 = {
  "alias": null,
  "args": null,
  "kind": "ScalarField",
  "name": "avatarColor",
  "storageKey": null
};
return {
  "fragment": {
    "argumentDefinitions": [],
    "kind": "Fragment",
    "metadata": null,
    "name": "StreamDemoOnQuery",
    "selections": [
      {
        "kind": "Stream",
        "selections": [
          {
            "alias": null,
            "args": null,
            "concreteType": "Activity",
            "kind": "LinkedField",
            "name": "activity",
            "plural": true,
            "selections": [
              (v0/*:: as any*/),
              (v1/*:: as any*/),
              {
                "alias": null,
                "args": null,
                "concreteType": "User",
                "kind": "LinkedField",
                "name": "actor",
                "plural": false,
                "selections": [
                  (v2/*:: as any*/),
                  (v3/*:: as any*/)
                ],
                "storageKey": null
              }
            ],
            "storageKey": null
          }
        ]
      }
    ],
    "type": "Query",
    "abstractKey": null
  },
  "kind": "Request",
  "operation": {
    "argumentDefinitions": [],
    "kind": "Operation",
    "name": "StreamDemoOnQuery",
    "selections": [
      {
        "if": null,
        "kind": "Stream",
        "label": "StreamDemoOnQuery$stream$activity",
        "selections": [
          {
            "alias": null,
            "args": null,
            "concreteType": "Activity",
            "kind": "LinkedField",
            "name": "activity",
            "plural": true,
            "selections": [
              (v0/*:: as any*/),
              (v1/*:: as any*/),
              {
                "alias": null,
                "args": null,
                "concreteType": "User",
                "kind": "LinkedField",
                "name": "actor",
                "plural": false,
                "selections": [
                  (v2/*:: as any*/),
                  (v3/*:: as any*/),
                  (v0/*:: as any*/)
                ],
                "storageKey": null
              }
            ],
            "storageKey": null
          }
        ]
      }
    ]
  },
  "params": {
    "id": "b5dd6473cd00a2070e81f1a515341253",
    "metadata": {},
    "name": "StreamDemoOnQuery",
    "operationKind": "query",
    "text": null
  }
};
})();

(node as any).hash = "159d0df95e295e4c053b70a76dce6a5d";

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.