Relay: what does `ahead-of-time code generation` mean in `relay-modern`?

Created on 25 May 2017  路  5Comments  路  Source: facebook/relay

stack-overview is probably a better place to ask, but to get a quick response (and potentially could help more people who are struggling with the same question), I'll just raise it here.

the document says relay-modern provides

Relay Modern accomplishes this with static queries and ahead-of-time code generation.

which I don't quite understand.
my understanding of how classic relay works is that, during build (not run-time) babelRelayPlugin will transform/parse the graphql query that developers define in containers into some internal functions that could be later used to generated AST which relay utilizes. here is an example of a compiled container which will be loaded into browser.

    exports.default = _reactRelay2.default.createContainer(TodoApp, {
      fragments: {
        viewer: function viewer() {
          return function (RQL_0, RQL_1) {
            return {
              children: [].concat.apply([], [{
                fieldName: 'totalCount',
                kind: 'Field',
                metadata: {},
                type: 'Int'
              }, {
              .................................
    });

it seems that now, we split the creation of inline internal functions into 2 parts

  1. create artifacts and store into separate files
const fragment /*: ConcreteFragment*/ = {
  "argumentDefinitions": [],
  "kind": "Fragment",
  "metadata": null,
  "selections": [
    {
      "kind": "ScalarField",
      "alias": null,
      "args": null,
      "name": "complete",
      "storageKey": null
    },
    {
      "kind": "ScalarField",
      "alias": null,
      "args": null,

       ....

  "type": "Todo"
};

module.exports = fragment;
  1. during building time, babel-plugin-relay will require them and turns RelayContainers source into
exports.default = (0, _reactRelay.createFragmentContainer)(TodoList, {
      viewer: function viewer() {
        return __webpack_require__(495);  
      }
    });

which is effectively the same as before (just modulized, known as #495 module to webpack.

My questions is can I say that relay-compile saves build time, not run time?

@wincent

Most helpful comment

Great question! As you noted, Relay classic does a limited amount of build-time processing. In particular, in transforms Relay.QL tagged literals into JavaScript functions that, when executed, will return an object representation of the GraphQL text. Relay Modern also transforms graphql tagged template literals ahead of time, but there is a notable difference: Relay classic transforms each literal in isolation and allows runtime interpolation, whereas Relay Modern compiler does whole-program transformation and disallows interpolation.

Because Relay classic transforms each GraphQL snippet in isolation - and allows runtime injection of variables via ${arbitraryValue} ES6-template interpolation - it's more challenging to determine queries statically. Instead, Relay constructs queries at rutime: executing the generated functions, converting the resulting ASTs into RelayQuery objects, and printing those queries to a GraphQL string to send to the server (in addition to a variety of other "traversals" over the query structure).

In contrast, the Relay Modern compiler compiles all the GraphQL snippets for an application together. Runtime interpolation is also disallowed, so queries are fully static and can be determined at build time. This means that relay-compiler can not only combine the text of each query with the text of all of its included fragments, but also that it can generate an optimized artifact to use when processing those query results.

The end result is that at runtime Relay Modern does less AST construction/traversal, and the remaining traversals are over pre-optimized ASTs. For example, normalization - writing query results to the in-memory cache - is typically faster in Relay Modern because the queries have been flattened ahead of time. In internal benchmarks based on real-world use-cases we saw that normalization can be up to 10x faster than in Relay classic, thanks in part to these ahead-of-time optimizations (this doesn't mean apps will be 10x faster, just that one portion of Relay could be improved that much).

All 5 comments

i believe queries have always been static, that's why sometimes variables are overridden by parents ( pointed out by @josephsavona https://github.com/facebook/relay/issues/1138 )

Relay Modern accomplishes this with static queries and ahead-of-time code generation.

i don't think I understand what that means

exports.default = (0, _reactRelay.createFragmentContainer)(TodoList, {
      viewer: {


        modern: function modern() {
          return __webpack_require__(668);
        },


        classic: function classic() {
          var RelayQL_GENERATED = __webpack_require__(481).QL,
              Todo_todo = _Todo2.default.getFragment('todo'),
              Todo_viewer = _Todo2.default.getFragment('viewer');

          return {
            kind: 'FragmentDefinition',
            argumentDefinitions: [],
            node: function () {
              return {
                children: [].concat.apply([], [{
                  calls: [{
                    kind: 'Call',
                    metadata: {
                      type: 'Int'
                    },
                    name: 'first',
                    value: {
                      kind: 'CallValue',
                      callValue: 2147483647
                    }
                  }],
                  children: [{
                    children: [{
                      children: [].concat.apply([], [{

it seems that relay-compile is saving build time, not run time

Great question! As you noted, Relay classic does a limited amount of build-time processing. In particular, in transforms Relay.QL tagged literals into JavaScript functions that, when executed, will return an object representation of the GraphQL text. Relay Modern also transforms graphql tagged template literals ahead of time, but there is a notable difference: Relay classic transforms each literal in isolation and allows runtime interpolation, whereas Relay Modern compiler does whole-program transformation and disallows interpolation.

Because Relay classic transforms each GraphQL snippet in isolation - and allows runtime injection of variables via ${arbitraryValue} ES6-template interpolation - it's more challenging to determine queries statically. Instead, Relay constructs queries at rutime: executing the generated functions, converting the resulting ASTs into RelayQuery objects, and printing those queries to a GraphQL string to send to the server (in addition to a variety of other "traversals" over the query structure).

In contrast, the Relay Modern compiler compiles all the GraphQL snippets for an application together. Runtime interpolation is also disallowed, so queries are fully static and can be determined at build time. This means that relay-compiler can not only combine the text of each query with the text of all of its included fragments, but also that it can generate an optimized artifact to use when processing those query results.

The end result is that at runtime Relay Modern does less AST construction/traversal, and the remaining traversals are over pre-optimized ASTs. For example, normalization - writing query results to the in-memory cache - is typically faster in Relay Modern because the queries have been flattened ahead of time. In internal benchmarks based on real-world use-cases we saw that normalization can be up to 10x faster than in Relay classic, thanks in part to these ahead-of-time optimizations (this doesn't mean apps will be 10x faster, just that one portion of Relay could be improved that much).

I see...

so the dramatic change is ditching SetVariables API and only allow GraphQL variable syntax
so that the query itself is deterministic and hence

  1. can be made static
  2. allow global compilation at build time and optimization like flattening.

that makes sense to me. thanks for explanation!

Looks like this is resolved. Thanks for asking, @bochen2014.

Was this page helpful?
0 / 5 - 0 ratings