diff --git a/src/configuration.js b/src/configuration.js index a1416ca62..95f19840a 100644 --- a/src/configuration.js +++ b/src/configuration.js @@ -54,6 +54,9 @@ const configuration = { // Global error overlay ErrorOverlay: undefined, + + // react hot dom features enabled + IS_REACT_MERGE_ENABLED: false, }; export const internalConfiguration = { diff --git a/src/hot.dev.js b/src/hot.dev.js index 85f71413b..4bed1da5c 100644 --- a/src/hot.dev.js +++ b/src/hot.dev.js @@ -8,6 +8,7 @@ import { isOpened as isModuleOpened, hotModule, getLastModuleOpened } from './gl import logger from './logger'; import { clearExceptions, logException } from './errorReporter'; import { createQueue } from './utils/runQueue'; +import { enterHotUpdate, getHotGeneration } from './global/generation'; /* eslint-disable camelcase, no-undef */ const requireIndirect = typeof __webpack_require__ !== 'undefined' ? __webpack_require__ : require; @@ -48,6 +49,29 @@ const makeHotExport = (sourceModule, moduleId) => { } const module = hotModule(moduleId); + const deepUpdate = () => { + // force flush all updates + runInRenderQueue(() => { + enterHotUpdate(); + const gen = getHotGeneration(); + module.instances.forEach(inst => inst.forceUpdate()); + + let runLimit = 0; + const checkTailUpdates = () => { + setTimeout(() => { + if (getHotGeneration() !== gen) { + console.warn('React-Hot-Loader has detected a stale state. Updating...'); + deepUpdate(); + } else if (++runLimit < 5) { + checkTailUpdates(); + } + }, 16); + }; + + checkTailUpdates(); + }); + }; + // require all modules runInRequireQueue(() => { try { @@ -58,12 +82,7 @@ const makeHotExport = (sourceModule, moduleId) => { console.error('React-Hot-Loader: error detected while loading', moduleId); console.error(e); } - }).then(() => { - // force flush all updates - runInRenderQueue(() => { - module.instances.forEach(inst => inst.forceUpdate()); - }); - }); + }).then(deepUpdate); }; if (sourceModule.hot) { diff --git a/src/internal/getReactStack.js b/src/internal/getReactStack.js index 87e040a8e..d0736b123 100644 --- a/src/internal/getReactStack.js +++ b/src/internal/getReactStack.js @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'; import hydrateFiberStack from './stack/hydrateFiberStack'; import hydrateLegacyStack from './stack/hydrateLegacyStack'; import { getInternalInstance } from './reactUtils'; +import { resolveType } from '../reconciler/resolver'; function getReactStack(instance) { const rootNode = getInternalInstance(instance); @@ -30,10 +31,18 @@ const markUpdate = ({ fiber }) => { if (!fiber || typeof fiber.type === 'string') { return; } + + const mostResentType = resolveType(fiber.type) || fiber.type; + if (fiber.elementType === fiber.type) { + fiber.elementType = mostResentType; + } + fiber.type = mostResentType; + fiber.expirationTime = 1; if (fiber.alternate) { fiber.alternate.expirationTime = 1; fiber.alternate.type = fiber.type; + fiber.alternate.elementType = fiber.elementType; } if (fiber.memoizedProps && typeof fiber.memoizedProps === 'object') { diff --git a/src/reactHotLoader.js b/src/reactHotLoader.js index dd741a483..e4d50b6b1 100644 --- a/src/reactHotLoader.js +++ b/src/reactHotLoader.js @@ -57,7 +57,6 @@ const hookWrapper = hook => { const noDeps = () => []; const reactHotLoader = { - IS_REACT_MERGE_ENABLED: false, signature(type, key, getCustomHooks = noDeps) { addSignature(type, { key, getCustomHooks }); return type; @@ -75,7 +74,7 @@ const reactHotLoader = { const proxy = getProxyById(id); if (proxy && proxy.getCurrent() !== type) { - if (!reactHotLoader.IS_REACT_MERGE_ENABLED) { + if (!configuration.IS_REACT_MERGE_ENABLED) { if (isTypeBlacklisted(type) || isTypeBlacklisted(proxy.getCurrent())) { logger.error('React-hot-loader: Cold component', uniqueLocalName, 'at', fileName, 'has been updated'); } @@ -146,7 +145,7 @@ const reactHotLoader = { configuration.ignoreSFC = configuration.ignoreSFCWhenInjected; - reactHotLoader.IS_REACT_MERGE_ENABLED = true; + configuration.IS_REACT_MERGE_ENABLED = true; configuration.showReactDomPatchNotification = false; configuration.integratedComparator = true; diff --git a/src/reconciler/hotReplacementRender.js b/src/reconciler/hotReplacementRender.js index 44b822235..b3b26a63d 100644 --- a/src/reconciler/hotReplacementRender.js +++ b/src/reconciler/hotReplacementRender.js @@ -13,7 +13,6 @@ import { isLazyType, isForwardType, } from '../internal/reactUtils'; -import reactHotLoader from '../reactHotLoader'; import logger from '../logger'; import configuration, { internalConfiguration } from '../configuration'; import { areSwappable } from './utils'; @@ -142,7 +141,7 @@ const mergeInject = (a, b, instance) => { } if (flatB.length === 0 && flatA.length === 1 && typeof flatA[0] !== 'object') { // terminal node - } else if (!reactHotLoader.IS_REACT_MERGE_ENABLED) { + } else if (!configuration.IS_REACT_MERGE_ENABLED) { logger.warn(`React-hot-loader: unable to merge `, a, 'and children of ', instance); stackReport(); } @@ -312,7 +311,7 @@ const hotReplacementRender = (instance, stack) => { } if (!stackChild.type[PROXY_KEY]) { - if (!reactHotLoader.IS_REACT_MERGE_ENABLED) { + if (!configuration.IS_REACT_MERGE_ENABLED) { if (isTypeBlacklisted(stackChild.type)) { logger.warn('React-hot-loader: cold element got updated ', stackChild.type); } diff --git a/src/reconciler/resolver.js b/src/reconciler/resolver.js index d5e9802c7..aa3fdfaa5 100644 --- a/src/reconciler/resolver.js +++ b/src/reconciler/resolver.js @@ -66,12 +66,30 @@ export function resolveNotComponent(type) { return undefined; } +export const getLatestTypeVersion = type => { + const existingProxy = getProxyByType(type); + return existingProxy && existingProxy.getCurrent && existingProxy.getCurrent(); +}; + export const resolveSimpleType = type => { if (!type) { return type; } - return resolveProxy(type) || resolveUtility(type) || type; + const simpleResult = resolveProxy(type) || resolveUtility(type) || resolveNotComponent(type); + if (simpleResult) { + return simpleResult; + } + + const lastType = getLatestTypeVersion(type); + + // only lazy loaded components any now failing into this branch + + // if (lastType && lastType !== type) { + // console.warn('RHL: used type', type, 'is obsolete. Something is wrong with HMR.'); + // } + + return lastType || type; }; export const resolveType = (type, options = {}) => {