feat(v2): proper realtime hot reload with react-hot-loader (#1336)

* feat(v2): proper realtime hot reload with react-hot-loader

* nits

* nits again
This commit is contained in:
Endilie Yacop Sucipto 2019-04-04 22:24:20 +07:00 committed by Yangshun Tay
parent 94a4ef55c0
commit 50bbc1dcd7
8 changed files with 101 additions and 15 deletions

View file

@ -80,9 +80,18 @@ program
.option('-p, --port <port>', 'use specified port (default: 3000)')
.option('-h, --host <host>', 'use specified host (default: localhost')
.option('-nw, --no-watch <noWatch>', 'disable live reload (default: false)')
.option(
'--hot-only',
'Do not fallback to page refresh if hot reload fails (default: false)',
)
.option('--no-cache-loader', 'Do not use cache-loader')
.action((siteDir = '.', {port, noWatch, cacheLoader}) => {
wrapCommand(start)(path.resolve(siteDir), {port, noWatch, cacheLoader});
.action((siteDir = '.', {port, noWatch, hotOnly, cacheLoader}) => {
wrapCommand(start)(path.resolve(siteDir), {
port,
noWatch,
hotOnly,
cacheLoader,
});
});
program.parse(process.argv);

View file

@ -7,6 +7,7 @@
const _ = require('lodash');
const path = require('path');
const express = require('express');
const chalk = require('chalk');
const webpack = require('webpack');
const chokidar = require('chokidar');
@ -76,8 +77,6 @@ module.exports = async function start(siteDir, cliOptions = {}) {
let config = createClientConfig(props);
const {siteConfig, plugins = []} = props;
// Needed for hot reload.
config.plugin('hmr').use(HotModuleReplacementPlugin);
config.plugin('html-webpack-plugin').use(HtmlWebpackPlugin, [
{
inject: false,
@ -90,6 +89,8 @@ module.exports = async function start(siteDir, cliOptions = {}) {
title: siteConfig.title,
},
]);
// Needed for hot reload.
config.plugin('hmr').use(HotModuleReplacementPlugin);
config = config.toConfig();
// Plugin lifecycle - configureWebpack
@ -105,6 +106,9 @@ module.exports = async function start(siteDir, cliOptions = {}) {
compress: true,
clientLogLevel: 'error',
hot: true,
// Do not fallback to page refresh if hot reload fails
// https://webpack.js.org/configuration/dev-server/#devserverhotonly
hotOnly: cliOptions.hotOnly,
quiet: true,
headers: {
'access-control-allow-origin': '*', // Needed for CORS.
@ -119,7 +123,17 @@ module.exports = async function start(siteDir, cliOptions = {}) {
disableHostCheck: true,
overlay: false,
host,
contentBase: path.resolve(siteDir, 'static'),
// https://webpack.js.org/configuration/dev-server/#devserverbefore
// eslint-disable-next-line
before(app, server) {
app.use(baseUrl, express.static(path.resolve(siteDir, 'static')));
// TODO: add plugins beforeDevServer hook
},
// https://webpack.js.org/configuration/dev-server/#devserverbefore
// eslint-disable-next-line
after(app, server) {
// TODO: add plugins afterDevServer hook
},
};
WebpackDevServer.addDevServerEntrypoints(config, devServerConfig);
const compiler = webpack(config);

View file

@ -7,6 +7,7 @@
import React from 'react';
import {hydrate, render} from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import {BrowserRouter} from 'react-router-dom';
import App from './App';
@ -21,10 +22,19 @@ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
const renderMethod = process.env.NODE_ENV === 'production' ? hydrate : render;
preload(routes, window.location.pathname).then(() => {
renderMethod(
<BrowserRouter>
<App />
</BrowserRouter>,
<AppContainer>
<BrowserRouter>
<App />
</BrowserRouter>
</AppContainer>,
document.getElementById('__docusaurus'),
);
});
// Webpack Hot Module Replacement API
if (module.hot) {
// Self-accepting method/ trick
// (https://github.com/webpack/webpack-dev-server/issues/100#issuecomment-290911036)
module.hot.accept();
}
}

View file

@ -13,7 +13,7 @@ import {matchRoutes} from 'react-router-config';
*
* @param {Array<RouteConfig>} routes react-router-config
* @param {string} pathname the route pathname, example: /docs/installation
* @returns {Promise} Promise object represents whether pathname
* @returns {Promise} Promise object represents whether pathname has been preloaded
*/
export default function preload(routes, pathname) {
const matches = matchRoutes(routes, pathname);

View file

@ -105,6 +105,7 @@ module.exports = function createBaseConfig(props, isServer) {
configFile: false,
presets: ['@babel/env', '@babel/react'],
plugins: [
'react-hot-loader/babel', // To enable react-hot-loader
isServer ? 'dynamic-import-node' : '@babel/syntax-dynamic-import',
'react-loadable/babel',
],

View file

@ -19,6 +19,12 @@ module.exports = function createClientConfig(props) {
const config = createBaseConfig(props);
config.entry('main').add(path.resolve(__dirname, '../core/clientEntry.js'));
// https://github.com/gaearon/react-hot-loader#react--dom
// To enable react-hot-loader in development
if (!isProd) {
config.resolve.alias.set('react-dom', '@hot-loader/react-dom').end();
}
const {generatedFilesDir} = props;
// Write webpack stats object so we can pickup correct client bundle path in server.
config

View file

@ -32,6 +32,7 @@
"@babel/preset-env": "^7.4.2",
"@babel/preset-react": "^7.0.0",
"@docusaurus/utils": "^1.0.0",
"@hot-loader/react-dom": "^16.8.4",
"@mapbox/rehype-prism": "^0.3.1",
"@mdx-js/mdx": "^0.20.3",
"@mdx-js/tag": "^0.20.3",
@ -47,6 +48,7 @@
"css-loader": "^1.0.0",
"docsearch.js": "^2.5.2",
"ejs": "^2.6.1",
"express": "^4.16.4",
"front-matter": "^3.0.1",
"fs-extra": "^7.0.0",
"globby": "^9.1.0",
@ -60,6 +62,7 @@
"prismjs": "^1.16.0",
"react-dev-utils": "^8.0.0",
"react-helmet": "^5.2.0",
"react-hot-loader": "^4.8.2",
"react-listener-provider": "^0.2.0",
"react-loadable": "^5.5.0",
"react-perimeter": "^0.4.0",

View file

@ -799,6 +799,16 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@hot-loader/react-dom@^16.8.4":
version "16.8.6"
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.8.6.tgz#7923ba27db1563a7cc48d4e0b2879a140df461ea"
integrity sha512-+JHIYh33FVglJYZAUtRjfT5qZoT2mueJGNzU5weS2CVw26BgbxGKSujlJhO85BaRbg8sqNWyW1hYBILgK3ZCgA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.13.6"
"@jest/console@^24.3.0":
version "24.3.0"
resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.3.0.tgz#7bd920d250988ba0bf1352c4493a48e1cb97671e"
@ -5242,7 +5252,7 @@ expect@^24.5.0:
jest-message-util "^24.5.0"
jest-regex-util "^24.3.0"
express@^4.15.3, express@^4.16.2, express@^4.16.3:
express@^4.15.3, express@^4.16.2, express@^4.16.3, express@^4.16.4:
version "4.16.4"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
@ -5382,7 +5392,7 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
fast-levenshtein@~2.0.4:
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
@ -6027,7 +6037,7 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@^4.3.2:
global@^4.3.0, global@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=
@ -6420,7 +6430,7 @@ hogan.js@^3.0.2:
mkdirp "0.3.0"
nopt "1.0.10"
hoist-non-react-statics@^3.1.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
@ -10527,7 +10537,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@^15.5.0, prop-types@^15.5.3, prop-types@^15.5.4, prop-types@^15.6.2:
prop-types@^15.5.0, prop-types@^15.5.3, prop-types@^15.5.4, prop-types@^15.6.1, prop-types@^15.6.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -10834,6 +10844,21 @@ react-helmet@^5.2.0:
prop-types "^15.5.4"
react-side-effect "^1.1.0"
react-hot-loader@^4.8.2:
version "4.8.2"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.8.2.tgz#21a00cfca7fc848c53d78c34f6081f5ae3737dda"
integrity sha512-W5I8ps/32q5zL0mKfGGdPgsZfgljs/tdCTYxM6P1N8GV4+rUAu4g6ysy//5/jJpAFM0Bpgr6HrVLWK9h0jMdrA==
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
hoist-non-react-statics "^3.3.0"
loader-utils "^1.1.0"
lodash "^4.17.11"
prop-types "^15.6.1"
react-lifecycles-compat "^3.0.4"
shallowequal "^1.0.2"
source-map "^0.7.3"
react-is@^16.6.0, react-is@^16.7.0:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
@ -10844,6 +10869,11 @@ react-is@^16.8.1, react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.5.tgz#c54ac229dd66b5afe0de5acbe47647c3da692ff8"
integrity sha512-sudt2uq5P/2TznPV4Wtdi+Lnq3yaYW8LfvPKLM9BKD8jJNBkxMVyB0C9/GmVhLw7Jbdmndk/73n7XQGeN9A3QQ==
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-listener-provider@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/react-listener-provider/-/react-listener-provider-0.2.0.tgz#fb6ce123f9e20ad948f75906ddf647db1df31f18"
@ -11569,6 +11599,14 @@ scheduler@^0.13.5:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.13.6:
version "0.13.6"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889"
integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^0.4.5:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
@ -11747,7 +11785,7 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
shallowequal@^1.0.1:
shallowequal@^1.0.1, shallowequal@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
@ -12018,6 +12056,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
space-separated-tokens@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"