scala-relay-compiler
v0.12.4
Published
A compiler tool for building GraphQL-driven Scala.js applications.
Downloads
120
Readme
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
- Relay1.6.2
0.9.0
- Relay1.5.0
0.8.2
- Relay1.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 usingA | 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.
- [x] 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
- [ ]