files cleanup

This commit is contained in:
eric-burel 2018-11-20 15:50:48 +01:00
parent 42ac3c37db
commit c08df1a36f
11 changed files with 144 additions and 190 deletions

View file

@ -26,8 +26,7 @@ import getPlaygroundConfig from './playground';
import { GraphQLSchema } from '../../modules/graphql.js';
// SSR
import { ssrMiddleware } from '../apollo-ssr'
import { enableSSR } from '../apollo-ssr'
/**
* Options: Apollo server usual options
*
@ -91,7 +90,7 @@ const createApolloServer = ({ options: givenOptions = {}, config: givenConfig =
// ssr
//WebApp.connectHandlers.use(ssrMiddleware)
enableSSR()
/*
* Alternative syntax with Express instead of Connect

View file

@ -1,75 +0,0 @@
// TODO: adapt to Vulcan
// @see https://github.com/apollographql/GitHunt-React/blob/master/src/routes/Html.js
/* eslint-disable react/no-danger */
import React from 'react';
import { Helmet } from 'react-helmet'
const Head = () => {
// Helmet.rewind() is deprecated in favour of renderStatic() for better readability
//@see https://github.com/nfl/react-helmet/releases/tag/5.0.0
const helmet = Helmet.renderStatic()
return (
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
)
}
// this render the page and add a script that will load the serialized data
const Html = ({ content, state }) => {
return (
<html>
<Head />
<body>
<div id="root" dangerouslySetInnerHTML={{ __html: content }} />
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(/</g, '\\u003c')};`,
}} />
</body>
</html>
);
}
export default Html;
// Previous implementation from router.js:
// Some code could be useful eg to handle CSS
//function sendSSRHtml(options, req, res, next, renderProps) {
// const { css, html, styledComponentCss } = generateSSRData(options, req, res, renderProps);
//
// req.dynamicHead = req.dynamicHead || '';
// req.dynamicBody = req.dynamicBody || '';
//
// // css declared in the project
// if (css) {
// req.dynamicHead += `<style id="${options.styleCollectorId || 'css-style-collector-data'}">${css}</style>`;
// }
//
// // css collected by styled-components
// if (styledComponentCss) {
// req.dynamicHead += styledComponentCss;
// }
//
// let rootElementAttributes = '';
// const attributes = options.rootElementAttributes instanceof Array ? options.rootElementAttributes : [];
// if (attributes[0] instanceof Array) {
// for (let i = 0; i < attributes.length; i++) {
// rootElementAttributes += ` ${attributes[i][0]}="${attributes[i][1]}"`;
// }
// } else if (attributes.length > 0) {
// rootElementAttributes = ` ${attributes[0]}="${attributes[1]}"`;
// }
//
// req.dynamicBody += `<${options.rootElementType || 'div'} id="${options.rootElement || 'react-app'}"${rootElementAttributes}>${html || ''}</${options.rootElementType || 'div'}>`;
//
// if (typeof options.htmlHook === 'function') {
// const { dynamicHead, dynamicBody } = options.htmlHook(req, res, req.dynamicHead, req.dynamicBody);
// req.dynamicHead = dynamicHead;
// req.dynamicBody = dynamicBody;
// }
//
// next();
//}

View file

@ -0,0 +1,18 @@
/**
* Component that serialize the Apollo client state
*
* The client can then deserialize it and avoid unecessary requests
*/
import React from 'react';
const ApolloState = ({ initialState }) => (
<script
dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__ = ${JSON.stringify(initialState).replace(
/</g,
'\\u003c'
)};`
}}
/>
);
export default ApolloState;

View file

