-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.jsx
253 lines (199 loc) · 6.44 KB
/
server.jsx
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import React from 'react';
import path from 'path';
import crypto from 'crypto';
import express from 'express';
import compression from 'compression';
import serveStatic from 'serve-static';
import bodyParser from 'body-parser';
import sessions from 'client-sessions';
import helmet from 'helmet';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import { Provider } from 'react-redux';
import reactHelmet from 'react-helmet';
import HtmlMinifier from 'html-minifier';
import d from 'debug';
import serverConfig from './config';
import configureStore from '../client/store';
import fetchComponentData from './util/fetchData';
import routes from '../client/routes';
/**
* Debug
*/
const debug = {
log: d('server:log'),
err: d('server:err'),
};
/**
* HTTPS
*/
// const https = require('https'); // eslint-disable-line global-require, import/no-extraneous-dependencies, no-unused-vars
// const fs = require('fs'); // eslint-disable-line global-require, import/no-extraneous-dependencies, no-unused-vars
// const options = {
// key: fs.readFileSync('/srv/www/keys/my-site-key.pem'),
// cert: fs.readFileSync('/srv/www/keys/chain.pem'),
// };
/**
* App
*/
const app = express();
app.enable('trust proxy');
/**
* Middleware Stack
*/
// Webpack if in development
if (process.env.NODE_ENV === 'development') {
const webpack = require('webpack'); // eslint-disable-line global-require, import/no-extraneous-dependencies
const webpackConfig = require('../webpack.config.dev'); // eslint-disable-line global-require
const webpackDevMiddleware = require('webpack-dev-middleware'); // eslint-disable-line global-require, import/no-extraneous-dependencies
const webpackHotMiddleware = require('webpack-hot-middleware'); // eslint-disable-line global-require, import/no-extraneous-dependencies
const compiler = webpack(webpackConfig);
// Dev Middleware
app.use(webpackDevMiddleware(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
},
}));
// Hot Reloading Middleware
app.use(webpackHotMiddleware(compiler, {
log: () => {},
heartbeat: 1000,
}));
}
// Gzip Compression
app.use(compression());
// Session
const secureProxy = process.env.NODE_ENV === 'production';
const sessionsSecret = crypto.randomBytes(256).toString('base64'); // TODO Change this when hosting situation is finalized
app.use(sessions({
cookieName: 'session',
secret: sessionsSecret,
duration: 24 * 60 * 60 * 1000, // 1 day
activeDuration: 5 * 60 * 1000, // 5 minutes
cookie: {
ephemeral: false,
httpOnly: true,
secureProxy,
},
}));
// Handle JSON/URL Encoding
app.use(bodyParser.json({
limit: '20mb',
}));
app.use(bodyParser.urlencoded({
limit: '20mb',
extended: false,
}));
// Helmet
app.use(helmet());
/**
* Static Content
*/
// Static client content from Webpack
app.use(serveStatic(path.join(__dirname, '../client-dist')));
// Other static content
app.use(serveStatic(path.join(__dirname, '../public')));
/**
* Routes
*/
/**
* Render Initial HTML
*/
const renderFullPage = (initialView, initialState) => {
const prod = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging';
const assetsManifest = process.env.webpackAssets && JSON.parse(process.env.webpackAssets);
const chunkManifest = process.env.webpackChunkAssets && JSON.parse(process.env.webpackChunkAssets);
const renderedHelmet = reactHelmet.renderStatic();
// const headString = Object.keys(head).map(key => head[key].toString()).join('');
const manifestScript = `
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
${prod ?
`//<![CDATA[
window.webpackManifest = ${JSON.stringify(chunkManifest)};
//]]>` : ''}
`;
const html = `
<!DOCTYPE html>
<html ${renderedHelmet.htmlAttributes.toString()}>
<head>
${renderedHelmet.meta.toString()}
${renderedHelmet.link.toString()}
${renderedHelmet.title.toString()}
${renderedHelmet.style.toString()}
${prod ? `<link rel="stylesheet" href="${assetsManifest['/app.css']}" />` : ''}
</head>
<body ${renderedHelmet.bodyAttributes.toString()}>
<div id="root" data-reactmount>${initialView}</div>
<script>${manifestScript}</script>
<script defer src="${prod ? assetsManifest['/vendor.js'] : '/vendor.js'}"></script>
<script defer src="${prod ? assetsManifest['/app.js'] : '/app.js'}"></script>
</body>
</html>
`;
const minifiedHtml = HtmlMinifier.minify(html, {
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: false,
collapseWhitespace: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: false,
removeComments: false,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true,
});
return minifiedHtml;
};
app.use((req, res, next) => {
match({ routes, location: req.url }, (matchErr, redirectLocation, renderProps) => {
if (matchErr) {
return debug.err(matchErr);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
if (!renderProps) {
return next();
}
const store = configureStore();
return fetchComponentData(store, renderProps.components, renderProps.params)
.then(() => {
const initialView = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
const finalState = store.getState();
finalState._csrf = res.locals._csrf;
res
.set('content-type', 'text/html')
.status(200)
.end(renderFullPage(initialView, finalState));
})
.catch(fetchErr => next(fetchErr));
});
});
/**
* Create Server and Listen
*/
app.listen(serverConfig.port, (err) => {
if (err) {
debug.err(err);
} else {
debug.log(`Express server listening on port ${serverConfig.port}`);
if (process.env.NODE_ENV === 'development') {
const opn = require('opn'); // eslint-disable-line global-require, import/no-extraneous-dependencies
opn(`http://127.0.0.1:${serverConfig.port}`);
}
}
});
/**
* Server-side route to D4SD 2019 application form
*/
app.get('/apply', (req, res) => {
res.status(301).redirect('https://docs.google.com/forms/d/e/1FAIpQLSeNqFs9qOseMVc8NFJxrG1clB1r6MK71NvMiBBBKAypEKKVPg/viewform?c=0&w=1');
});