yoga2
yoga2 copied to clipboard
Re-exposing configurations through yoga
Description
Since yoga will mostly be a wrapper around several existing libraries, I believe we're left with two ways of re-exposing the configurations:
- Either we re-expose the configuration properties as-is, making it easier for everyone to find what config is doing what by simply going on the documentation of each underlying libraries
- Or we also wrap the configuration, trying to make them more explicit and/or more consistent with Yoga
Nexus as use-case
Option 1
If we simply re-exposed nexus configuration, we could have something like this:
export interface InputConfig {
/** Path to the directory where your resolvers are defined */
resolversPath?: string
/** Path to your context.ts file */
contextPath?: string
/** Object containing all the properties for the outputted files */
output?: {
/** Path to the directory where the GraphQL server will be compiled */
buildPath?: string
}
/** Nexus configuration */
nexus?: NexusConfiguration // `NexusConfiguration` is a placeholder here
}
Option 2
Versus some custom configuration:
export interface InputConfig {
/** Path to the directory where your resolvers are defined */
resolversPath?: string
/** Path to your context.ts file */
contextPath?: string
/** Object containing all the properties for the outputted files */
output?: {
/** Path to the generated schema */
schemaPath?: string
/** Path to the generated typings */
typegenPath?: string
/** Path to the directory where the GraphQL server will be compiled */
buildPath?: string
}
}
Above, output.schemaPath
and output.typegenPath
are properties needed for nexus.
Although it seems more consistent, it may feel confusing for people already using some of the underlying libraries.
On the other hand, re-exposing config properties as-is might very well make the configuration super clumsy and force people to understand each building block behind yoga.
What are your thoughts?
IMHO +1 for exposing library types. Nexus here is simple but something more complex can make things less clean. Good example of complex config: ApolloConfig that has many nested objects etc.
By the end of the day some libraries could be reexposed or unwrapped like nexus example above.
My preference would be to provide a custom config (option2). If we're aiming for a convention over configuration approach, ideally most settings are handled automatically and hidden from the user.
For options that do need to be configurable, I'd lean towards wrapping the underlying building blocks. In particular, this would really improve the DX if any common yoga-level config options require touching multiple underlying libraries.
Update
@briandennis: We're indeed aiming for conventions over configurations, although we still want/need to offer a way for users to "eject" whenever needed from the severals defaults we'll take for them, and not lock them in.
I'm going to add another option here, that we think is better than the two current ones
Motivation
Re-exposing all the underlying configurations while also offering the possibility to extend/change some of the underlying libraries (eg: use a different server than the one bundled). Think of this option as a convenient way to "eject" from the defaults
Option 3
Note: This is not yet an RFC but rather just an idea for now. If it ends up being chosen, we'll have to investigate the feasibility/complexity of it.
If needed, users could optionally provide a ./src/index.ts
file. This file would require the users to "reimplement" themselves some of the underlying blocks of yoga
.
yoga
would still provide the API to make it hook properly into its internals. While this sounds heavy, it actually enables the best escape-hatch.
The underlying configuration blocks would never be exposed through a config file, but rather by requiring users to:
- eg: reinstantiate
new ApolloServer({ ...customConfig })
with a custom config , or use a completely different GraphQL server - eg: call nexus'
makeSchema({ ...customConfig })
with a custom config - Add additional & custom behaviour
To make it simpler for users, we could provide in the docs the code needed in order to get back to the default behaviour. (keep in mind that this API isn't final at all)
// ./src/index.ts
import { ApolloServer } from 'apollo-server'
import { initYoga } from 'yoga'
import context from './context.ts'
initYoga({
server: () => {
const schema = makeSchema({ ...customConfig })
return new ApolloServer({
schema,
context,
...customConfig,
})
},
startServer: server =>
server
.listen()
.then(({ url }) => console.log(`🚀 Server ready at ${url}`)),
stopServer: server => server.stop(),
})
More hooks could be exposed in the future, such as a before
(triggered before the server is instantiated) and after
(triggered when the process exists) hook for instance.
This leaves one open question though: How do we handle build step ? (#14)
As custom code will be passed as parameters of the initYoga
function, it might be that we need to extract that code once we build the server for "production" use. (as initYoga
might do extra unneeded stuff)
While #24 was merged, effectively implementing Option 3, I still wonder whether we should not offer a less radical option to eject
With option 3, here are the steps to get back to the previous state:
- Create
index.ts
and cp the following code:
import * as path from 'path'
import { ApolloServer, makeSchema, Yoga } from 'yoga'
import context from './context'
import * as types from './graphql'
export default {
server: dirname => {
const schema = makeSchema({
types,
outputs: {
schema: path.join(dirname, './generated/nexus.graphql'),
typegen: path.join(dirname, './generated/nexus.ts'),
},
typegenAutoConfig: {
sources: [
{
module: path.join(dirname, './context.ts'),
alias: 'ctx',
},
],
contextType: 'ctx.Context',
},
})
return new ApolloServer({
schema,
context,
})
},
startServer: server =>
server.listen().then(s => console.log(`Server listening at ${s.url}`)),
stopServer: server => server.stop(),
} as Yoga<ApolloServer>
- Go to your
/graphql
folder, create anindex.ts
file, andexport * from './your-file'
for all your files
Which seems quite a lot if you just want to run apollo-server on a different port for instance 🤔