@ -1,3 +1,6 @@
/**
* The App + relevant wrappers
*/
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import { StaticRouter } from 'react-router';
@ -7,11 +10,10 @@ import { StaticRouter } from 'react-router';
// so Components.App is not defined here
import { Components } from 'meteor/vulcan:lib'
// TODO: adapt to Vulcan
// The client-side App will instead use <BrowserRouter>
// see client-side vulcan:core/lib/client/start.jsx implementation
// we do the same server side
const appGenerator = ({ req, client, context }) => {
const AppGenerator = ({ req, client, context }) => {
const App = (
<ApolloProvider client={client}>
<StaticRouter location={req.url} context={context}>
@ -21,4 +23,4 @@ const appGenerator = ({ req, client, context }) => {
);
return App;
};
export default appGenerator;
export default AppGenerator;

View file

@ -5,7 +5,6 @@
* /!\ It must be recreated on every request
*/
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';

View file

@ -0,0 +1,30 @@
/**
* Actually enable SSR
*/
import {
populateComponentsApp,
populateRoutesApp,
initializeFragments
} from 'meteor/vulcan:lib';
// onPageLoad is mostly equivalent to an Express middleware
// excepts it is tailored to handle Meteor server side rendering
import { onPageLoad } from 'meteor/server-render'
import renderPage from './renderPage'
const enableSSR = () => {
Meteor.startup(() => {
// init the application components and routes, including components & routes from 3rd-party packages
initializeFragments();
populateComponentsApp();
populateRoutesApp();
// render the page
onPageLoad(renderPage)
});
}
export default enableSSR

View file

@ -1,2 +1 @@
import './start'
export { default as ssrMiddleware } from './ssrMiddleware'
export { default as enableSSR } from './enableSSR'

View file

@ -0,0 +1,88 @@
/**
* Render the page server side
* @see https://github.com/szomolanyi/MeteorApolloStarter/blob/master/imports/startup/server/ssr.js
* @see https://github.com/apollographql/GitHunt-React/blob/master/src/server.js
* @see https://www.apollographql.com/docs/react/features/server-side-rendering.html#renderToStringWithData
*/
import React from 'react';
import ReactDOM from 'react-dom/server';
import { renderToStringWithData } from 'react-apollo';
import createClient from './createApolloClient';
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);
// 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} />
)
// 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 Apollo state, the client will then parse the string
const initialState = client.extract();
const serializedApolloState = ReactDOM.renderToString(
<ApolloState initialState={initialState} />
)
sink.appendToBody(serializedApolloState)
}
export default renderPage
// 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
// Could be useful for SEO though or if we switch away from Meteor
//const ssrMiddleware = (req, res) => {
// // according to the Apollo doc, client needs to be recreated on every request
// // this avoids caching server side
// const client = createClient(req);
// // TODO adapt to Vulcan
// const context = {};
//
// const App = appGenerator({ req, client, context })
//
// // Alternative that relies on Meteor server-render:
// // @see https://github.com/szomolanyi/MeteorApolloStarter/blob/master/imports/startup/server/ssr.js
//
// // TODO: adapt to Vulcan
// // @see https://github.com/apollographql/GitHunt-React/blob/master/src/server.js
// // @see https://www.apollographql.com/docs/react/features/server-side-rendering.html#renderToStringWithData
// renderToStringWithData(App)
// .then(content => {
// const initialState = client.extract();
// const html = <Html content={content} state={initialState} />;
// //const html = <Html content={content} client={client} />;
// res.statusCode = 200;
// //res.status(200);
// res.end(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`, 'utf8');
// //res.end();
// })
// .catch(e => {
// console.error('RENDERING ERROR:', e); // eslint-disable-line no-console
// res.statusCode = 500;
// res.end(`An error occurred. Stack trace:\n\n${e.stack}`);
// });
//
// // TODO here we actually render with context (see render_context)
//};
// export default ssrMiddleware;

View file

@ -1,94 +0,0 @@
/**
* Server-side Apollo client
*
*
* @see https://www.apollographql.com/docs/react/features/server-side-rendering.html#server-initialization
*/
// This example uses React Router v4, although it should work
// equally well with other routers that support SSR
import { renderToStringWithData } from 'react-apollo';
import createClient from './createClient';
import React from 'react';
import ReactDOM from 'react-dom/server';
// onPageLoad is mostly equivalent to an Express middleware
// excepts it is tailored to handle Meteor server side rendering
import { onPageLoad } from 'meteor/server-render'
import Html from './Html';
import Head from './Head'
import appGenerator from './appGenerator';
onPageLoad(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);
// TODO adapt to Vulcan
const context = {};
const App = appGenerator({ req, client, context })
// Alternative that relies on Meteor server-render:
// @see https://github.com/szomolanyi/MeteorApolloStarter/blob/master/imports/startup/server/ssr.js
// TODO: adapt to Vulcan
// @see https://github.com/apollographql/GitHunt-React/blob/master/src/server.js
// @see https://www.apollographql.com/docs/react/features/server-side-rendering.html#renderToStringWithData
// equivalent to calling getDataFromTree and then renderToStringWithData
//sink.appendToBody(ReactDOM.renderToStaticMarkup(<div id='react-app'></div>))
const content = await renderToStringWithData(App)
console.log(content.slice(0,100))
const wrappedContent = `<div id="react-app">${content}</div>`
sink.appendToBody(wrappedContent)
//sink.renderIntoElementById('react-app', 'HI')//content)
// add headers
const head = ReactDOM.renderToString(Head)
sink.appendToHead(head)
// add data
const initialState = client.extract();
sink.appendToBody(ReactDOM.renderToString(
<script dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__ = ${JSON.stringify(initialState).replace(/</g, '\\u003c')};`,
}} />
))
})
const ssrMiddleware = (req, res) => {
// according to the Apollo doc, client needs to be recreated on every request
// this avoids caching server side
const client = createClient(req);
// TODO adapt to Vulcan
const context = {};
const App = appGenerator({ req, client, context })
// Alternative that relies on Meteor server-render:
// @see https://github.com/szomolanyi/MeteorApolloStarter/blob/master/imports/startup/server/ssr.js
// TODO: adapt to Vulcan
// @see https://github.com/apollographql/GitHunt-React/blob/master/src/server.js
// @see https://www.apollographql.com/docs/react/features/server-side-rendering.html#renderToStringWithData
renderToStringWithData(App)
.then(content => {
const initialState = client.extract();
const html = <Html content={content} state={initialState} />;
//const html = <Html content={content} client={client} />;
res.statusCode = 200;
//res.status(200);
res.end(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`, 'utf8');
//res.end();
})
.catch(e => {
console.error('RENDERING ERROR:', e); // eslint-disable-line no-console
res.statusCode = 500;
res.end(`An error occurred. Stack trace:\n\n${e.stack}`);
});
// TODO here we actually render with context (see render_context)
};
export default ssrMiddleware;

View file

@ -1,12 +0,0 @@
import {
populateComponentsApp,
populateRoutesApp,
initializeFragments
} from 'meteor/vulcan:lib';
Meteor.startup(() => {
// init the application components and routes, including components & routes from 3rd-party packages
initializeFragments();
populateComponentsApp();
populateRoutesApp();
});