diff --git a/docusaurus/docs/advanced-configuration.md b/docusaurus/docs/advanced-configuration.md index 3fc01ee583..fb71df0e23 100644 --- a/docusaurus/docs/advanced-configuration.md +++ b/docusaurus/docs/advanced-configuration.md @@ -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. | diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 1054ce48f8..0379358fb9 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -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; } diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index f354439dc3..f4303bda95 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -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 diff --git a/packages/react-scripts/config/hotRefreshOverlayModuleStub.js b/packages/react-scripts/config/hotRefreshOverlayModuleStub.js new file mode 100644 index 0000000000..8171063fc7 --- /dev/null +++ b/packages/react-scripts/config/hotRefreshOverlayModuleStub.js @@ -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, +}; diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 25840d9114..5f88dcd06e 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -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 @@ -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'; @@ -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 = [ @@ -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 @@ -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/ @@ -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 diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 63e7faf390..a5983f373c 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -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", @@ -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",