pass context to the schemaLink too in order to setup resolvers

This commit is contained in:
eric-burel 2018-11-22 12:40:27 +01:00
parent 8a1e1b7a64
commit ec0e7b9e7f
5 changed files with 65 additions and 56 deletions

View file

@ -32,24 +32,27 @@ import { enableSSR } from '../apollo-ssr'
*
* Config: a config specific to Vulcan
*/
const createApolloServer = ({ options: givenOptions = {}, config: givenConfig = {}, contextFromReq }) => {
const createApolloServer = ({ options: givenOptions = {}, config: givenConfig = {}, customContextFromReq }) => {
const graphiqlOptions = { ...defaultConfig.graphiqlOptions, ...givenConfig.graphiqlOptions };
const config = { ...defaultConfig, ...givenConfig };
config.graphiqlOptions = graphiqlOptions;
// get the options and merge in defaults
const options = { ...defaultOptions, ...givenOptions };
const context = initContext(options.context);
const initialContext = initContext(options.context);
// this replace the previous syntax graphqlExpress(async req => { ... })
// this function takes the context, which contains the current request,
// and setup the options accordingly ({req}) => { ...; return options }
const contextFromReq = computeContextFromReq(initialContext, customContextFromReq)
// given options contains the schema
const apolloServer = new ApolloServer({
engine: engineConfig,
// graphql playground (replacement to graphiql), available on the app path
playground: getPlaygroundConfig(config),
...options,
// this replace the previous syntax graphqlExpress(async req => { ... })
// this function takes the context, which contains the current request,
// and setup the options accordingly ({req}) => { ...; return options }
context: computeContextFromReq(context, contextFromReq)
// context optionbject or a function of the current request (+ maybe some other params)
context: ({req}) => contextFromReq(req)
});
// default function does nothing
@ -90,7 +93,7 @@ const createApolloServer = ({ options: givenOptions = {}, config: givenConfig =
// ssr
enableSSR()
enableSSR({ computeContext: contextFromReq})
/*
* Alternative syntax with Express instead of Connect

View file

@ -45,24 +45,24 @@ export const initContext = currentContext => {
return context;
};
// Call on every request
export const computeContextFromReq = (currentContext, contextFromReq) => {
// @see https://www.apollographql.com/docs/react/recipes/meteor#Server
const setupAuthToken = async (context, req) => {
const user = await getUser(req.headers.authorization);
if (user) {
context.userId = user._id;
context.currentUser = user;
// identify user to any server-side analytics providers
runCallbacks('events.identify', user);
} else {
context.userId = undefined;
context.currentUser = undefined;
}
};
// Returns a function called on every request to compute context
export const computeContextFromReq = (currentContext, customContextFromReq) => {
// givenOptions can be either a function of the request or an object
const getBaseContext = req =>
contextFromReq ? { ...currentContext, ...contextFromReq(req) } : { ...currentContext };
// @see https://www.apollographql.com/docs/react/recipes/meteor#Server
const setupAuthToken = async (context, req) => {
const user = await getUser(req.headers.authorization);
if (user) {
context.userId = user._id;
context.currentUser = user;
// identify user to any server-side analytics providers
runCallbacks('events.identify', user);
} else {
context.userId = undefined;
context.currentUser = undefined;
}
};
customContextFromReq ? { ...currentContext, ...customContextFromReq(req) } : { ...currentContext };
// Previous implementation
// Now meteor/apollo already provide this
// Get the token from the header
@ -91,7 +91,7 @@ export const computeContextFromReq = (currentContext, contextFromReq) => {
//}
// create options given the current request
const handleReq = async ({ req }) => {
const handleReq = async (req) => {
let context;
let user = null;

View file

@ -18,14 +18,16 @@ import { ApolloLink } from 'apollo-link';
// import { createHttpLink } from 'apollo-link-http';
// import fetch from 'node-fetch'
export const createClient = (req) => {
export const createClient = async ({req, computeContext}) => {
// init
// stateLink will init the client internal state
const cache = new InMemoryCache()
const stateLink = createStateLink({ cache });
// schemaLink will fetch data directly based on the executable schema
const schema = GraphQLSchema.getExecutableSchema()
const schemaLink = new SchemaLink({ schema })
// this is the resolver context
const context = await computeContext(req)
const schemaLink = new SchemaLink({ schema, context })
const client = new ApolloClient({
ssrMode: true,
link: ApolloLink.from([stateLink, schemaLink, errorLink]),

View file

@ -11,17 +11,17 @@ import {
// excepts it is tailored to handle Meteor server side rendering
import { onPageLoad } from 'meteor/server-render'
import renderPage from './renderPage'
import makePageRenderer from './renderPage'
const enableSSR = () => {
const enableSSR = ({computeContext}) => {
Meteor.startup(() => {
// init the application components and routes, including components & routes from 3rd-party packages
initializeFragments();
populateComponentsApp();
populateRoutesApp();
// render the page
onPageLoad(renderPage)
onPageLoad(makePageRenderer({computeContext}))
});
}

View file

@ -14,40 +14,44 @@ import Head from './components/Head'
import ApolloState from './components/ApolloState'
import AppGenerator from './components/AppGenerator';
const renderPage = async sink => {
const req = sink.request
// according to the Apollo doc, client needs to be recreated on every request
// this avoids caching server side
const client = createClient(req);
const makePageRenderer = ({ computeContext }) => {
// onPageLoad callback
const renderPage = async sink => {
const req = sink.request
// according to the Apollo doc, client needs to be recreated on every request
// this avoids caching server side
const client = await createClient({req, computeContext});
// TODO? do we need this?
const context = {};
// TODO? do we need this?
const context = {};
// equivalent to calling getDataFromTree and then renderToStringWithData
const content = await renderToStringWithData(
<AppGenerator req={req} client={client} context={context} />
)
// equivalent to calling getDataFromTree and then renderToStringWithData
const content = await renderToStringWithData(
<AppGenerator req={req} client={client} context={context} />
)
// TODO: there should be a cleaner way to set this wrapper
// id must always match the client side start.jsx file
const wrappedContent = `<div id="react-app">${content}</div>`
sink.appendToBody(wrappedContent)
// TODO: this sounds cleaner but where do we add the <div id="react-app"> ?
//sink.renderIntoElementById('react-app', content)
// TODO: there should be a cleaner way to set this wrapper
// id must always match the client side start.jsx file
const wrappedContent = `<div id="react-app">${content}</div>`
sink.appendToBody(wrappedContent)
// TODO: this sounds cleaner but where do we add the <div id="react-app"> ?
//sink.renderIntoElementById('react-app', content)
// add headers using helmet
const head = ReactDOM.renderToString(<Head />)
sink.appendToHead(head)
// add headers using helmet
const head = ReactDOM.renderToString(<Head />)
sink.appendToHead(head)
// add Apollo state, the client will then parse the string
const initialState = client.extract();
const serializedApolloState = ReactDOM.renderToString(
<ApolloState initialState={initialState} />
)
sink.appendToBody(serializedApolloState)
// add Apollo state, the client will then parse the string
const initialState = client.extract();
const serializedApolloState = ReactDOM.renderToString(
<ApolloState initialState={initialState} />
)
sink.appendToBody(serializedApolloState)
}
return renderPage
}
export default renderPage
export default makePageRenderer
// FIRST TRY WITH EXPRESS
// However this won't work as Meteor is in charge of loading relevant JS script
// This code would only render dead HTML