Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
.vscode
bin
example
flow-typed
src
.babelrc
.flowconfig
.gitignore
README.md
package.json
yarn.lock

README.md

scala-relay-compiler

The purpose of this project is to generate Scala.js bindings for the relay-compiler. Typically the relay-compiler generates flow bindings along with the compiled queries. This project replaces that generation and outputs js.native traits instead. It is experimental and the code needs work, but it can generated traits for deep hierarchical graphs.

It uses flow because it's what relay uses, and its better than raw javascript.

It uses outputDir to generate all the source files and whatnot in the same package. So its a big flat package repository in the same directory. Typically (resourceManaged in Compile).value / "relay-compiler-out" is where it outputs in sbt parlance.

Versions

  • 0.11.0 - Relay 1.6.2
  • 0.9.0 - Relay 1.5.0
  • 0.8.2 - Relay 1.4.0

Example

$ ./bin/scala-relay-compiler.js --src example/src/ --schema example/schema.graphql --out example/out/

Features

  • Handles names elegantly by scoping them to the companion object.
  • Handles first layer of spreading, right now we spit out js.| to join disjoint fields, even though in fact they are not disjoint, they are a union, however, this requires a fix later down the line.
  • @scalajs(extends: String) This can give you a parent class to mixin. It's your job to verify it.
  • @scalajs(useNulls: Boolean) this can give you finer control on using A | Null on a Fragment, field or inline fragment.

Example

Ill walk you through a simple example. I'll assume you know roughly how Relay Modern works.

We'll obviously need a schema, for which we'll use the example provided.

schema {
  query: Root
}

type Root {
  dictionary: [Word]
}

type Word {
  id: String!
  definition: WordDefinition
}

type WordDefinition {
  id: String
  text: String
  image: String
}

From this we'll want to generate some queries in which Ill list one top level query including two fragments. Now this is where we start to diverge from the stock relay-compiler. Our method of collecting queries/fragments/mutations is a regex (I know, ugly) through *.scala files. We look for the @gql(""" indicator and its corresponding """) to isolate the queries.

Here is an example taken straight from the repository.

object Foo {

  val otherGql = @gql("""
  fragment DictionaryComponent_definition on WordDefinition {
    text
    image
  }
  """)

  val gql = @gql("""
  fragment DictionaryComponent_word on Word {
    id
    definition {
      ...DictionaryComponent_definition
      text
      id
    }
  }
  """)

  val query = @gql("""
  query DictionaryQuery {
    dictionary {
      ...DictionaryComponent_word
    }
  }
  """)
}

So now we have the schema, and a query + two fragments. Time to generate! So we run the following command.

$ ./bin/scala-relay-compiler.js --src example/src/ --schema example/schema.graphql --out example/out/

And get three files. Queries and Mutations or top level components all have the same structure. The object represents the top level query, and the trait represents the data coming over the wire.

This basically replaces the js files that typically get generated by the compiler. Specifically the part the runtime needs is in DictionaryQuery.query

So the generation below handles DictionaryQuery. I cut some text for brevity

trait DictionaryQuery extends js.Object {
  /** Combined the fields on a spread: DictionaryComponent_word */
   val dictionary : js.Array[DictionaryComponent_word]
}


object DictionaryQuery extends _root_.relay.graphql.GenericGraphQLTaggedNode {
  val query: _root_.relay.graphql.ConcreteBatch = _root_.scala.scalajs.js.JSON.parse("""{
  "fragment": {
    ....
    "text": "query DictionaryQuery {\n  dictionary {\n    ...DictionaryComponent_word\n  }\n}\n\nfragment DictionaryComponent_word on Word {\n  id\n  definition {\n    ...DictionaryComponent_definition\n    text\n    id\n  }\n}\n\nfragment DictionaryComponent_definition on WordDefinition {\n  text\n  image\n}\n"
}""").asInstanceOf[_root_.relay.graphql.ConcreteBatch]

What does fragments look like you ask? You'll notice some comments, its basically a way to look up what happened in the code to make sure the generator didn't do something wonky.

As you can see the top level definition lives at the root package relay.generated And all the sub traits get generated underneath the trait's companion object.

package relay.generated

trait DictionaryComponent_word extends js.Object {
  /** New fields added, conflicts detected. */
   val definition : DictionaryComponent_word.Definition
   val id : String
}

object DictionaryComponent_word extends _root_.relay.graphql.GenericGraphQLTaggedNode {

  trait Definition extends js.Object {
    val id : String
    /** getDirectMembersForFrag child of DictionaryComponent_definition Combining fields, with or? "true"  */
    val text : String
    /** getDirectMembersForFrag child of DictionaryComponent_definition */
    val image : String
  }

  val query: _root_.relay.graphql.ConcreteFragment = _root_.scala.scalajs.js.JSON.parse("""{
    ....
    """)
}

Ill skip the other definition because there's nothing additionally interesting.

So that's it.

You can use from sbt by using, and it will include the tiny shim of javascript traits to use.

addSbtPlugin("com.dispalt.relay" % "sbt-relay-compiler" % "<version>")

TODO

A list of tasks/ideas the community could help with. High | Med | Low refers to the complexity/difficulty.

  • Med: Fix InlineFragments so they work properly. Right now we just really don't handle them.
  • Big: Fix spreading so it goes recursively? Right now spreading is difficult because the types that are mixed in couple potentially conflict
    • Med: Make it work recursively
    • Med: Generate the necessary class depth.
    • Big: How to handle traversing since the order of the frags are unable to to be changed.
  • Med: Handle connections and edges with a superclass.
  • High: Handle exotic features of graphql.
    • Low: Handle conditionals.
    • Low: Figure out what is not working.
  • High: Does recursion work?
  • Low: handle indents in generated code better
  • [ ]