mattkrick / cashay

:moneybag: Relay for the rest of us :moneybag:
MIT License
453 stars 28 forks source link

Webpack client schema loader #50

Closed simenbrekken closed 8 years ago

simenbrekken commented 8 years ago

During development it would be very nice if one could load the client schema using a webpack loader:

const clientSchema = require('schema!./schema');
jordanh commented 8 years ago

I've been thinking about this too. Right now, we have a npm run hook that looks like this:

"build:gqlschema": "cashay-schema src/server/graphql/rootSchema.js build/graphqlSchema.json --oncomplete src/server/database/rethinkExit.js",

Then, we're pulling it into our client using webpack's JSON loader.

I suppose we could write a cashay-schema-loader for webpack that would take the same parameters as cashay-schema to build and inject the schema into the client. It'd be wonderful to not have to remember to run cashay-schema manually!

mattkrick commented 8 years ago

😳 This is so beautifully obvious I don't know why I never thought to perform it in the loader. Crap. Guess I have to learn how to write loaders!

This is gonna take some learning on my part so I can't promise a fast turnaround since subscriptions and multi-part queries are next up in my sprints, but if you took a crack at it I'd be more than happy to review & accept!

simenbrekken commented 8 years ago

@mattkrick Shazam! #51 💫

simenbrekken commented 8 years ago

See https://github.com/mattkrick/cashay-schema-loader

jordanh commented 8 years ago

Oops, should have added comments here, rather than on #51.

See #52. Very similar to @simenbrekken's implementation. Considering we did this independently, must only be a few ways of doing it.

Here's how it is using in the client:

// Create the store:
const store = makeStore(initialState);

// Create the Cashay singleton:
const cashaySchema = require('raw!cashay!../server/utils/getCashaySchema.babel.js');
///                           ^^^ note the loader string

const cashayHttpTransport = new HTTPTransport(...);
const cashayParams = {
  store,
  schema: cashaySchema,
  getToState: reduxStore => reduxStore.getState().get('cashay'),
  transport: cashayHttpTransport
};

// export the Cashay singleton:
export const cashay = new Cashay(cashayParams);

In order to user the loader, the user must provide a javascript file that, when executed, returns a Promise for a schema. I figured this would be even more flexible than cashay-schema as folks can pull their schema from their local runtime, an Internet URL, from morse code, or anything other source.

The one I've put into Action, getCashaySchema.babel.js, looks like this:

require('babel-register');
require('babel-polyfill');
module.exports = require('./getCashaySchema')();

It's friend getCashaySchema.js looks like this:

import rootSchema from '../graphql/rootSchema';
import {transformSchema} from 'cashay';
import rethinkExit from './rethinkExit';

// side-step 'Error: Schema must be an instance of GraphQLSchema.'
const graphql = require('graphql').graphql;

export default async () => {
  const schema = await transformSchema(rootSchema, graphql);
  rethinkExit();
  return schema;
};

One very small difference between my implementation and @simenbrekken's is I'm using the raw loader, which avoids a transform to JSON.

simenbrekken commented 8 years ago

@jordanh Interesting with the raw loader, is it any way explicitly requiring that loader in project dependencies could be avoided?

mattkrick commented 8 years ago
jordanh commented 8 years ago

We could, but I think it'd be less webpack-y to eliminate the explicit requirement for raw-loader. Consider the myriad CSS loaders that explicitly depend on one another.

AFAIK, there isn't any way to call another loader raw from a custom loader implementation, so we'd have to literally copy the functionality of raw (which is quite small) into cashay-loader.

My preferences:

If issues are opened by others (e.g. "eliminate dependancy on raw-loader"), then we revisit the implementation at that time.

mattkrick commented 8 years ago

I say we integrate raw loader. Oh, and by integrate, i mean write 2 lines of code: https://github.com/webpack/raw-loader/blob/master/index.js#L7-L8

jordanh commented 8 years ago

I know, right? It's a pretty simple little animal.

Ok, done! Updated #52.

mattkrick commented 8 years ago

i'll rewrite the test that is causing the fail & push to npm shortly

simenbrekken commented 8 years ago

I still think it's a bit clunky having to import both babel-register and babel-polyfill just to use the loader, IMHO the loader should work without any of these as package.json does not list any of these dependencies.

Could we perhaps include the polyfill when building the library or the loader itself?

jordanh commented 8 years ago

A note here: it's the user's code which is driving the requirement to babel-register and babel-polyfill not Cashay. Cashay itself has already been transpiled.

The usage of babel-register and babel-polyfill in the example I gave was mine for the babel application we're writing, not a requirement of Cashay.

jordanh commented 8 years ago

Ah, I take it back. @mattkrick points out that babel-polyfill is required due to Cashay's usage of async await. In which case, @simenbrekken you're completely correct. Be nice not to require that of the end user...