$ 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.
Last active
December 14, 2017 16:57
-
-
Save gbozee/2a4bba0633986027dc4d24391d63bf86 to your computer and use it in GitHub Desktop.
Preparing Server Code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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