Skip to content

Instantly share code, notes, and snippets.

@birkir
Last active March 6, 2018 22:54
Show Gist options
  • Save birkir/ce07f3fb262a149f6892854f6af50280 to your computer and use it in GitHub Desktop.
Save birkir/ce07f3fb262a149f6892854f6af50280 to your computer and use it in GitHub Desktop.
Server side rendering create-react-app
From d5dd3de72a08bc671e33c484f9c1854dc817e20a Mon Sep 17 00:00:00 2001
From: Birkir Gudjonsson <birkir.gudjonsson@gmail.com>
Date: Tue, 6 Mar 2018 17:39:46 -0500
Subject: [PATCH 1/1] Server-side rendering create-react-app
---
package.json | 11 +++
patches/react-dev-utils+6.0.0-next.47d2d941.patch | 14 ++++
patches/react-scripts+2.0.0-next.47d2d941.patch | 24 +++++++
server/app.js | 42 +++++++++++
server/bin/index.js | 32 +++++++++
server/html.js | 48 +++++++++++++
server/index.js | 49 +++++++++++++
webpack.config.dev.js | 85 +++++++++++++++++++++++
webpack.config.prod.js | 69 ++++++++++++++++++
yarn.lock | 73 +++++++++++++++++--
10 files changed, 442 insertions(+), 5 deletions(-)
create mode 100644 patches/react-dev-utils+6.0.0-next.47d2d941.patch
create mode 100644 patches/react-scripts+2.0.0-next.47d2d941.patch
create mode 100644 server/app.js
create mode 100644 server/bin/index.js
create mode 100644 server/html.js
create mode 100644 server/index.js
create mode 100644 webpack.config.dev.js
create mode 100644 webpack.config.prod.js
diff --git a/package.json b/package.json
index fc92d9b..7281469 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,17 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "express": "^4.16.2",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "2.0.0-next.47d2d941"
},
"scripts": {
+ "prepare": "patch-package",
+ "dev": "node server/bin",
+ "build:server": "webpack --config webpack.config.prod.js",
+ "build:all": "yarn build; yarn build:server",
+ "start:build": "node build/server.js",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
@@ -25,5 +31,10 @@
"Firefox ESR",
"not ie < 11"
]
+ },
+ "devDependencies": {
+ "node-hot-loader": "^1.6.0",
+ "patch-package": "^5.1.1",
+ "postinstall-prepare": "^1.0.1"
}
}
diff --git a/patches/react-dev-utils+6.0.0-next.47d2d941.patch b/patches/react-dev-utils+6.0.0-next.47d2d941.patch
new file mode 100644
index 0000000..678b40e
--- /dev/null
+++ b/patches/react-dev-utils+6.0.0-next.47d2d941.patch
@@ -0,0 +1,14 @@
+patch-package
+--- a/node_modules/react-dev-utils/webpackHotDevClient.js
++++ b/node_modules/react-dev-utils/webpackHotDevClient.js
+@@ -61,8 +61,8 @@ if (module.hot && typeof module.hot.dispose === 'function') {
+ var connection = new SockJS(
+ url.format({
+ protocol: window.location.protocol,
+- hostname: window.location.hostname,
+- port: window.location.port,
++ hostname: window.__devServerHostname || window.location.hostname,
++ port: window.__devServerPort || window.location.port,
+ // Hardcoded in WebpackDevServer
+ pathname: '/sockjs-node',
+ })
diff --git a/patches/react-scripts+2.0.0-next.47d2d941.patch b/patches/react-scripts+2.0.0-next.47d2d941.patch
new file mode 100644
index 0000000..7701610
--- /dev/null
+++ b/patches/react-scripts+2.0.0-next.47d2d941.patch
@@ -0,0 +1,24 @@
+patch-package
+--- a/node_modules/react-scripts/config/webpack.config.dev.js
++++ b/node_modules/react-scripts/config/webpack.config.dev.js
+@@ -22,7 +22,7 @@ const paths = require('./paths');
+
+ // Webpack uses `publicPath` to determine where the app is being served from.
+ // In development, we always serve from the root. This makes config easier.
+-const publicPath = '/';
++const publicPath = process.env.PUBLIC_PATH || '/';
+ // `publicUrl` is just like `publicPath`, but we will provide it to our app
+ // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
+ // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
+--- a/node_modules/react-scripts/config/webpackDevServer.config.js
++++ b/node_modules/react-scripts/config/webpackDevServer.config.js
+@@ -39,6 +39,9 @@ module.exports = function(proxy, allowedHost) {
+ !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
+ // Enable gzip compression of generated files.
+ compress: true,
++ headers: {
++ 'Access-Control-Allow-Origin': 'http://localhost:' + (process.env.LOCAL_PORT || 3000),
++ },
+ // Silence WebpackDevServer's own logs since they're generally not useful.
+ // It will still show compile warnings and errors with this setting.
+ clientLogLevel: 'none',
diff --git a/server/app.js b/server/app.js
new file mode 100644
index 0000000..7583221
--- /dev/null
+++ b/server/app.js
@@ -0,0 +1,42 @@
+import fs from 'fs';
+import React from 'react';
+import ReactDOMServer from 'react-dom/server';
+import express from 'express';
+import Html from './html';
+import App from '../src/App';
+import '../src/index.css';
+
+// Check if server was started with node-hot
+const isNodeHot = !!Array.from(process.argv)
+ .find(argument => argument.indexOf('node_modules/.bin/node-hot') >= 0);
+
+// Create express app
+const app = express();
+
+let manifest = {};
+fs.readFile('./build/asset-manifest.json', 'utf8', (err, contents) => {
+ if (!err) {
+ manifest = JSON.parse(contents);
+ }
+});
+
+// Serve built content
+app.use('/static', express.static('./build/static'));
+
+// Serve react app
+app.get('/', (req, res) => {
+
+ // Write doctype to response
+ res.write('<!DOCTYPE html>');
+
+ // Stream react to response
+ ReactDOMServer.renderToNodeStream(
+ <Html manifest={manifest} devServerPort={process.env.REMOTE_PORT}>
+ <App />
+ </Html>,
+ )
+ .pipe(res);
+});
+
+export default app;
+
diff --git a/server/bin/index.js b/server/bin/index.js
new file mode 100644
index 0000000..d9d9bef
--- /dev/null
+++ b/server/bin/index.js
@@ -0,0 +1,32 @@
+const spawn = require('child_process').spawn;
+const { choosePort } = require('react-dev-utils/WebpackDevServerUtils');
+const detect = require('detect-port-alt');
+
+const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
+const HOST = process.env.HOST || '0.0.0.0';
+
+// Start our server on default port
+choosePort(HOST, DEFAULT_PORT)
+.then(port => {
+ detect(port + 1)
+ .then(devServerPort => {
+
+ const serverEnv = Object.create(process.env);
+ serverEnv.PORT = port;
+ serverEnv.REMOTE_PORT = devServerPort;
+ const server = spawn('./node_modules/.bin/node-hot', ['--config', 'webpack.config.dev.js'], { stdio: 'inherit', env: serverEnv });
+
+ const devServerEnv = Object.create(process.env);
+ devServerEnv.PORT = devServerPort;
+ devServerEnv.LOCAL_PORT = port;
+ devServerEnv.BROWSER = 'none';
+ devServerEnv.PUBLIC_PATH = `//localhost:${devServerPort}/`;
+ const devServer = spawn('./node_modules/.bin/react-scripts', ['start'], { stdio: 'inherit', env: devServerEnv });
+
+ process.on('SIGTERM', () => {
+ if (server) server.kill();
+ if (devServer) devServer.kill();
+ process.exit(0);
+ });
+ });
+});
diff --git a/server/html.js b/server/html.js
new file mode 100644
index 0000000..53e6644
--- /dev/null
+++ b/server/html.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default function Html({ children, manifest, devServerPort }) {
+
+ const header = [];
+ const footer = [];
+ const isProd = process.env.NODE_ENV === 'production';
+
+ if (manifest['main.css']) {
+ header.push(
+ <link key="css" rel="stylesheet" href={`/${manifest['main.css']}`} />
+ );
+ }
+
+ if (isProd) {
+ footer.push(
+ <script key="js" src={manifest['main.js']} />
+ );
+ } else {
+ header.push(
+ <script key="port" dangerouslySetInnerHTML={{ __html: `window.__devServerPort = ${devServerPort};` }} />
+ );
+ footer.push(
+ <script key="js" src={`http://localhost:${devServerPort}/static/js/bundle.js`} />
+ );
+ }
+
+ return (
+ <html lang="en">
+ <head>
+ <meta charSet="utf-8" />
+ <title>App</title>
+ {header}
+ </head>
+ <body>
+ <div id="root">{children}</div>
+ {footer}
+ </body>
+ </html>
+ );
+}
+
+Html.propTypes = {
+ children: PropTypes.node.isRequired,
+ manifest: PropTypes.object,
+ devServerPort: PropTypes.string.isRequired,
+};
diff --git a/server/index.js b/server/index.js
new file mode 100644
index 0000000..3eb7c55
--- /dev/null
+++ b/server/index.js
@@ -0,0 +1,49 @@
+/* eslint-disable no-console */
+import app from './app';
+
+const port = process.env.PORT || 3000;
+
+// Start server
+const server = app.listen(port, (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+ console.log(`\n\nStarted server on http://localhost:${port} 🦄\nPress Ctrl-C to stop.\n`);
+});
+
+// Check for hot-module-reload support
+if (module.hot) {
+
+ // Store current app
+ let currentApp = app;
+
+ module.hot.accept('./app', () => {
+
+ // Stop listening for requests
+ server.removeListener('request', currentApp);
+ // Import newly built app
+ import('./app').then(({ default: newApp }) => {
+
+ // Start listening on new app
+ server.on('request', newApp);
+ currentApp = newApp;
+
+ console.log('[🦄 ] Server was hot-reloaded successfully!');
+ })
+ .catch((err) => {
+ console.log('[🦄 ] Failed to hot-reload server!');
+ console.error(err);
+ });
+ });
+
+ // Accept this file too (will restart server)
+ module.hot.accept();
+
+ // Make sure to close connection
+ module.hot.dispose(() => {
+ console.log('[🦄 ] Closing all connections to server.');
+ server.close();
+ });
+}
+
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
new file mode 100644
index 0000000..37a91fe
--- /dev/null
+++ b/webpack.config.dev.js
@@ -0,0 +1,85 @@
+process.env.BABEL_ENV = 'development';
+process.env.NODE_ENV = 'development';
+
+const path = require('path');
+const config = require('react-scripts/config/webpack.config.dev');
+const webpack = require('webpack');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
+const ManifestPlugin = require('webpack-manifest-plugin');
+const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+const isStyleLoader = loader => loader && loader.indexOf('style-loader') >= 0;
+const isCssLoader = loader => loader.loader && loader.loader.indexOf('css-loader') >= 0;
+const findInRule = pattern => rule => (rule.test instanceof Array ? rule.test : [rule.test]).find(n => n && n.test(pattern));
+const removePlugin = pl => {
+ const pluginIndex = config.plugins.findIndex(p => p instanceof pl);
+ if (pluginIndex >= 0) {
+ config.plugins.splice(pluginIndex, 1);
+ }
+};
+
+const cssFilename = 'static/css/[name].[contenthash:8].css';
+const preIndex = config.module.rules.findIndex(r => r.enforce === 'pre');
+config.module.rules.splice(preIndex, 1);
+
+const mainRule = config.module.rules.find(n => n.hasOwnProperty('oneOf'));
+const jsRules = mainRule.oneOf.filter(findInRule('foo.js'));
+const cssRules = mainRule.oneOf.filter(findInRule('foo.module.css'));
+
+// First JS Rule
+jsRules[0].include.push(path.join(__dirname, 'server'));
+// Babel
+jsRules[0].use[1].options.plugins.splice(0, 0, require.resolve('babel-plugin-transform-dynamic-import'));
+
+
+const et = new ExtractTextPlugin({
+ filename: cssFilename,
+ allChunks: true,
+});
+
+// All CSS rules
+cssRules.forEach((rule, i) => {
+ // Change css-loader
+ // rule.use.find(isCssLoader).loader = require.resolve('css-loader/locals');
+ // Remove style-loader
+ rule.use.splice(rule.use.findIndex(isStyleLoader), 1);
+ rule.use = et.extract(rule.use);
+});
+
+removePlugin(SWPrecacheWebpackPlugin);
+removePlugin(webpack.DefinePlugin);
+removePlugin(InterpolateHtmlPlugin);
+removePlugin(HtmlWebpackPlugin);
+removePlugin(UglifyJsPlugin);
+
+config.plugins.push(
+ et,
+ new ManifestPlugin({
+ fileName: 'asset-manifest.json',
+ }),
+);
+
+config.target = 'node';
+
+config.entry = [
+ path.join(__dirname, 'server', 'index.js'),
+];
+
+config.output = {
+ path: path.join(__dirname, 'build'),
+ filename: 'server.js',
+ publicPath: '/',
+};
+
+delete config.node;
+
+// Inspect generated config
+// ------------------------
+//
+// const util = require('util');
+// console.log(util.inspect(config, {showHidden: false, depth: null}));
+
+module.exports = config;
\ No newline at end of file
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
new file mode 100644
index 0000000..237293e
--- /dev/null
+++ b/webpack.config.prod.js
@@ -0,0 +1,69 @@
+process.env.BABEL_ENV = 'production';
+process.env.NODE_ENV = 'production';
+
+const path = require('path');
+const config = require('react-scripts/config/webpack.config.prod');
+const webpack = require('webpack');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
+const ManifestPlugin = require('webpack-manifest-plugin');
+const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
+
+const isStyleLoader = loader => loader.loader.indexOf('style-loader') >= 0;
+const isCssLoader = loader => loader.loader.indexOf('css-loader') >= 0;
+const findInRule = pattern => rule => (rule.test instanceof Array ? rule.test : [rule.test]).find(n => n && n.test(pattern));
+const removePlugin = pl => {
+ const pluginIndex = config.plugins.findIndex(p => p instanceof pl);
+ if (pluginIndex >= 0) {
+ config.plugins.splice(pluginIndex, 1);
+ }
+};
+
+const preIndex = config.module.rules.findIndex(r => r.enforce === 'pre');
+config.module.rules.splice(preIndex, 1);
+
+const mainRule = config.module.rules.find(n => n.hasOwnProperty('oneOf'));
+const jsRules = mainRule.oneOf.filter(findInRule('foo.js'));
+const cssRules = mainRule.oneOf.filter(findInRule('foo.module.css'));
+
+// First JS Rule
+jsRules[0].include.push(path.join(__dirname, 'server'));
+// Babel
+jsRules[0].use[1].options.plugins.splice(0, 0, require.resolve('babel-plugin-transform-dynamic-import'));
+
+// All CSS rules
+cssRules.forEach(rule => {
+ // Change css-loader
+ rule.loader.find(isCssLoader).loader = require.resolve('css-loader/locals');
+ // Remove style-loader
+ rule.loader.splice(rule.loader.findIndex(isStyleLoader), 1);
+});
+
+removePlugin(SWPrecacheWebpackPlugin);
+removePlugin(InterpolateHtmlPlugin);
+removePlugin(HtmlWebpackPlugin);
+removePlugin(UglifyJsPlugin);
+removePlugin(ManifestPlugin);
+
+config.target = 'node';
+
+config.entry = [
+ path.join(__dirname, 'server', 'index.js'),
+];
+
+config.output = {
+ path: path.join(__dirname, 'build'),
+ filename: 'server.js',
+ publicPath: '/',
+};
+
+delete config.node;
+
+// Inspect generated config
+// ------------------------
+//
+// const util = require('util');
+// console.log(util.inspect(config, {showHidden: false, depth: null}));
+
+module.exports = config;
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 8daa656..52c87a0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3694,6 +3694,14 @@ fs-extra@^0.30.0:
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
+fs-extra@^4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@@ -5686,6 +5694,15 @@ node-forge@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
+node-hot-loader@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/node-hot-loader/-/node-hot-loader-1.6.0.tgz#031a902543452c3b5c69d2643420e0bfee2e1741"
+ dependencies:
+ babel-register "^6.26.0"
+ path-is-absolute "^1.0.1"
+ source-map-support "^0.5.3"
+ yargs "^11.0.0"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -5948,7 +5965,7 @@ os-locale@^2.0.0:
lcid "^1.0.0"
mem "^1.1.0"
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
@@ -6071,6 +6088,19 @@ pascalcase@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
+patch-package@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-5.1.1.tgz#e5e82fe08bed760b773b8eb73a7bcb7c1634f802"
+ dependencies:
+ chalk "^1.1.3"
+ cross-spawn "^5.1.0"
+ fs-extra "^4.0.1"
+ minimist "^1.2.0"
+ rimraf "^2.6.1"
+ slash "^1.0.0"
+ tmp "^0.0.31"
+ update-notifier "^2.2.0"
+
path-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
@@ -6493,6 +6523,10 @@ postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.16:
source-map "^0.6.1"
supports-color "^5.2.0"
+postinstall-prepare@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/postinstall-prepare/-/postinstall-prepare-1.0.1.tgz#dac9b5d91b054389141b13c0192eb68a0aa002b5"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -6727,7 +6761,7 @@ react-dev-utils@6.0.0-next.47d2d941:
strip-ansi "4.0.0"
text-table "0.2.0"
-react-dom@16.2.0:
+react-dom@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
dependencies:
@@ -6794,7 +6828,7 @@ react-scripts@2.0.0-next.47d2d941:
optionalDependencies:
fsevents "1.1.3"
-react@16.2.0:
+react@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
dependencies:
@@ -7486,7 +7520,7 @@ source-map-support@^0.4.15:
dependencies:
source-map "^0.5.6"
-source-map-support@^0.5.0:
+source-map-support@^0.5.0, source-map-support@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76"
dependencies:
@@ -7951,6 +7985,12 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
+tmp@^0.0.31:
+ version "0.0.31"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
+ dependencies:
+ os-tmpdir "~1.0.1"
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -8193,7 +8233,7 @@ upath@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d"
-update-notifier@^2.3.0:
+update-notifier@^2.2.0, update-notifier@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451"
dependencies:
@@ -8608,6 +8648,12 @@ yargs-parser@^8.1.0:
dependencies:
camelcase "^4.1.0"
+yargs-parser@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
+ dependencies:
+ camelcase "^4.1.0"
+
yargs@6.6.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
@@ -8643,6 +8689,23 @@ yargs@^10.0.3:
y18n "^3.2.1"
yargs-parser "^8.1.0"
+yargs@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"
+ dependencies:
+ cliui "^4.0.0"
+ decamelize "^1.1.1"
+ find-up "^2.1.0"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^9.0.2"
+
yargs@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
--
2.16.1
@birkir
Copy link
Author

birkir commented Mar 6, 2018

Here is how you can apply this masterpiece...

create-react-app --scripts-version 2.0.0-next.47d2d941 my-app
cd my-app
curl -Ls https://goo.gl/r6yCvT | git apply --ignore-whitespace
yarn install

Commands:

  • yarn dev - Starts development server (hot reloads express)
  • yarn build:server - Build server
  • yarn build:all - Build server and client
  • yarn start:build - Start built express server

@birkir
Copy link
Author

birkir commented Mar 6, 2018

Also you can add HMR to client in ./src/index.js.

// Add HMR
if (module.hot) {
  module.hot.accept('./App', () => {
    const NextApp = require('./App').default;
    ReactDOM.render(<NextApp />, document.getElementById('root'));
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment