From 33309cf3e6d9876f6ac0eb5d909ea4a1a05faa75 Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Tue, 15 Jun 2021 02:14:23 +0200 Subject: [PATCH 1/2] fix: fast refresh stops on needed bail outs Fast Refresh requires the HMR runtime to support bail out behaviour (we do not do so within the core runtime as it has to be platform agnostic) - and currently webpackHotDevClient does not do so properly as it circumvents the logic for forced reloads completely when using Fast Refresh. The changes done here ensures that: - If Fast Refresh is not enabled, we would always bail out to a forced reload; - If Fast Refresh is enabled and there are updated modules, it indicates the update has at least partially executed, and we can rely on Fast Refresh being resilient to errors and skip the forced reload; - If Fast Refresh is enabled and there are none updated modules, it indicates the update cannot be executed without being inconsistent (i.e. Fast Refresh bailed out), we would bail out to a forced reload. --- packages/react-dev-utils/webpackHotDevClient.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index db87aba7d0c..9af9bc567e9 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -245,9 +245,13 @@ function tryApplyUpdates(onHotUpdateSuccess) { function handleApplyUpdates(err, updatedModules) { // NOTE: This var is injected by Webpack's DefinePlugin, and is a boolean instead of string. const hasReactRefresh = process.env.FAST_REFRESH; - const wantsForcedReload = err || !updatedModules || hadRuntimeError; + const wantsErrorReload = err || hadRuntimeError; + const needsForcedReload = !updatedModules; // React refresh can handle hot-reloading over errors. - if (!hasReactRefresh && wantsForcedReload) { + // However, when updatedModules is falsy, + // it indicates the current update cannot be applied safely, + // and thus we should bail out to a forced reload for consistency. + if ((!hasReactRefresh && wantsErrorReload) || needsForcedReload) { window.location.reload(); return; } From 1156f1ec8f173fa862a4a004d488a6e4ccc966bd Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Tue, 15 Jun 2021 23:53:32 +0200 Subject: [PATCH 2/2] chore: better heuristic to skip unnecessary reloads --- .../react-dev-utils/webpackHotDevClient.js | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js index 9af9bc567e9..5fc15267e01 100644 --- a/packages/react-dev-utils/webpackHotDevClient.js +++ b/packages/react-dev-utils/webpackHotDevClient.js @@ -230,6 +230,18 @@ function canApplyUpdates() { return module.hot.status() === 'idle'; } +function canAcceptErrors() { + // NOTE: This var is injected by Webpack's DefinePlugin, and is a boolean instead of string. + const hasReactRefresh = process.env.FAST_REFRESH; + + const status = module.hot.status(); + // React refresh can handle hot-reloading over errors. + // However, when hot-reload status is abort or fail, + // it indicates the current update cannot be applied safely, + // and thus we should bail out to a forced reload for consistency. + return hasReactRefresh && ["abort", "fail"].indexOf(status) === -1 +} + // Attempt to update code on the fly, fall back to a hard reload. function tryApplyUpdates(onHotUpdateSuccess) { if (!module.hot) { @@ -243,15 +255,13 @@ function tryApplyUpdates(onHotUpdateSuccess) { } function handleApplyUpdates(err, updatedModules) { - // NOTE: This var is injected by Webpack's DefinePlugin, and is a boolean instead of string. - const hasReactRefresh = process.env.FAST_REFRESH; - const wantsErrorReload = err || hadRuntimeError; - const needsForcedReload = !updatedModules; - // React refresh can handle hot-reloading over errors. - // However, when updatedModules is falsy, - // it indicates the current update cannot be applied safely, - // and thus we should bail out to a forced reload for consistency. - if ((!hasReactRefresh && wantsErrorReload) || needsForcedReload) { + const haveErrors = err || hadRuntimeError; + // When there is no error but updatedModules is unavailable, + // it indicates a critical failure in hot-reloading, + // e.g. server is not ready to serve new bundle, + // and hence we need to do a forced reload. + const needsForcedReload = !err && !updatedModules; + if ((haveErrors && !canAcceptErrors()) || needsForcedReload) { window.location.reload(); return; }