Skip to content

Instantly share code, notes, and snippets.

@gbozee
Last active December 14, 2017 16:57
Show Gist options
  • Save gbozee/2a4bba0633986027dc4d24391d63bf86 to your computer and use it in GitHub Desktop.
Save gbozee/2a4bba0633986027dc4d24391d63bf86 to your computer and use it in GitHub Desktop.
Preparing Server Code.
$ yarn add --dev babel-cli # to compile our node application 
$ yarn add --dev babel-plugin-dynamic-import-webpack # To ensure server webpack config understands the `import` provided by webpack
$ yarn add --dev babel-plugin-transform-object-rest-spread # ability to use the compile the spread operator in Node
$ yarn add --dev babel-preset-env # default preset for babel when compiling
$ yarn add --dev babel-preset-react # ensure that babel understands react from the server
$ yarn add --dev file-loader url-loader webpack-node-externals # webpack specific loaders to be used by the server.
$ yarn add module-alias # Since we are swapping out React for Preact in production, does the job of webpack resolve alias.
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"express": "^4.16.2",
"loadable-components": "^0.4.0",
"module-alias": "^2.0.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-loadable": "^5.3.1",
"react-router-dom": "^4.2.2",
"react-scripts": "1.0.17",
"styled-components": "^2.2.4"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject",
"build:server": "webpack",
"server": "node build/server.js"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-dynamic-import-webpack": "^1.0.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"dynamic-cdn-webpack-plugin": "^3.4.1",
"file-loader": "^1.1.5",
"json-loader": "^0.5.7",
"module-to-cdn": "^3.1.1",
"preact": "^8.2.7",
"preact-compat": "^3.17.0",
"preload-webpack-plugin": "^2.0.0",
"react-app-rewire-preact": "^1.0.1",
"react-app-rewire-styled-components": "^3.0.0",
"react-app-rewired": "^1.3.8",
"url-loader": "^0.6.2",
"webpack-node-externals": "^1.6.0"
},
"_moduleAliases": {
"react": "preact-compat",
"react-dom": "preact-compat"
}
}
require('module-alias/register'); //aliasing react to preact. Reads the information from the "_moduleAliases" section in the `package.json`
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import {ServerStyleSheet} from 'styled-components'
import { StaticRouter } from 'react-router-dom'; // Using the static router provided by react router in SSR
import App from './App'; // Our react application
import Loadable from "react-loadable";
import Routes from './routes'
import { getBundles } from 'react-loadable/webpack'
import stats from './react-loadable.json';
/*The html in this function is actually the html generated by create react app after running `yarn build`.
For some reasons, I couldn't automatically generate the bundles dynamically as i ended up with an empty module
as described here https://github.com/thejameskyle/react-loadable#------------server-side-rendering.
but the Below works. Its just a bit annoying since every time we build our create react app application, we need to
remember to update this function with the latest output.*/
// render is used to inject html in a globale template
function render(html, css, bundles){
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href="/favicon.ico">
<title>React App</title>
${css}
<link rel="prefetch" href="/static/js/0.17e10159.chunk.js">
<link rel="prefetch" href="/static/js/1.392e12f1.chunk.js">
</head>
<body>
<div id="root">${html}</div>
<script type="text/javascript" src="https://unpkg.com/@umds/[email protected]/object-assign.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react-router-dom.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script type="text/javascript" src="/static/js/main.68d442dc.js"></script>
${bundles.map(bundle => {
return `<script src="${bundle.file}"></script>`
}).join('\n')}
</body>
</html>
`
}
const app = express();
// Serve client.js and vendor.js
app.use('/static', express.static(__dirname + '/static')); //Using the same directory that contains the static files build by cra
let modules = [];
app.get('*', (req, res) => {
const context = {};
const appWithRouter = (
<Loadable.Capture report={moduleName => modules.push(moduleName)}> // Doesn't seem to work yet.
<StaticRouter location={req.url} context={context}>
<Routes />
</StaticRouter>
</Loadable.Capture>
);
if (context.url) {
res.redirect(context.url);
return;
}
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToString(sheet.collectStyles(appWithRouter)) // our react application as a string
const styleTags = sheet.getStyleTags() // styles from styled-components
console.log(modules)
let bundles = getBundles(stats, modules); // Doesn't work yet and always end up with an empty module.
res.status(200).send(render(html, styleTags, bundles));
});
/*Ensure that all modules are preloaded before the server starts.
Only one instance is required in the application else it fails.*/
Loadable.preloadAll().then(() => {
app.listen(3000, () => console.log('Demo app listening on port 3000'));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment