This repository has been archived by the owner on Oct 10, 2023. It is now read-only.
/
render.js
135 lines (120 loc) · 4.16 KB
/
render.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* @flow */
import App from '../../browser/app/App';
import Helmet from 'react-helmet';
import Html from './Html';
import Promise from 'bluebird';
import React from 'react';
import ServerFetchProvider from './ServerFetchProvider';
import config from '../config';
import configureStore from '../../common/configureStore';
import createInitialState from './createInitialState';
import serialize from 'serialize-javascript';
import { Provider as Redux } from 'react-redux';
import { createServerRenderContext, ServerRouter } from 'react-router';
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
import { toJSON } from '../../common/transit';
const settleAllWithTimeout = promises => Promise
.all(promises.map(p => p.reflect()))
.each(inspection => {
if (inspection.isFulfilled()) return;
console.log('Server fetch failed:', inspection.reason());
})
.timeout(5000) // Do not block rendering if any fetch is still pending.
.catch(error => {
if (error instanceof Promise.TimeoutError) {
console.log('Server fetch timeouted:', error);
return;
}
throw error;
});
const initialState = createInitialState();
const getHost = req =>
`${req.headers['x-forwarded-proto'] || req.protocol}://${req.headers.host}`;
const getLocale = req => process.env.IS_SERVERLESS
? config.defaultLocale
: req.acceptsLanguages(config.locales) || config.defaultLocale;
const createStore = (req) => configureStore({
initialState: {
...initialState,
device: initialState.device
.set('host', getHost(req)),
intl: initialState.intl
.set('currentLocale', getLocale(req))
.set('initialNow', Date.now()),
},
});
const renderBody = (store, context, location, fetchPromises) => {
const markup = renderToString(
<Redux store={store}>
<ServerFetchProvider promises={fetchPromises}>
<ServerRouter
context={context}
location={location}
>
<App />
</ServerRouter>
</ServerFetchProvider>
</Redux>
);
return { markup, helmet: Helmet.rewind() };
};
const renderScripts = (state, appJsFilename) =>
// https://github.com/yahoo/serialize-javascript#user-content-automatic-escaping-of-html-characters
// TODO: Switch to CSP, https://github.com/este/este/pull/731
`
<script>
window.__INITIAL_STATE__ = ${serialize(toJSON(state))};
</script>
<script src="${appJsFilename}"></script>
`;
const renderHtml = (state, bodyMarkupWithHelmet) => {
const {
styles: { app: appCssFilename },
javascript: { app: appJsFilename },
} = global.webpackIsomorphicTools.assets();
if (!config.isProduction) {
global.webpackIsomorphicTools.refresh();
}
const { markup: bodyMarkup, helmet } = bodyMarkupWithHelmet;
const scriptsMarkup = renderScripts(state, appJsFilename);
const markup = renderToStaticMarkup(
<Html
appCssFilename={appCssFilename}
bodyHtml={`<div id="app">${bodyMarkup}</div>${scriptsMarkup}`}
googleAnalyticsId={config.googleAnalyticsId}
helmet={helmet}
isProduction={config.isProduction}
/>
);
return `<!DOCTYPE html>${markup}`;
};
// react-router.now.sh/ServerRouter
const render = async (req: Object, res: Object, next: Function) => {
try {
const context = createServerRenderContext();
const store = createStore(req);
const fetchPromises = [];
let bodyMarkupWithHelmet = renderBody(store, context, req.url, fetchPromises);
const result = context.getResult();
if (result.redirect) {
res.redirect(301, result.redirect.pathname + result.redirect.search);
return;
}
if (result.missed) {
bodyMarkupWithHelmet = renderBody(store, context, req.url);
const htmlMarkup = renderHtml(store.getState(), bodyMarkupWithHelmet);
res.status(404).send(htmlMarkup);
return;
}
if (!process.env.IS_SERVERLESS && fetchPromises.length > 0) {
await settleAllWithTimeout(fetchPromises);
bodyMarkupWithHelmet = renderBody(store, context, req.url);
}
const htmlMarkup = renderHtml(store.getState(), bodyMarkupWithHelmet);
res.status(200).send(htmlMarkup);
} catch (error) {
console.log(error);
next(error);
}
};
export default render;