Skip to content
Open

SSR #22

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const path = require('path');
const params = require('express-route-params');
const config = require('./config');

import loader from './loader';

const app = express();
params(express);

Expand Down Expand Up @@ -208,4 +210,45 @@ function ensureAuthenticated(req, res, next) {
return next();
}

// SSR

app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
app.use(cookieParser());

// Set up homepage, static assets, and capture everything else
app.use(express.Router().get('/', loader));
app.use(express.static(path.resolve(__dirname, '../build')));
app.use(loader);

// We tell React Loadable to load all required assets and start listening - ROCK AND ROLL!
Loadable.preloadAll().then(() => {
app.listen(PORT, console.log(`App listening on port ${PORT}!`));
});

// Handle the bugs somehow
app.on('error', error => {
if (error.syscall !== 'listen') {
throw error;
}

const bind = typeof PORT === 'string' ? 'Pipe ' + PORT : 'Port ' + PORT;

switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});


app.listen(3001);
18 changes: 18 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const md5File = require('md5-file');
const path = require('path');

const ignoreStyles = require('ignore-styles');
const register = ignoreStyles.default;

require("@babel/polyfill");
require("@babel/register")({
ignore: [/\/(build|node_modules)\//],
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"@babel/plugin-syntax-dynamic-import",
"dynamic-import-node",
"react-loadable/babel"
]
});

require("./app.js");
96 changes: 96 additions & 0 deletions server/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import path from 'path';
import fs from 'fs';

import React from 'react';
import { renderToString } from 'react-dom/server';
import Helmet from 'react-helmet';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router';
import { Frontload, frontloadServerRender } from 'react-frontload';
import Loadable from 'react-loadable';


import createStore from '../src/store';
import ExchangeRate from '../src/components/pages/ExchangeRate'



export default (req, res) => {
const injectHTML = (data, { html, title, meta, body, scripts, state }) => {
data = data.replace('<html>', `<html ${html}>`);
data = data.replace(/<title>.*?<\/title>/g, title);
data = data.replace('</head>', `${meta}</head>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${body}</div><script>window.__PRELOADED_STATE__ = ${state}</script>${scripts.join('')}`
);

return data;
};

fs.readFile(
path.resolve(__dirname, '../build/index.html'),
'utf8',
(err, htmlData) => {
if (err) {
console.error('Read error', err);

return res.status(404).end();
}

const { store } = createStore(req.url);


const context = {};
const modules = [];

frontloadServerRender(() =>
renderToString(
<Loadable.Capture report={m => modules.push(m)}>
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<Frontload isServer={true}>
<ExchangeRate />
</Frontload>
</StaticRouter>
</Provider>
</Loadable.Capture>
)
).then(routeMarkup => {
if (context.url) {
res.writeHead(302, {
Location: context.url
});

res.end();
} else {

const extractAssets = (assets, chunks) =>
Object.keys(assets)
.filter(asset => chunks.indexOf(asset.replace('.js', '')) > -1)
.map(k => assets[k]);

const extraChunks = extractAssets(manifest, modules).map(
c => `<script type="text/javascript" src="/${c.replace(/^\//, '')}"></script>`
);


const helmet = Helmet.renderStatic();

console.log('THE TITLE', helmet.title.toString());

const html = injectHTML(htmlData, {
html: helmet.htmlAttributes.toString(),
title: helmet.title.toString(),
meta: helmet.meta.toString(),
body: routeMarkup,
scripts: extraChunks,
state: JSON.stringify(store.getState()).replace(/</g, '\\u003c')
});

res.send(html);
}
});
}
);
};