Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hack Idea: Runtime Relay Compiler #3548

Closed
captbaritone opened this issue Jul 21, 2021 · 6 comments
Closed

Hack Idea: Runtime Relay Compiler #3548

captbaritone opened this issue Jul 21, 2021 · 6 comments

Comments

@captbaritone
Copy link
Contributor

In this issue I'm proposing an in-browser version of the Relay compiler which could allow people to try out Relay (with some restrictions) without needing to setup Babel transform or compiler. I'm curious to hear from the community how valuable (or not) and option like this would be given the limitations.


Motivation: We believe that Relay’s main impediment to broader OSS adoption, and thus broader industry impact is the complexity of the initial setup. The largest part of this is the requirement that new users must get the compiler and Babel transform working for their code base before they even start to try Relay. If we could allow users to try Relay’s APIs first and only configure the compiler and Babel transform after/if they are convinced of its value, we could increase our industry impact.

Hypothesis If a user can abide by the following restrictions, we can support most of Relay’s existing APIs.

  1. All graphql tagged template literals must be in the module scope
  2. Schema must be available at runtime
  3. No TypeScript/Flow support
  4. No code is lazily loaded*

Summary: We could provide a new NPM module which exposes a custom graphql tagged template literal which collects module scoped GraphQL strings and — right before we begin rendering — transforms them to Relay runtime modules using a Wasm version of the compiler.

Proposed Implementation

We create a new NPM module which exposes a graphql tagged template and a compile(schema: string) function. The new module maintains a module scope registry (map object) from each GraphQL document string to that document’s runtime object. When the graphql tagged template is called it immediately creates an empty object (to be mutated later), adds it to the registry and returns that empty object.

After all modules have been loaded, but before we start to render — for example right before the initial render of the app — the user must call the new module’s compile(schema) function.

When the compile(schema) function is called, it collects up all the documents that have been seen so far and the schema string and calls a method on a Wasm version of the compiler. The compiler returns an array of strings (one for each document, in the same order), each string contains the JavaScript for that document’s runtime module. Those modules are eval()ed and the resulting module is merged into the empty object (Object.assign()) created by the call to the graphql tagged template.

The result is that the in-browser compiler would have access to all GraphQL documents and by the time any of the Relay runtime data is read by Relay, it would be available.

Rough Code for the New Module:

import * as wasm from 'relay-compiler-playground';

const DOCUMENT_MAP = {};

function graphql(args) {
  const text = args[0];
  const runtimeValue = {};
  DOCUMENT_MAP[text] = runtimeValue;
  return runtimeValue;
}

export function compile(schema) {
  const docs = [];
  const runtimes = [];
  for (const [doc, runtime] of Object.entries(DOCUMENT_MAP)) {
    docs.push(doc);
    runtimes.push(runtime);
  }
  const compiled = wasm.compile(schema, docs);
  compiled.forEach((runtime, i) => {
    Object.assign(runtimes[i], runtime);
  });
}

export default graphql;

A Component Using the New Module

import { useLazyLoadQuery } from 'react-relay/hooks';
import graphql from 'NEW_MODULE'; // Differet import
import Issues from './Issues';
import React from 'react';

// Only constraint: We define the query in module scope
const QUERY = graphql`
  query HomeRootIssuesQuery($owner: String!, $name: String!) {
    repository(owner: $owner, name: $name) {
      ...Issues_repository
    }
  }
`;

export default function HomeRoot() {
  const data = useLazyLoadQuery(QUERY, {
    owner: 'facebook',
    name: 'relay',
  });
  const { repository } = data;

  return <Issues repository={repository} />;
}

A Project Root Using the New Module

import React from 'react';
import ReactDOM from 'react-dom';
import { RelayEnvironmentProvider } from 'react-relay/hooks';
import RelayEnvironment from './RelayEnvironment';
import HomeRoot from './HomeRoot';

// These three lines can be removed once
// you upgrade to the real compiler
import { compile } from 'NEW_MODULE';
import SCHEMA from './schema';
compile(SCHEMA);

ReactDOM.createRoot(document.getElementById('root')).render(
  <RelayEnvironmentProvider environment={RelayEnvironment}>
    <React.Suspense fallback={'Loading...'}>
      <HomeRoot />
    </React.Suspense>
  </RelayEnvironmentProvider>,
);

Arguments In Favor

Features of this proposal:

  1. Can be implemented as a separate module and does not involve any changes to the Relay Runtime
  2. Upgrading to the real compiler — once you have the build-time stuff setup — is just a matter of changing how you import the graphql tagged template and removing the runtime call to compile(schema)

Arguments Against

Reasons we might not want to consider this proposal:

  1. Part of Relay’s value proposition is the entrypoints-like behavior where code and data are loaded in parallel. This is not possible to implement without lazy loading component code and thus would not work with this simple proposal. (That said, it may be possible to allow lazy loaded components by rerunning the compiler after each entrypoint is loaded)
  2. Relay features which create non-type imports/require calls in the runtime module would not work, and it may be difficult to communicate that limitation.
  3. Compiler errors would need to be presented in the console and could not point directly to file/line numbers
  4. Optimizations like lazy module evaluation would break this approach

Overall, the limitations of this setup might be too complex to communicate via documentation or runtime checks.

@captbaritone
Copy link
Contributor Author

@poteto made a great point internally, that this is basically a solution in search of a problem. It presupposes that build tooling setup is a meaningful impediment to adoption, which may or may not actually be true. Additionally, there are many other impediments (poor documentation generally for example) which this would not help address.

So one thing that I'd appreciate the communities feedback on is: Do you have any reason to believe/doubt that build tooling setup is a significant impediment to people trying out, and eventually adopting Relay?

@sibelius
Copy link
Contributor

devs forget or don't understand babel plugin or why to run relay compiler

@Daniel15
Copy link
Member

Daniel15 commented Jul 23, 2021

This sounds similar to what Relay Classic used to do!

which could allow people to try out Relay

Do you mean try it out within their own codebase, or just try it out in a standalone codebase to get a feel for it? The latter could more easily be done using something like CodeSandbox or repl.it, I think. For trying it out locally in a standalone codebase, I don't think it's hard to get working with create-react-app, just creating the server-side is the tricky bit (although people could use public GraphQL servers for testing purposes)

@captbaritone
Copy link
Contributor Author

The idea would be to allow people to try it out in their own app without needing to setup the compiler or Babel transform.

For trying out a demo app we have https://codesandbox.io/s/relay-sandbox-nxl7i?file=/src/TodoApp.tsx

Good point about the pain of spinning up a server being a big pain point. Are there any public GraphQL servers that have permissive CORS headers?

@leoasis
Copy link
Contributor

leoasis commented Jul 27, 2021

I really like the idea of being able to run Relay without needing to set up a compiler, though I think I agree with your later comment about thinking if this is impactful enough for adoption. I do think a compiler is a slight barrier of entry, but most other GraphQL clients require code generation already.

Maybe the issue is more around explaining/understanding more when the compiler is needed to be run, what it does, and how does it play with the runtime code. I like the idea that the new work with the IDE aims to do, where it runs the compiler for you automatically and devs don't have to even think about that, while still getting all the benefits.

Specific to the runtime compiler:

Compiler errors would need to be presented in the console and could not point directly to file/line numbers

Could they be surfaced with an error overlay, similar to how create-react-app does it?

@captbaritone
Copy link
Contributor Author

After discussion, I think this is probably not worth pursuing. Thanks everyone for the input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants