-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathhandler.js
116 lines (95 loc) · 3.38 KB
/
handler.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
import React from 'react';
import { Provider } from 'react-redux';
import { StaticRouter, matchPath } from 'react-router';
import { setMobileDetect, mobileParser } from 'react-responsive-redux';
import { renderToString } from 'react-dom/server';
import { ErrorPage } from 'components/common';
import { getBundles } from 'react-loadable/webpack';
import Loadable from 'react-loadable';
import render from './render';
import routes from 'routes';
import configureStore from 'store';
import App from 'containers/App';
import config from '../../config';
let stats = null;
if (config.enableDynamicImports) {
stats = require('../../react-loadable.json');
}
export default function handleRender(req, res) {
const initialState = {};
// Create a new Redux store instance
const store = configureStore(initialState);
// Server side responsive detection
const mobileDetect = mobileParser(req);
// set mobile detection for our responsive store
store.dispatch(setMobileDetect(mobileDetect));
// Grab the initial state from our Redux store
const finalState = store.getState();
// See react-router's Server Rendering section:
// https://reacttraining.com/react-router/web/guides/server-rendering
const matchRoutes = routes => {
return routes.reduce((matches, route) => {
const { path } = route;
const match = matchPath(req.baseUrl, {
path,
exact: true,
strict: false
});
if (match) {
const wc =
(route.component && route.component.WrappedComponent) ||
route.component;
matches.push({
route,
match,
fetchData: (wc && wc.fetchData) || (() => Promise.resolve())
});
}
if (!match && route.routes) {
// recursively try to match nested routes
const nested = matchRoutes(route.routes);
if (nested.length) {
matches = matches.concat(nested);
}
}
return matches;
}, []);
};
const matches = matchRoutes(routes);
// No matched route, render a 404 page.
if (!matches.length) {
res.contentType('text/html');
res.status(404).send(render(<ErrorPage code={404} />, finalState));
return;
}
// There's a match, render the component with the matched route, firing off
// any fetchData methods that are statically defined on the server.
const fetchData = matches.map(match => {
const { fetchData, ...rest } = match; // eslint-disable-line no-unused-vars
// return fetch data Promise, excluding unnecessary fetchData method
return match.fetchData({ store, ...rest });
});
let context = {}, modules = [];
const component = (
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<Provider store={store}>
<StaticRouter context={context} location={req.baseUrl}>
<App />
</StaticRouter>
</Provider>
</Loadable.Capture>
);
// Execute the render only after all promises have been resolved.
Promise.all(fetchData).then(() => {
const state = store.getState();
const html = renderToString(component);
const bundles = stats && getBundles(stats, modules) || [];
const markup = render(html, state, bundles);
// A 301 redirect was rendered somewhere if context.url exists after
// rendering has happened.
if (context.url) {
return res.redirect(302, context.url);
}
return res.status(200).send(markup);
});
}