yoga2 icon indicating copy to clipboard operation
yoga2 copied to clipboard

Re-exposing configurations through yoga

Open Weakky opened this issue 6 years ago • 4 comments

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?

Weakky avatar Jan 10 '19 17:01 Weakky

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.

wtrocki avatar Jan 10 '19 22:01 wtrocki

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.

briandennis avatar Jan 13 '19 18:01 briandennis

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)

Weakky avatar Jan 15 '19 10:01 Weakky

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 an index.ts file, and export * 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 🤔

Weakky avatar Jan 23 '19 17:01 Weakky