-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnode.ucp.js
114 lines (98 loc) · 5.17 KB
/
node.ucp.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
/*
* @author : Simran Singh ( simran.singh2198@gmail.com )
*/
process.env['UV_THREADPOOL_SIZE'] = 128;
import Express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter, matchPath } from 'react-router';
import { Route } from 'react-router-dom';
import serialize from 'serialize-javascript';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import compression from 'compression';
import fs from 'fs';
import './src/initialize.js';
import config from './src/config.js';
import routes from './src/shared/routes/routes.js';
import AppRoutes from './src/shared/routes';
import APIHandler from './src/api';
import generateMetaData from './src/utils/generateMetaData';
import verifySession from './src/utils/verifySession';
const app = Express();
// lets first load our index page template
let template = fs.readFileSync('./public/index.template.html', 'utf8');
if (!template) {
console.error('[ERROR]: Unable to read index template file, make sure ./public/index.template.html file exists and has right permissions.');
process.exit();
}
app.use(compression()); // enable gzip compression
app.use(bodyParser.json()); // parse POST data, doesn't parse multi-part data for uploading files see upload avatar api for handling file uploads
app.use(cookieParser()); // parse cookies
//=====================================================================
// only for DEV mode
// does compiling on server restart(hot reloading)
// also modfies template to use global.css instead of global.min.css and to setup cache buster for bundle.js
// this cache buster is handled by service worker see /public/js/sw.js for seeing exactly how
if (process.env.NODE_ENV != 'production') {
//editing our page template for cache busting and using global.css instead of minified version
template = template.replace(/bundle\.js.*"/g, "bundle\.js\?v=" + Math.floor(Date.now() / 1000) + "\"")
.replace(/css\/global.*?"/g, 'css\/global.css"');
const webpack = require('webpack'),
MemoryFS = require('memory-fs');
const webpackConfig = require(`./webpack${process.env.NODE_ENV == 'production' ? '' : '.dev'}.config.js`);
let compiler = webpack(webpackConfig),
mfs = new MemoryFS(),
bundle = "";
compiler.outputFileSystem = mfs;
compiler.run((err, stats) => {
if (err || stats.hasErrors()) {
console.error("[Webpack failed to compile]:\n", err.details || stats.toString({ chunks: false, colors: true }));
}
else {
console.log("[Webpack compiled sucessfully]:\n", stats.toString({ chunks: false, colors: true }));
bundle = mfs.readFileSync("/bundle.js");
mfs.unlink("/bundle.js", () => { });
}
});
app.use('/public/bundle.js', (req, res, next) => {
const checkBundle = () => {
if (bundle.length == 0) {
setTimeout(checkBundle, 50); // delaying reply until bundle is compiled
} else {
res.write(bundle);
res.end();
}
}
checkBundle();
});
}
//=====================================================================
//due to scoping issue service worker must be served from root directory and not public
const serviceWorker = __dirname + '/public/js/sw.js';
app.get('/sw.js', (req, res) => {
res.sendFile(serviceWorker);
});
app.use('/api', APIHandler); // this makes calls to /api* redirect to our module in /src/handlers/api.js
app.use('/public', Express.static(__dirname + '/public')); // making 'public' directory public
app.get('*', (req, res) => {
const currentRoute = routes.map(r => ({ match: matchPath(req.url, r), route: r })).find(r => (r.match)); // getting current route
// checking if current route needs any data to be pre-fetched, if yes then do it:
const initialData = currentRoute && currentRoute.route.component.fetchData && currentRoute.route.component.fetchData(req, currentRoute.match.params);
Promise.all([initialData, verifySession(req, res, true)]).then(result => {
const context = { initialData: result[0], userID: result[1] };
const markup = template
.replace('<!--METADATA-->', generateMetaData(currentRoute.route.title(result[0], currentRoute.match.params), currentRoute.route.keywords(result[0], currentRoute.match.params), currentRoute.route.description(result[0], currentRoute.match.params)))
.replace("'<!--InitialData-->'", serialize(result[0]) + `; window.userID = ${result[1]}`) //injecting userID too for use in global store
.replace('<!--REACTSTRING-->', ReactDOMServer.renderToString(<StaticRouter location={req.url} context={context}><Route component={AppRoutes} /></StaticRouter>));
res.set('Content-Type', 'text/html');
res.write(markup);
res.end();
}).catch(er => {
res.send(`Something went wrong<br>URL:${req.url}<br>Error: ${er}`);
console.log(`Something went wrong while server side rendering, error: ${er}`, req.url);
});
});
app.listen(config.appPort, () => {
console.log('\x1b[36m', `node.ucp is now running and listening to port ${config.appPort}`, '\x1b[0m');
});