From de556afd6ded7c09fc2e16e5e60c2e998bcd9756 Mon Sep 17 00:00:00 2001 From: Adam Charron Date: Fri, 28 Feb 2020 21:52:38 -0500 Subject: [PATCH 01/10] Add react-refresh webpack plugin --- .../react-dev-utils/webpackHotDevClient.js | 149 +----------------- .../react-scripts/config/webpack.config.js | 5 + packages/react-scripts/package.json | 2 + 3 files changed, 12 insertions(+), 144 deletions(-) diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 1054ce48f81..fe32f3a60b9 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -10,51 +10,14 @@ // This alternative WebpackDevServer combines the functionality of: // https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js // https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js - // It only supports their simplest configuration (hot updates on same server). // It makes some opinionated choices on top, like adding a syntax error overlay -// that looks similar to our console output. The error overlay is inspired by: +// The error overlay is inspired by: // https://github.com/glenjamin/webpack-hot-middleware +// The error overlay is provided by: +// https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/src/overlay -var stripAnsi = require('strip-ansi'); var url = require('url'); -var launchEditorEndpoint = require('./launchEditorEndpoint'); -var formatWebpackMessages = require('./formatWebpackMessages'); -var ErrorOverlay = require('react-error-overlay'); - -ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) { - // Keep this sync with errorOverlayMiddleware.js - fetch( - launchEditorEndpoint + - '?fileName=' + - window.encodeURIComponent(errorLocation.fileName) + - '&lineNumber=' + - window.encodeURIComponent(errorLocation.lineNumber || 1) + - '&colNumber=' + - window.encodeURIComponent(errorLocation.colNumber || 1) - ); -}); - -// We need to keep track of if there has been a runtime error. -// Essentially, we cannot guarantee application state was not corrupted by the -// runtime error. To prevent confusing behavior, we forcibly reload the entire -// application. This is handled below when we are notified of a compile (code -// change). -// See https://github.com/facebook/create-react-app/issues/3096 -var hadRuntimeError = false; -ErrorOverlay.startReportingRuntimeErrors({ - onError: function() { - hadRuntimeError = true; - }, - filename: '/static/js/bundle.js', -}); - -if (module.hot && typeof module.hot.dispose === 'function') { - module.hot.dispose(function() { - // TODO: why do we need this? - ErrorOverlay.stopReportingRuntimeErrors(); - }); -} // Connect to WebpackDevServer via a socket. var connection = new WebSocket( @@ -82,106 +45,15 @@ connection.onclose = function() { // Remember some state related to hot module replacement. var isFirstCompilation = true; var mostRecentCompilationHash = null; -var hasCompileErrors = false; - -function clearOutdatedErrors() { - // Clean up outdated compile errors, if any. - if (typeof console !== 'undefined' && typeof console.clear === 'function') { - if (hasCompileErrors) { - console.clear(); - } - } -} // Successful compilation. function handleSuccess() { - clearOutdatedErrors(); - var isHotUpdate = !isFirstCompilation; isFirstCompilation = false; - hasCompileErrors = false; // Attempt to apply hot updates or reload. if (isHotUpdate) { - tryApplyUpdates(function onHotUpdateSuccess() { - // Only dismiss it when we're sure it's a hot update. - // Otherwise it would flicker right before the reload. - tryDismissErrorOverlay(); - }); - } -} - -// Compilation with warnings (e.g. ESLint). -function handleWarnings(warnings) { - clearOutdatedErrors(); - - var isHotUpdate = !isFirstCompilation; - isFirstCompilation = false; - hasCompileErrors = false; - - function printWarnings() { - // Print warnings to the console. - var formatted = formatWebpackMessages({ - warnings: warnings, - errors: [], - }); - - if (typeof console !== 'undefined' && typeof console.warn === 'function') { - for (var i = 0; i < formatted.warnings.length; i++) { - if (i === 5) { - console.warn( - 'There were more warnings in other files.\n' + - 'You can find a complete log in the terminal.' - ); - break; - } - console.warn(stripAnsi(formatted.warnings[i])); - } - } - } - - printWarnings(); - - // Attempt to apply hot updates or reload. - if (isHotUpdate) { - tryApplyUpdates(function onSuccessfulHotUpdate() { - // Only dismiss it when we're sure it's a hot update. - // Otherwise it would flicker right before the reload. - tryDismissErrorOverlay(); - }); - } -} - -// Compilation with errors (e.g. syntax error or missing modules). -function handleErrors(errors) { - clearOutdatedErrors(); - - isFirstCompilation = false; - hasCompileErrors = true; - - // "Massage" webpack messages. - var formatted = formatWebpackMessages({ - errors: errors, - warnings: [], - }); - - // Only show the first error. - ErrorOverlay.reportBuildError(formatted.errors[0]); - - // Also log them to the console. - if (typeof console !== 'undefined' && typeof console.error === 'function') { - for (var i = 0; i < formatted.errors.length; i++) { - console.error(stripAnsi(formatted.errors[i])); - } - } - - // Do not attempt to reload now. - // We will reload on next success instead. -} - -function tryDismissErrorOverlay() { - if (!hasCompileErrors) { - ErrorOverlay.dismissBuildError(); + tryApplyUpdates(); } } @@ -206,12 +78,6 @@ connection.onmessage = function(e) { // Triggered when a file from `contentBase` changed. window.location.reload(); break; - case 'warnings': - handleWarnings(message.data); - break; - case 'errors': - handleErrors(message.data); - break; default: // Do nothing. } @@ -242,12 +108,7 @@ function tryApplyUpdates(onHotUpdateSuccess) { return; } - function handleApplyUpdates(err, updatedModules) { - if (err || !updatedModules || hadRuntimeError) { - window.location.reload(); - return; - } - + function handleApplyUpdates() { if (typeof onHotUpdateSuccess === 'function') { // Maybe we want to do something. onHotUpdateSuccess(); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 25840d91148..676b6f2aae0 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 @@ -419,6 +420,7 @@ module.exports = function(webpackEnv) { }, }, }, + isEnvDevelopment && require.resolve('react-refresh/babel'), ], ], // This is a feature of `babel-loader` for webpack (not Babel itself). @@ -607,6 +609,9 @@ module.exports = function(webpackEnv) { new webpack.DefinePlugin(env.stringified), // This is necessary to emit hot updates (currently CSS only): isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), + // Provide fast-refresh https://github.com/facebook/react/tree/master/packages/react-refresh + isEnvDevelopment && + new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }), // 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 63e7faf390c..7c406d25b63 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.0", "@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", From 78065e6e34dfd9cb4951bf2b01be653ccba5672f Mon Sep 17 00:00:00 2001 From: Adam Charron Date: Fri, 28 Feb 2020 22:22:58 -0500 Subject: [PATCH 02/10] Add boolean filter to babel plugins --- packages/react-scripts/config/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 676b6f2aae0..af59629d3b1 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -421,7 +421,7 @@ module.exports = function(webpackEnv) { }, }, isEnvDevelopment && 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/ From c7f00320a90f387d9daea19a0f9ad1a3b9db78c2 Mon Sep 17 00:00:00 2001 From: Adam Charron Date: Sat, 29 Feb 2020 13:46:10 -0500 Subject: [PATCH 03/10] Add feature flag env variable for react refresh --- .../webpackFastRefreshDevClient.js | 137 ++++++++++++++++ .../react-dev-utils/webpackHotDevClient.js | 149 +++++++++++++++++- .../react-scripts/config/webpack.config.js | 15 +- 3 files changed, 293 insertions(+), 8 deletions(-) create mode 100644 packages/react-dev-utils/webpackFastRefreshDevClient.js diff --git a/packages/react-dev-utils/webpackFastRefreshDevClient.js b/packages/react-dev-utils/webpackFastRefreshDevClient.js new file mode 100644 index 00000000000..fe32f3a60b9 --- /dev/null +++ b/packages/react-dev-utils/webpackFastRefreshDevClient.js @@ -0,0 +1,137 @@ +/** + * 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. + */ + +'use strict'; + +// This alternative WebpackDevServer combines the functionality of: +// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js +// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js +// It only supports their simplest configuration (hot updates on same server). +// It makes some opinionated choices on top, like adding a syntax error overlay +// The error overlay is inspired by: +// https://github.com/glenjamin/webpack-hot-middleware +// The error overlay is provided by: +// https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/src/overlay + +var url = require('url'); + +// Connect to WebpackDevServer via a socket. +var connection = new WebSocket( + url.format({ + protocol: window.location.protocol === 'https:' ? 'wss' : 'ws', + hostname: process.env.WDS_SOCKET_HOST || window.location.hostname, + port: process.env.WDS_SOCKET_PORT || window.location.port, + // Hardcoded in WebpackDevServer + pathname: process.env.WDS_SOCKET_PATH || '/sockjs-node', + slashes: true, + }) +); + +// Unlike WebpackDevServer client, we won't try to reconnect +// to avoid spamming the console. Disconnect usually happens +// when developer stops the server. +connection.onclose = function() { + if (typeof console !== 'undefined' && typeof console.info === 'function') { + console.info( + 'The development server has disconnected.\nRefresh the page if necessary.' + ); + } +}; + +// Remember some state related to hot module replacement. +var isFirstCompilation = true; +var mostRecentCompilationHash = null; + +// Successful compilation. +function handleSuccess() { + var isHotUpdate = !isFirstCompilation; + isFirstCompilation = false; + + // Attempt to apply hot updates or reload. + if (isHotUpdate) { + tryApplyUpdates(); + } +} + +// There is a newer version of the code available. +function handleAvailableHash(hash) { + // Update last known compilation hash. + mostRecentCompilationHash = hash; +} + +// Handle messages from the server. +connection.onmessage = function(e) { + var message = JSON.parse(e.data); + switch (message.type) { + case 'hash': + handleAvailableHash(message.data); + break; + case 'still-ok': + case 'ok': + handleSuccess(); + break; + case 'content-changed': + // Triggered when a file from `contentBase` changed. + window.location.reload(); + break; + default: + // Do nothing. + } +}; + +// Is there a newer version of this code available? +function isUpdateAvailable() { + /* globals __webpack_hash__ */ + // __webpack_hash__ is the hash of the current compilation. + // It's a global variable injected by webpack. + return mostRecentCompilationHash !== __webpack_hash__; +} + +// webpack disallows updates in other states. +function canApplyUpdates() { + return module.hot.status() === 'idle'; +} + +// Attempt to update code on the fly, fall back to a hard reload. +function tryApplyUpdates(onHotUpdateSuccess) { + if (!module.hot) { + // HotModuleReplacementPlugin is not in webpack configuration. + window.location.reload(); + return; + } + + if (!isUpdateAvailable() || !canApplyUpdates()) { + return; + } + + function handleApplyUpdates() { + if (typeof onHotUpdateSuccess === 'function') { + // Maybe we want to do something. + onHotUpdateSuccess(); + } + + if (isUpdateAvailable()) { + // While we were updating, there was a new update! Do it again. + tryApplyUpdates(); + } + } + + // https://webpack.github.io/docs/hot-module-replacement.html#check + var result = module.hot.check(/* autoApply */ true, handleApplyUpdates); + + // // webpack 2 returns a Promise instead of invoking a callback + if (result && result.then) { + result.then( + function(updatedModules) { + handleApplyUpdates(null, updatedModules); + }, + function(err) { + handleApplyUpdates(err, null); + } + ); + } +} diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index fe32f3a60b9..1054ce48f81 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -10,14 +10,51 @@ // This alternative WebpackDevServer combines the functionality of: // https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js // https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js + // It only supports their simplest configuration (hot updates on same server). // It makes some opinionated choices on top, like adding a syntax error overlay -// The error overlay is inspired by: +// that looks similar to our console output. The error overlay is inspired by: // https://github.com/glenjamin/webpack-hot-middleware -// The error overlay is provided by: -// https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/src/overlay +var stripAnsi = require('strip-ansi'); var url = require('url'); +var launchEditorEndpoint = require('./launchEditorEndpoint'); +var formatWebpackMessages = require('./formatWebpackMessages'); +var ErrorOverlay = require('react-error-overlay'); + +ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) { + // Keep this sync with errorOverlayMiddleware.js + fetch( + launchEditorEndpoint + + '?fileName=' + + window.encodeURIComponent(errorLocation.fileName) + + '&lineNumber=' + + window.encodeURIComponent(errorLocation.lineNumber || 1) + + '&colNumber=' + + window.encodeURIComponent(errorLocation.colNumber || 1) + ); +}); + +// We need to keep track of if there has been a runtime error. +// Essentially, we cannot guarantee application state was not corrupted by the +// runtime error. To prevent confusing behavior, we forcibly reload the entire +// application. This is handled below when we are notified of a compile (code +// change). +// See https://github.com/facebook/create-react-app/issues/3096 +var hadRuntimeError = false; +ErrorOverlay.startReportingRuntimeErrors({ + onError: function() { + hadRuntimeError = true; + }, + filename: '/static/js/bundle.js', +}); + +if (module.hot && typeof module.hot.dispose === 'function') { + module.hot.dispose(function() { + // TODO: why do we need this? + ErrorOverlay.stopReportingRuntimeErrors(); + }); +} // Connect to WebpackDevServer via a socket. var connection = new WebSocket( @@ -45,15 +82,106 @@ connection.onclose = function() { // Remember some state related to hot module replacement. var isFirstCompilation = true; var mostRecentCompilationHash = null; +var hasCompileErrors = false; + +function clearOutdatedErrors() { + // Clean up outdated compile errors, if any. + if (typeof console !== 'undefined' && typeof console.clear === 'function') { + if (hasCompileErrors) { + console.clear(); + } + } +} // Successful compilation. function handleSuccess() { + clearOutdatedErrors(); + var isHotUpdate = !isFirstCompilation; isFirstCompilation = false; + hasCompileErrors = false; // Attempt to apply hot updates or reload. if (isHotUpdate) { - tryApplyUpdates(); + tryApplyUpdates(function onHotUpdateSuccess() { + // Only dismiss it when we're sure it's a hot update. + // Otherwise it would flicker right before the reload. + tryDismissErrorOverlay(); + }); + } +} + +// Compilation with warnings (e.g. ESLint). +function handleWarnings(warnings) { + clearOutdatedErrors(); + + var isHotUpdate = !isFirstCompilation; + isFirstCompilation = false; + hasCompileErrors = false; + + function printWarnings() { + // Print warnings to the console. + var formatted = formatWebpackMessages({ + warnings: warnings, + errors: [], + }); + + if (typeof console !== 'undefined' && typeof console.warn === 'function') { + for (var i = 0; i < formatted.warnings.length; i++) { + if (i === 5) { + console.warn( + 'There were more warnings in other files.\n' + + 'You can find a complete log in the terminal.' + ); + break; + } + console.warn(stripAnsi(formatted.warnings[i])); + } + } + } + + printWarnings(); + + // Attempt to apply hot updates or reload. + if (isHotUpdate) { + tryApplyUpdates(function onSuccessfulHotUpdate() { + // Only dismiss it when we're sure it's a hot update. + // Otherwise it would flicker right before the reload. + tryDismissErrorOverlay(); + }); + } +} + +// Compilation with errors (e.g. syntax error or missing modules). +function handleErrors(errors) { + clearOutdatedErrors(); + + isFirstCompilation = false; + hasCompileErrors = true; + + // "Massage" webpack messages. + var formatted = formatWebpackMessages({ + errors: errors, + warnings: [], + }); + + // Only show the first error. + ErrorOverlay.reportBuildError(formatted.errors[0]); + + // Also log them to the console. + if (typeof console !== 'undefined' && typeof console.error === 'function') { + for (var i = 0; i < formatted.errors.length; i++) { + console.error(stripAnsi(formatted.errors[i])); + } + } + + // Do not attempt to reload now. + // We will reload on next success instead. +} + +function tryDismissErrorOverlay() { + if (!hasCompileErrors) { + ErrorOverlay.dismissBuildError(); } } @@ -78,6 +206,12 @@ connection.onmessage = function(e) { // Triggered when a file from `contentBase` changed. window.location.reload(); break; + case 'warnings': + handleWarnings(message.data); + break; + case 'errors': + handleErrors(message.data); + break; default: // Do nothing. } @@ -108,7 +242,12 @@ function tryApplyUpdates(onHotUpdateSuccess) { return; } - function handleApplyUpdates() { + function handleApplyUpdates(err, updatedModules) { + if (err || !updatedModules || hadRuntimeError) { + window.location.reload(); + return; + } + if (typeof onHotUpdateSuccess === 'function') { // Maybe we want to do something. onHotUpdateSuccess(); diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index af59629d3b1..8ab65c988a6 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -42,6 +42,13 @@ 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'; + +// React refresh isn't 100% stable right now. We have a feature flag to enable it. +const shouldUseReactRefresh = process.env.REACT_REFRESH === 'true'; +const webpackDevClientEntry = shouldUseReactRefresh + ? require.resolve('react-dev-utils/webpackFastRefreshDevClient') + : 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'; @@ -161,8 +168,7 @@ module.exports = function(webpackEnv) { // 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'), + isEnvDevelopment && 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 @@ -420,7 +426,9 @@ module.exports = function(webpackEnv) { }, }, }, - isEnvDevelopment && require.resolve('react-refresh/babel'), + isEnvDevelopment && + shouldUseReactRefresh && + require.resolve('react-refresh/babel'), ].filter(Boolean), ], // This is a feature of `babel-loader` for webpack (not Babel itself). @@ -611,6 +619,7 @@ module.exports = function(webpackEnv) { isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), // Provide fast-refresh https://github.com/facebook/react/tree/master/packages/react-refresh isEnvDevelopment && + shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }), // 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. From aee58491b2f538df556d46cc6a93310e91301e09 Mon Sep 17 00:00:00 2001 From: Adam Charron Date: Sat, 29 Feb 2020 14:40:18 -0500 Subject: [PATCH 04/10] Fix refresh not occuring with warnings --- packages/react-dev-utils/webpackFastRefreshDevClient.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-dev-utils/webpackFastRefreshDevClient.js b/packages/react-dev-utils/webpackFastRefreshDevClient.js index fe32f3a60b9..ac02244ce93 100644 --- a/packages/react-dev-utils/webpackFastRefreshDevClient.js +++ b/packages/react-dev-utils/webpackFastRefreshDevClient.js @@ -72,6 +72,7 @@ connection.onmessage = function(e) { break; case 'still-ok': case 'ok': + case 'warnings': // Warnings are not fatal. We should try refreshing. handleSuccess(); break; case 'content-changed': From 670b60ad29f63c776d857a96d615e32b6fb82038 Mon Sep 17 00:00:00 2001 From: Adam Charron Date: Tue, 31 Mar 2020 00:00:23 -0400 Subject: [PATCH 05/10] Remove duplicated config and update react refresh client --- .../errorOverlayModuleEntry.js | 22 +++ .../webpackFastRefreshDevClient.js | 138 ------------------ .../react-dev-utils/webpackHotDevClient.js | 5 +- packages/react-scripts/config/env.js | 4 + .../react-scripts/config/webpack.config.js | 20 ++- packages/react-scripts/package.json | 1 + 6 files changed, 44 insertions(+), 146 deletions(-) create mode 100644 packages/react-dev-utils/errorOverlayModuleEntry.js delete mode 100644 packages/react-dev-utils/webpackFastRefreshDevClient.js diff --git a/packages/react-dev-utils/errorOverlayModuleEntry.js b/packages/react-dev-utils/errorOverlayModuleEntry.js new file mode 100644 index 00000000000..8171063fc78 --- /dev/null +++ b/packages/react-dev-utils/errorOverlayModuleEntry.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-dev-utils/webpackFastRefreshDevClient.js b/packages/react-dev-utils/webpackFastRefreshDevClient.js deleted file mode 100644 index ac02244ce93..00000000000 --- a/packages/react-dev-utils/webpackFastRefreshDevClient.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * 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. - */ - -'use strict'; - -// This alternative WebpackDevServer combines the functionality of: -// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js -// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js -// It only supports their simplest configuration (hot updates on same server). -// It makes some opinionated choices on top, like adding a syntax error overlay -// The error overlay is inspired by: -// https://github.com/glenjamin/webpack-hot-middleware -// The error overlay is provided by: -// https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/src/overlay - -var url = require('url'); - -// Connect to WebpackDevServer via a socket. -var connection = new WebSocket( - url.format({ - protocol: window.location.protocol === 'https:' ? 'wss' : 'ws', - hostname: process.env.WDS_SOCKET_HOST || window.location.hostname, - port: process.env.WDS_SOCKET_PORT || window.location.port, - // Hardcoded in WebpackDevServer - pathname: process.env.WDS_SOCKET_PATH || '/sockjs-node', - slashes: true, - }) -); - -// Unlike WebpackDevServer client, we won't try to reconnect -// to avoid spamming the console. Disconnect usually happens -// when developer stops the server. -connection.onclose = function() { - if (typeof console !== 'undefined' && typeof console.info === 'function') { - console.info( - 'The development server has disconnected.\nRefresh the page if necessary.' - ); - } -}; - -// Remember some state related to hot module replacement. -var isFirstCompilation = true; -var mostRecentCompilationHash = null; - -// Successful compilation. -function handleSuccess() { - var isHotUpdate = !isFirstCompilation; - isFirstCompilation = false; - - // Attempt to apply hot updates or reload. - if (isHotUpdate) { - tryApplyUpdates(); - } -} - -// There is a newer version of the code available. -function handleAvailableHash(hash) { - // Update last known compilation hash. - mostRecentCompilationHash = hash; -} - -// Handle messages from the server. -connection.onmessage = function(e) { - var message = JSON.parse(e.data); - switch (message.type) { - case 'hash': - handleAvailableHash(message.data); - break; - case 'still-ok': - case 'ok': - case 'warnings': // Warnings are not fatal. We should try refreshing. - handleSuccess(); - break; - case 'content-changed': - // Triggered when a file from `contentBase` changed. - window.location.reload(); - break; - default: - // Do nothing. - } -}; - -// Is there a newer version of this code available? -function isUpdateAvailable() { - /* globals __webpack_hash__ */ - // __webpack_hash__ is the hash of the current compilation. - // It's a global variable injected by webpack. - return mostRecentCompilationHash !== __webpack_hash__; -} - -// webpack disallows updates in other states. -function canApplyUpdates() { - return module.hot.status() === 'idle'; -} - -// Attempt to update code on the fly, fall back to a hard reload. -function tryApplyUpdates(onHotUpdateSuccess) { - if (!module.hot) { - // HotModuleReplacementPlugin is not in webpack configuration. - window.location.reload(); - return; - } - - if (!isUpdateAvailable() || !canApplyUpdates()) { - return; - } - - function handleApplyUpdates() { - if (typeof onHotUpdateSuccess === 'function') { - // Maybe we want to do something. - onHotUpdateSuccess(); - } - - if (isUpdateAvailable()) { - // While we were updating, there was a new update! Do it again. - tryApplyUpdates(); - } - } - - // https://webpack.github.io/docs/hot-module-replacement.html#check - var result = module.hot.check(/* autoApply */ true, handleApplyUpdates); - - // // webpack 2 returns a Promise instead of invoking a callback - if (result && result.then) { - result.then( - function(updatedModules) { - handleApplyUpdates(null, updatedModules); - }, - function(err) { - handleApplyUpdates(err, null); - } - ); - } -} diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 1054ce48f81..00760370dbb 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.REACT_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 f354439dc3a..499dfb4fb1a 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -93,6 +93,10 @@ 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. + REACT_REFRESH: process.env.REACT_REFRESH || false, } ); // Stringify all values so we can feed into webpack DefinePlugin diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 8ab65c988a6..7f049e71226 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -43,11 +43,9 @@ 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'; -// React refresh isn't 100% stable right now. We have a feature flag to enable it. -const shouldUseReactRefresh = process.env.REACT_REFRESH === 'true'; -const webpackDevClientEntry = shouldUseReactRefresh - ? require.resolve('react-dev-utils/webpackFastRefreshDevClient') - : require.resolve('react-dev-utils/webpackHotDevClient'); +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. @@ -85,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.REACT_REFRESH; + // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ @@ -168,7 +168,7 @@ module.exports = function(webpackEnv) { // 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 && webpackDevClientEntry, + 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 @@ -620,7 +620,13 @@ module.exports = function(webpackEnv) { // Provide fast-refresh https://github.com/facebook/react/tree/master/packages/react-refresh isEnvDevelopment && shouldUseReactRefresh && - new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }), + new ReactRefreshWebpackPlugin({ + disableRefreshCheck: true, + overlay: { + entry: webpackDevClientEntry, + module: require.resolve('react-dev-utils/errorOverlayModuleEntry'), + }, + }), // 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 7c406d25b63..47eddf9db90 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -80,6 +80,7 @@ "url-loader": "2.3.0", "webpack": "4.42.0", "webpack-dev-server": "3.10.3", + "webpack-hot-middleware": "^2.25.0", "webpack-manifest-plugin": "2.2.0", "workbox-webpack-plugin": "4.3.1" }, From a07f4ac04aee4ba7105ad3e8c01e2e9df8cb4b8d Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Tue, 31 Mar 2020 22:16:58 -0700 Subject: [PATCH 06/10] Remove unnecessary dep --- packages/react-scripts/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 47eddf9db90..7c406d25b63 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -80,7 +80,6 @@ "url-loader": "2.3.0", "webpack": "4.42.0", "webpack-dev-server": "3.10.3", - "webpack-hot-middleware": "^2.25.0", "webpack-manifest-plugin": "2.2.0", "workbox-webpack-plugin": "4.3.1" }, From baa543be61c1f7ccb934c7ac946cb524cbf0e056 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Tue, 31 Mar 2020 22:19:11 -0700 Subject: [PATCH 07/10] Move module stub to hide from react-dev-utils --- packages/react-scripts/config/env.js | 1 + .../config/hotRefreshOverlayModuleStub.js} | 0 packages/react-scripts/config/webpack.config.js | 10 ++++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) rename packages/{react-dev-utils/errorOverlayModuleEntry.js => react-scripts/config/hotRefreshOverlayModuleStub.js} (100%) diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index 499dfb4fb1a..feb9011519c 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -96,6 +96,7 @@ function getClientEnvironment(publicUrl) { // 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. REACT_REFRESH: process.env.REACT_REFRESH || false, } ); diff --git a/packages/react-dev-utils/errorOverlayModuleEntry.js b/packages/react-scripts/config/hotRefreshOverlayModuleStub.js similarity index 100% rename from packages/react-dev-utils/errorOverlayModuleEntry.js rename to packages/react-scripts/config/hotRefreshOverlayModuleStub.js diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 7f049e71226..7cc43cf70ce 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -166,8 +166,12 @@ 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'), + // + // 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, @@ -617,14 +621,16 @@ module.exports = function(webpackEnv) { new webpack.DefinePlugin(env.stringified), // This is necessary to emit hot updates (currently CSS only): isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), - // Provide fast-refresh https://github.com/facebook/react/tree/master/packages/react-refresh + // Experimental hot reloading for React . + // https://github.com/facebook/react/tree/master/packages/react-refresh isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ disableRefreshCheck: true, overlay: { entry: webpackDevClientEntry, - module: require.resolve('react-dev-utils/errorOverlayModuleEntry'), + // 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 From 960867ad2a394675177a398853d7cc5658ff6bf0 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Tue, 31 Mar 2020 22:26:03 -0700 Subject: [PATCH 08/10] Change REACT_REFRESH to FAST_REFRESH --- packages/react-dev-utils/webpackHotDevClient.js | 2 +- packages/react-scripts/config/env.js | 2 +- packages/react-scripts/config/webpack.config.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 00760370dbb..0379358fb99 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -243,7 +243,7 @@ function tryApplyUpdates(onHotUpdateSuccess) { } function handleApplyUpdates(err, updatedModules) { - const hasReactRefresh = process.env.REACT_REFRESH; + const hasReactRefresh = process.env.FAST_REFRESH; const wantsForcedReload = err || !updatedModules || hadRuntimeError; // React refresh can handle hot-reloading over errors. if (!hasReactRefresh && wantsForcedReload) { diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index feb9011519c..f4303bda95d 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -97,7 +97,7 @@ function getClientEnvironment(publicUrl) { // 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. - REACT_REFRESH: process.env.REACT_REFRESH || false, + FAST_REFRESH: process.env.FAST_REFRESH || false, } ); // Stringify all values so we can feed into webpack DefinePlugin diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 7cc43cf70ce..d3662da80a7 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -83,7 +83,7 @@ module.exports = function(webpackEnv) { // Get environment variables to inject into our app. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); - const shouldUseReactRefresh = env.raw.REACT_REFRESH; + const shouldUseReactRefresh = env.raw.FAST_REFRESH; // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { From 95ee8221b79cf45cf942df81c76f68bc146e40a8 Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Tue, 31 Mar 2020 22:26:24 -0700 Subject: [PATCH 09/10] Add env variable docs --- docusaurus/docs/advanced-configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docusaurus/docs/advanced-configuration.md b/docusaurus/docs/advanced-configuration.md index 3fc01ee583a..fb71df0e231 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. | From 572d14c19cc9626656450de6ec545d90e460142e Mon Sep 17 00:00:00 2001 From: Ian Schmitz Date: Sun, 5 Apr 2020 20:58:33 -0700 Subject: [PATCH 10/10] Update refresh webpack plugin --- packages/react-scripts/config/webpack.config.js | 1 - packages/react-scripts/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index d3662da80a7..5f88dcd06e5 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -626,7 +626,6 @@ module.exports = function(webpackEnv) { isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({ - disableRefreshCheck: true, overlay: { entry: webpackDevClientEntry, // TODO: This is just a stub module. Clean this up if possible. diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 7c406d25b63..a5983f373c2 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -29,7 +29,7 @@ "types": "./lib/react-app.d.ts", "dependencies": { "@babel/core": "7.9.0", - "@pmmmwh/react-refresh-webpack-plugin": "0.3.0-beta.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",