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

Using Relay without Babel #828

Closed
domkm opened this issue Feb 12, 2016 · 19 comments
Closed

Using Relay without Babel #828

domkm opened this issue Feb 12, 2016 · 19 comments

Comments

@domkm
Copy link

domkm commented Feb 12, 2016

It looks like Relay currently requires Babel to compile Relay.QL strings. This is unfortunate because it makes Relay unusable from compile-to-JS languages like ClojureScript (where React is the dominant rendering library) and TypeScript.

I searched for issues about decoupling Relay from Babel but didn't find any. What is the status of this? Is Relay going to be permanently tied to Babel or is this temporary? It would be great if there were a self-contained Relay package that could run entirely client-side. Perhaps this may not be possible or be too slow to be feasible, in which case the compilation logic could be packaged separately and thereby made accessible to build tools other than Babel.

@chandu0101
Copy link
Contributor

👍 . I was also in same situation few months back , for now i am using macros to fire up relay-babel-plugin and then passing generated string to js.eval(..) in my scala.js project. RelayQL Macro.

I'll love to see no babel dependency in future !.. :)

@josephsavona
Copy link
Contributor

Great questions! And thanks, @chandu0101, for following up with your approach.

It would be great if there were a self-contained Relay package that could run entirely client-side.

This gets at why the plugin is necessary in the first place. Relay needs information from the GraphQL schema in order to interpret queries correctly. Examples include knowing the types of all fragments and fields, and being able to distinguish whether friends(first: 10) should return a list or connection. In practice, the schema can be far too large to load in full on the client (encoding bits of the schema in each query also ensures that performance is proportional to the part of the schema you actually use).

That said, we've discussed the idea of a schema-less Relay in which the queries themselves were simple objects, and any schema-level metadata was encoded in a composing layer (technically the queries are simple objects, but there's enough metadata that they aren't fun to write by hand). If that sounds vague, it is. tl;dr we're open to this idea but for the foreseeable future we're likely to continue using the plugin.

I searched for issues about decoupling Relay from Babel but didn't find any. What is the status of this? Is Relay going to be permanently tied to Babel or is this temporary?

We don't currently use Relay with any compile-to-JS languages internally, so the core team is not likely to work on this directly. However, we are open to making changes to support using Relay in these languages. The best bet might be to submit a PR that changes the plugin to work with your language so we can discuss something concrete.

If you're interested in working on this, note that we've tried to structure the plugin in a way that most of the babel-specific code is in getBabelRelayPlugin. The code to compile source string + schema into output code is in RelayQLTransformer. This file accepts a babel object as an input and uses it only for constructing JS AST nodes (we used to use string concatenation - not recommended). You might try exporting RelayQLTransformer and modifying it to work with your language - let's go from there.

@schweller
Copy link

@domkm have you seen that https://github.com/apollostack/relay-runtime-query ?
It's more like a proof of concept and runs only in dev mode, as far as I know.

Hope it helps.

@stubailo
Copy link

Currently working on improving that! My goal is to have exactly the thing requested above - a Relay client that can connect to any server at runtime, and still does query validation, etc. I think I'm pretty close, just need to improve things so that all of the Relay tests pass.

@stubailo
Copy link

Darn, looks like this commit in Relay 0.7.1 changed the API between the plugin and runtime: 6a1fdc9

Looks like there is an expectation that the version of the Babel plugin will always exactly match the Relay runtime version, so it could be really hard to make sure the tool I built above will work with new versions without some cooperation with the Relay team, or a more stable way to generate a working query AST.

@josephsavona
Copy link
Contributor

Looks like there is an expectation that the version of the Babel plugin will always exactly match the Relay runtime version

@stubailo Yes - as we iterate on Relay we often need to adjust the plugin to provide more/less/different information about the queries. The commit you linked is a perfect example, where we needed a stable identifier for a fragment at build time. We expect to continue iterating on the plugin for the foreseeable future, so the best bet here is to reuse parts of the plugin.

without some cooperation with the Relay team

