Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental react-refresh support #8582

Merged
merged 11 commits into from
Apr 6, 2020
1 change: 1 addition & 0 deletions docusaurus/docs/advanced-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ You can adjust various development and production settings by setting environmen
| INLINE_RUNTIME_CHUNK | 🚫 Ignored | ✅ Used | By default, Create React App will embed the runtime script into `index.html` during the production build. When set to `false`, the script will not be embedded and will be imported as usual. This is normally required when dealing with CSP. |
| IMAGE_INLINE_SIZE_LIMIT | 🚫 Ignored | ✅ Used | By default, images smaller than 10,000 bytes are encoded as a data URI in base64 and inlined in the CSS or JS build artifact. Set this to control the size limit in bytes. Setting it to 0 will disable the inlining of images. |
| EXTEND_ESLINT | ✅ Used | ✅ Used | When set to `true`, user provided ESLint configs will be used by `eslint-loader`. Note that any rules set to `"error"` will stop the application from building. |
| FAST_REFRESH | ✅ Used | 🚫 Ignored | When set to `true`, enables experimental support for fast refresh to allow you to tweak your components in real time without reloading the page. |
| TSC_COMPILE_ON_ERROR | ✅ Used | ✅ Used | When set to `true`, you can run and properly build TypeScript projects even if there are TypeScript type check errors. These errors are printed as warnings in the terminal and/or browser console. |
5 changes: 4 additions & 1 deletion packages/react-dev-utils/webpackHotDevClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ function tryApplyUpdates(onHotUpdateSuccess) {
}

function handleApplyUpdates(err, updatedModules) {
if (err || !updatedModules || hadRuntimeError) {
const hasReactRefresh = process.env.FAST_REFRESH;
const wantsForcedReload = err || !updatedModules || hadRuntimeError;
// React refresh can handle hot-reloading over errors.
if (!hasReactRefresh && wantsForcedReload) {
window.location.reload();
return;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/react-scripts/config/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ function getClientEnvironment(publicUrl) {
WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
// Whether or not react-refresh is enabled.
// react-refresh is not 100% stable at this time,
// which is why it's disabled by default.
// It is defined here so it is available in the webpackHotDevClient.
FAST_REFRESH: process.env.FAST_REFRESH || false,
}
);
// Stringify all values so we can feed into webpack DefinePlugin
Expand Down
22 changes: 22 additions & 0 deletions packages/react-scripts/config/hotRefreshOverlayModuleStub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// This file is currently required because the error overlay has a bug preventing the its outright disabling.

'use strict';

function handleRuntimeError() {
// Stubbed out due to a bug.
}
function clearRuntimeErrors() {
// Stubbed out due to a bug.
}

module.exports = {
handleRuntimeError,
clearRuntimeErrors,
};
31 changes: 28 additions & 3 deletions packages/react-scripts/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// @remove-on-eject-begin
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end
Expand All @@ -41,6 +42,11 @@ const appPackageJson = require(paths.appPackageJson);

// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';

const webpackDevClientEntry = require.resolve(
'react-dev-utils/webpackHotDevClient'
);

// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
Expand Down Expand Up @@ -77,6 +83,8 @@ module.exports = function(webpackEnv) {
// Get environment variables to inject into our app.
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));

const shouldUseReactRefresh = env.raw.FAST_REFRESH;

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
Expand Down Expand Up @@ -158,10 +166,13 @@ module.exports = function(webpackEnv) {
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
//
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
//
// When using the experimental react-refresh integration,
// the webpack plugin takes care of injecting the dev client for us.
isEnvDevelopment && !shouldUseReactRefresh && webpackDevClientEntry,
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
Expand Down Expand Up @@ -419,7 +430,10 @@ module.exports = function(webpackEnv) {
},
},
},
],
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve('react-refresh/babel'),
].filter(Boolean),
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
Expand Down Expand Up @@ -607,6 +621,17 @@ module.exports = function(webpackEnv) {
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Experimental hot reloading for React .
// https://github.com/facebook/react/tree/master/packages/react-refresh
isEnvDevelopment &&
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: {
entry: webpackDevClientEntry,
// TODO: This is just a stub module. Clean this up if possible.
module: require.resolve('./hotRefreshOverlayModuleStub'),
},
}),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
Expand Down
2 changes: 2 additions & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"types": "./lib/react-app.d.ts",
"dependencies": {
"@babel/core": "7.9.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.3.0-beta.1",
"@svgr/webpack": "4.3.3",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
Expand Down Expand Up @@ -68,6 +69,7 @@
"postcss-safe-parser": "4.0.1",
"react-app-polyfill": "^1.0.6",
"react-dev-utils": "^10.2.1",
"react-refresh": "^0.8.1",
"resolve": "1.15.0",
"resolve-url-loader": "3.1.1",
"sass-loader": "8.0.2",
Expand Down