Last active
March 6, 2018 22:54
-
-
Save birkir/ce07f3fb262a149f6892854f6af50280 to your computer and use it in GitHub Desktop.
Server side rendering create-react-app
This file contains 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
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 |
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
Here is how you can apply this masterpiece...
Commands:
yarn dev
- Starts development server (hot reloads express)yarn build:server
- Build serveryarn build:all
- Build server and clientyarn start:build
- Start built express server