We're happy to support changes that would allow reuse of the portion of the plugin that transforms source GraphQL to JS code outside of the babel plugin. How about sending a pull request that demonstrates what changes you'd need in babel-relay-plugin (ie. what you'd have to export as public API) in order to reuse that transform step outside of babel itself?

@stubailo
Copy link

Sounds great - I'll try to get together a PR soon, I don't think it will be a big problem because the Babel AST very closely matches the actual data structure (to the point where I was able to mock Babel in the package above to actually get the right data).

In the meanwhile, I've updated my NPM package to work with Relay 0.7.1!

@domkm
Copy link
Author

domkm commented Mar 13, 2016

Sorry for dropping out. I've been using Relay via Babel and Webpack for the past few weeks since I needed to be immediately productive and I wanted to understand Relay better. However, I am still very interested in making Relay work with ClojureScript.

After working with Relay and understanding it better, I am no longer interested in a client-side only version. Instead, I think it would be fairly easy to make Relay work with ClojureScript during the build process in a similar way to how it works with JavaScript and Babel. I think the only thing I need is a portable JavaScript function that, given a Relay.QL query string and a Relay schema object (or schema JSON string), returns the parsed/annotated/compiled/whatever query that should replace the Relay.QL query string. By "portable," I mean a function that does not depend on Babel, Webpack, or Node APIs; just plain ECMAScript. I could use this function to compile Relay.QL query strings using Java's Nashorn runtime during ClojureScript compilation. How does this sound? Could we provide a portable Relay.QL query string compiler? It would be a tremendous help for everyone who wants to use Relay in compile-to-JavaScript languages.

@josephsavona
Copy link
Contributor

I think the only thing I need is a portable JavaScript function that, given a Relay.QL query string and a Relay schema object (or schema JSON string), returns the parsed/annotated/compiled/whatever query that should replace the Relay.QL query string. By "portable," I mean a function that does not depend on Babel, Webpack, or Node APIs; just plain ECMAScript.

Yup, this is the direction that we recommend and are able to support (see my comments above). The only caveat is that the plugin replaces Relay.QL...`` templates with JavaScript code, not a modified query. We currently use babel helpers to construct an AST of the generated code - but these helpers are actually passed as arguments to the transformer, such that you could easily stub them out if you don't want to require babel within e.g. Nashorn. We would prefer to keep this approach (as compared with string concatenation for constructing the generated code) because it greatly increases our iteration speed on the plugin.

At this point I believe we have consensus on the high-level approach; feel free to submit a PR and we can discuss specifics there.

@domkm
Copy link
Author

domkm commented Mar 14, 2016

Thanks, @josephsavona. After looking through the code and discussing this with @stubailo in Slack, it seems quite a bit more complicated than I hoped, in large part due to my lack of familiarity with Babel, Flow, and the rest of the modern JS ecosystem. I'll try to get to it eventually, though I hope that someone who is familiar with this and can knock it out it a couple hours will do so. For what it's worth, this seems like a crucial step in growing Relay within the larger community (ClojureScript, TypeScript, etc.), and, if someone does refactor this to support a portable compiler function, I will commit to packaging it for use by the ClojureScript community.

@domkm domkm mentioned this issue Mar 17, 2016
@eyston
Copy link

eyston commented Mar 18, 2016

:edit: oops I am dumb @chandu0101 posted this approach as the first comment ;p

I messed with this a month or so ago (just saw this issue). The thing I was doing, and no idea if it was a good idea or not, was shelling to node.js from clojure. So you might have something like:

(relay "query { viewer { name } }")

This would call the babel transformer directly with the Relay plugin configured, get the AST, and then execute it (it turns an AST of a function call so you have to eval it), and return a JSON string of the AST. This json string could then be parsed by clojure.

I am clojurescript dumb so I wasn't sure the best way to do this so it happens in clojure vs clojurescript. I think a macro would happen in clojure and clojurescript could be left with just a js object?

;; this in code ...
(def a-query (relay "query { viewer { name } }"))

;; ... produces this in clojurescript?
(def a-query #js { ... AST ... })

I also tried a reader literal:

(def a-query #relay/ql "query { viewer { name } }")

The downside to this whole approach was that I was shelling to node.js per query ... so that is slow. Ideally nashorn could do this but I read it can't load babel so didn't even try. I'm sure you could do it so that it fires node.js once and then just streams queries in / out ... but I am not that smart :).

Knowing that the babel stuff is passed as parameters to the Printer sounds rad. I'd like to give that a try so I wouldn't need babel and could do this all in nashorn.

@eyston
Copy link

eyston commented Mar 18, 2016

Here was the script:

var babel = require('babel-core');
var language = require('graphql/language');
var utilities = require ('graphql/utilities');
var getBabelRelayPlugin = require('babel-relay-plugin');
var graphql = require('graphql');

const ast = language.parse('type Viewer { id: ID! name: String } type Root { viewer: Viewer }');

const schema = utilities.buildASTSchema(ast, 'Root');

graphql.graphql(schema, utilities.introspectionQuery).then(function(data) {
  var plugin = getBabelRelayPlugin(data.data);

  try {
    var query = process.argv[2];
    console.log(JSON.stringify(eval(babel.transform('Relay.QL`' + query + '`', {plugins: [plugin]}).code)));
  } catch (e) {
    console.log(JSON.stringify({error: e.message}));
  }
});

So ... extremely low tech ;p It would be rad if that could just run in nashorn but babel stops it?

@domkm
Copy link
Author

domkm commented Mar 18, 2016

@eyston That's very similar to what I am currently doing.

(ns project.relay
  (:require
   [clojure.java.io :as io]
   [me.raynes.conch :as conch]))

(defn ^:private ql* [env query]
  (let [rql (str "Relay.QL`" query "`")
        schema (-> "relay/schema.json" io/resource slurp)
        filename (-> env :ns :name)
        script (str "var schema = " schema ";"
                    "var schemaData = schema.data;"
                    "var getBabelRelayPlugin = require('babel-relay-plugin');"
                    "var babel = require('babel-core');"
                    "var plugins = [getBabelRelayPlugin(schemaData)];"
                    "var filename = '" filename "';"
                    "var options = {plugins, filename, comments: false, ast: false};"
                    "var code = '" rql "';"
                    "babel.transform(code, options).code;")]
    (conch/execute "node" "--print" script)))

(defmacro ql [^String query]
  (list 'js* (ql* &env query)))

I opened #962 specifically because I'd like to switch from shelling out to Node to directly compiling in Nashorn. The only thing stopping us JVM users (CC @chandu0101) from using Relay with Nashorn is that babel-relay-plugin and graphql-js currently lack UMD builds.

@eyston
Copy link

eyston commented Mar 18, 2016

awesome, thanks! I like that you can just inline the code directly versus having to eval. :)

@wincent
Copy link
Contributor

wincent commented Jan 30, 2017

(Spring-cleaning). Going to close this one due to inactivity. Summarizing the thread: a couple of workarounds have been shared, and we're open to PRs. Note also that Relay has a new core now (pieces of which have been landing on the master branch for some time now, with more to come), so if we want to continue discussion of this further we should probably do so in a new issue, and based the discussion on the current reality (and imminent future) of the master branch. In them meantime, thanks for the discussion on this issue!

@wincent wincent closed this as completed Jan 30, 2017
@guncha
Copy link

guncha commented Apr 23, 2017

If anyone lands here looking for a relay plugin for Typescript, I just published a package that converts between the output generated by babel-relay-plugin and Typescript AST. We've been using it in production for six months now without issues though compatibility with different plugin and Typescript versions could be suspect.

https://github.com/pathgather/typescript-relay-plugin

@jsphstls
Copy link

babel-plugin-relay is the only part of my typescript/monorepo setup that is not working.

I described the setup in this issue at babel-plugin-macros.

@tony
Copy link

tony commented Jan 13, 2021

@jsphstls If you (or anyone else) happen upon this thread, I created a fresh one at #3319

I ask about "next generation" JS bundlers and generally where relay fits between compilers.

At the moment - I'm a little fuzzy on what relay ingests, outputs, and if it's possible for us to make it work simply with plain-old tsc and these newer, opinionated "plugin-free" bundlers.

The reason why is, like many, build setups have been a copy-paste thing for me. What node, webpack, babel, tsc, and relay do are blurred. I've been focused on building out product for users, and not looking at what's under the hood.

I can't tell how they depend on one another. It's a blur to me.

Am I the only one?

I imagine it should be getting smoother and smoother! My understanding is node and webpack can hande more recent ES (as well as browsers in general), but still, TypeScript and relay are their own kind of thing.

@nicostombros
Copy link

For anyone using Vite and looking to get relay-compiler running without babel, I use the following setup:

  1. Installing vite-plugin-relay-lite https://github.com/cometkim/vite-plugin-relay-lite as a dev dependency
  2. Updating my vite.config.ts to look like
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import relay from 'vite-plugin-relay-lite';
import path from 'path';

const relayConfig = require('./relay.config.json')  // or you can pass this config location as an arg for relay-compiler

export default defineConfig({
  plugins: [react(), relay(relayConfig)],
})
  1. Having a relay.config.json file (I found that having a js module export would conflict with my setup) that looks like:
{
  "src": "./src",
  "language": "typescript",
  "schema": "./path/to/your/schema.graphql",
  "artifactDirectory": "./src/__generated__",
  "eagerEsModules": true,
  "excludes": ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"]
}

And that seemed to work for me.

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