From 2b01d4468960e73a3fad73d0451a92a3e4f9ecf2 Mon Sep 17 00:00:00 2001 From: acdlite Date: Fri, 19 Apr 2024 23:35:03 +0000 Subject: [PATCH] [Experiment] Reuse memo cache after interruption (#28878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an experimental feature flag to the implementation of useMemoCache, the internal cache used by the React Compiler (Forget). When enabled, instead of treating the cache as copy-on-write, like we do with fibers, we share the same cache instance across all render attempts, even if the component is interrupted before it commits. If an update is interrupted, either because it suspended or because of another update, we can reuse the memoized computations from the previous attempt. We can do this because the React Compiler performs atomic writes to the memo cache, i.e. it will not record the inputs to a memoization without also recording its output. This gives us a form of "resuming" within components and hooks. This only works when updating a component that already mounted. It has no impact during initial render, because the memo cache is stored on the fiber, and since we have not implemented resuming for fibers, it's always a fresh memo cache, anyway. However, this alone is pretty useful — it happens whenever you update the UI with fresh data after a mutation/action, which is extremely common in a Suspense-driven (e.g. RSC or Relay) app. So the impact of this feature is faster data mutations/actions (when the React Compiler is used). DiffTrain build for [ea26e38e33bffeba1ecc42688d7e8cd7e0da1c02](https://github.com/facebook/react/commit/ea26e38e33bffeba1ecc42688d7e8cd7e0da1c02) --- .../facebook-www/JSXDEVRuntime-dev.classic.js | 3 +- .../facebook-www/JSXDEVRuntime-dev.modern.js | 3 +- compiled/facebook-www/REVISION | 2 +- compiled/facebook-www/React-dev.classic.js | 5 ++-- compiled/facebook-www/React-dev.modern.js | 5 ++-- compiled/facebook-www/ReactART-dev.classic.js | 30 +++++++++++++++++-- compiled/facebook-www/ReactART-dev.modern.js | 30 +++++++++++++++++-- .../facebook-www/ReactART-prod.classic.js | 13 ++++---- compiled/facebook-www/ReactART-prod.modern.js | 13 ++++---- compiled/facebook-www/ReactDOM-dev.classic.js | 30 +++++++++++++++++-- compiled/facebook-www/ReactDOM-dev.modern.js | 30 +++++++++++++++++-- .../facebook-www/ReactDOM-prod.classic.js | 15 ++++++---- compiled/facebook-www/ReactDOM-prod.modern.js | 15 ++++++---- .../ReactDOM-profiling.classic.js | 15 ++++++---- .../facebook-www/ReactDOM-profiling.modern.js | 15 ++++++---- .../ReactDOMServer-dev.classic.js | 5 ++-- .../facebook-www/ReactDOMServer-dev.modern.js | 5 ++-- .../ReactDOMServerStreaming-dev.modern.js | 3 +- .../ReactDOMTesting-dev.classic.js | 30 +++++++++++++++++-- .../ReactDOMTesting-dev.modern.js | 30 +++++++++++++++++-- .../ReactDOMTesting-prod.classic.js | 17 ++++++----- .../ReactDOMTesting-prod.modern.js | 17 ++++++----- .../ReactReconciler-dev.classic.js | 30 +++++++++++++++++-- .../ReactReconciler-dev.modern.js | 30 +++++++++++++++++-- .../ReactReconciler-prod.classic.js | 11 ++++--- .../ReactReconciler-prod.modern.js | 11 ++++--- .../ReactTestRenderer-dev.classic.js | 27 +++++++++++++++-- .../ReactTestRenderer-dev.modern.js | 27 +++++++++++++++-- 28 files changed, 371 insertions(+), 96 deletions(-) diff --git a/compiled/facebook-www/JSXDEVRuntime-dev.classic.js b/compiled/facebook-www/JSXDEVRuntime-dev.classic.js index b446358853..24bb660b1c 100644 --- a/compiled/facebook-www/JSXDEVRuntime-dev.classic.js +++ b/compiled/facebook-www/JSXDEVRuntime-dev.classic.js @@ -100,7 +100,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, false is used for a new modern build. // because JSX is an extremely hot path. var disableStringRefs = false; diff --git a/compiled/facebook-www/JSXDEVRuntime-dev.modern.js b/compiled/facebook-www/JSXDEVRuntime-dev.modern.js index decac8f839..ce384b8f86 100644 --- a/compiled/facebook-www/JSXDEVRuntime-dev.modern.js +++ b/compiled/facebook-www/JSXDEVRuntime-dev.modern.js @@ -100,7 +100,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, true is used for a new modern build. // because JSX is an extremely hot path. var disableStringRefs = false; diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index fe1e7a6b9c..f84466e1b8 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -446aa9a632670eb9a373d897309452e24c10c55e +ea26e38e33bffeba1ecc42688d7e8cd7e0da1c02 diff --git a/compiled/facebook-www/React-dev.classic.js b/compiled/facebook-www/React-dev.classic.js index aaff9af906..750a7ff01b 100644 --- a/compiled/facebook-www/React-dev.classic.js +++ b/compiled/facebook-www/React-dev.classic.js @@ -25,7 +25,7 @@ if ( ) { __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); } - var ReactVersion = '19.0.0-www-classic-a296fa2c'; + var ReactVersion = '19.0.0-www-classic-8ac39151'; // ATTENTION // When adding new symbols to this file, @@ -429,7 +429,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, false is used for a new modern build. // because JSX is an extremely hot path. var disableStringRefs = false; diff --git a/compiled/facebook-www/React-dev.modern.js b/compiled/facebook-www/React-dev.modern.js index 9930f593c2..953a6ea001 100644 --- a/compiled/facebook-www/React-dev.modern.js +++ b/compiled/facebook-www/React-dev.modern.js @@ -25,7 +25,7 @@ if ( ) { __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); } - var ReactVersion = '19.0.0-www-modern-fe830ed2'; + var ReactVersion = '19.0.0-www-modern-f6026193'; // ATTENTION // When adding new symbols to this file, @@ -429,7 +429,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, true is used for a new modern build. // because JSX is an extremely hot path. var disableStringRefs = false; diff --git a/compiled/facebook-www/ReactART-dev.classic.js b/compiled/facebook-www/ReactART-dev.classic.js index 41c210ef70..4d9b5c4bbe 100644 --- a/compiled/facebook-www/ReactART-dev.classic.js +++ b/compiled/facebook-www/ReactART-dev.classic.js @@ -63,7 +63,7 @@ function _assertThisInitialized(self) { return self; } -var ReactVersion = '19.0.0-www-classic-94931f64'; +var ReactVersion = '19.0.0-www-classic-d14dfff2'; var LegacyRoot = 0; var ConcurrentRoot = 1; @@ -161,7 +161,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableInfiniteRenderLoopDetection = dynamicFeatureFlags.enableInfiniteRenderLoopDetection, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -8403,7 +8404,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 diff --git a/compiled/facebook-www/ReactART-dev.modern.js b/compiled/facebook-www/ReactART-dev.modern.js index 93b0a2e26d..81288d2065 100644 --- a/compiled/facebook-www/ReactART-dev.modern.js +++ b/compiled/facebook-www/ReactART-dev.modern.js @@ -63,7 +63,7 @@ function _assertThisInitialized(self) { return self; } -var ReactVersion = '19.0.0-www-modern-c57fe631'; +var ReactVersion = '19.0.0-www-modern-41846fdd'; var LegacyRoot = 0; var ConcurrentRoot = 1; @@ -161,7 +161,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableInfiniteRenderLoopDetection = dynamicFeatureFlags.enableInfiniteRenderLoopDetection, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -8192,7 +8193,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 diff --git a/compiled/facebook-www/ReactART-prod.classic.js b/compiled/facebook-www/ReactART-prod.classic.js index 6b30d4e827..18b1f95247 100644 --- a/compiled/facebook-www/ReactART-prod.classic.js +++ b/compiled/facebook-www/ReactART-prod.classic.js @@ -86,6 +86,7 @@ var ReactSharedInternals = enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -2739,9 +2740,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -10619,7 +10622,7 @@ var slice = Array.prototype.slice, return null; }, bundleType: 0, - version: "19.0.0-www-classic-e5fcf28b", + version: "19.0.0-www-classic-4cca10ad", rendererPackageName: "react-art" }; var internals$jscomp$inline_1322 = { @@ -10650,7 +10653,7 @@ var internals$jscomp$inline_1322 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-classic-e5fcf28b" + reconcilerVersion: "19.0.0-www-classic-4cca10ad" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_1323 = __REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/compiled/facebook-www/ReactART-prod.modern.js b/compiled/facebook-www/ReactART-prod.modern.js index d61e2ae2de..e5464822c1 100644 --- a/compiled/facebook-www/ReactART-prod.modern.js +++ b/compiled/facebook-www/ReactART-prod.modern.js @@ -86,6 +86,7 @@ var ReactSharedInternals = enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -2537,9 +2538,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -10098,7 +10101,7 @@ var slice = Array.prototype.slice, return null; }, bundleType: 0, - version: "19.0.0-www-modern-20a4f736", + version: "19.0.0-www-modern-c36cdef3", rendererPackageName: "react-art" }; var internals$jscomp$inline_1307 = { @@ -10129,7 +10132,7 @@ var internals$jscomp$inline_1307 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-modern-20a4f736" + reconcilerVersion: "19.0.0-www-modern-c36cdef3" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_1308 = __REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/compiled/facebook-www/ReactDOM-dev.classic.js b/compiled/facebook-www/ReactDOM-dev.classic.js index c00e8c7644..a414cd6332 100644 --- a/compiled/facebook-www/ReactDOM-dev.classic.js +++ b/compiled/facebook-www/ReactDOM-dev.classic.js @@ -126,7 +126,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -12043,7 +12044,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -30808,7 +30832,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-classic-bf2e83d8'; +var ReactVersion = '19.0.0-www-classic-3bfa22a0'; function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { diff --git a/compiled/facebook-www/ReactDOM-dev.modern.js b/compiled/facebook-www/ReactDOM-dev.modern.js index e8249e2479..c4ccc49b16 100644 --- a/compiled/facebook-www/ReactDOM-dev.modern.js +++ b/compiled/facebook-www/ReactDOM-dev.modern.js @@ -103,7 +103,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -18179,7 +18180,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -38715,7 +38739,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-modern-806cc996'; +var ReactVersion = '19.0.0-www-modern-b1784c6b'; function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { diff --git a/compiled/facebook-www/ReactDOM-prod.classic.js b/compiled/facebook-www/ReactDOM-prod.classic.js index fd403b0340..e58fb30310 100644 --- a/compiled/facebook-www/ReactDOM-prod.classic.js +++ b/compiled/facebook-www/ReactDOM-prod.classic.js @@ -63,6 +63,7 @@ var ReactSharedInternals = dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -3448,9 +3449,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -17033,7 +17036,7 @@ Internals.Events = [ var devToolsConfig$jscomp$inline_1729 = { findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-classic-38c43cf6", + version: "19.0.0-www-classic-7b6a2c60", rendererPackageName: "react-dom" }; var internals$jscomp$inline_2160 = { @@ -17063,7 +17066,7 @@ var internals$jscomp$inline_2160 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-classic-38c43cf6" + reconcilerVersion: "19.0.0-www-classic-7b6a2c60" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2161 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -17529,4 +17532,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-classic-38c43cf6"; +exports.version = "19.0.0-www-classic-7b6a2c60"; diff --git a/compiled/facebook-www/ReactDOM-prod.modern.js b/compiled/facebook-www/ReactDOM-prod.modern.js index 865b6063ae..c08db8d7df 100644 --- a/compiled/facebook-www/ReactDOM-prod.modern.js +++ b/compiled/facebook-www/ReactDOM-prod.modern.js @@ -45,6 +45,7 @@ var Scheduler = require("scheduler"), dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, assign = Object.assign, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, @@ -6053,9 +6054,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -16395,7 +16398,7 @@ Internals.Events = [ var devToolsConfig$jscomp$inline_1722 = { findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-modern-1217c10a", + version: "19.0.0-www-modern-c7f3d35a", rendererPackageName: "react-dom" }; var internals$jscomp$inline_2162 = { @@ -16425,7 +16428,7 @@ var internals$jscomp$inline_2162 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-modern-1217c10a" + reconcilerVersion: "19.0.0-www-modern-c7f3d35a" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_2163 = __REACT_DEVTOOLS_GLOBAL_HOOK__; @@ -16733,4 +16736,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-modern-1217c10a"; +exports.version = "19.0.0-www-modern-c7f3d35a"; diff --git a/compiled/facebook-www/ReactDOM-profiling.classic.js b/compiled/facebook-www/ReactDOM-profiling.classic.js index 9b5a37ac8e..a23539bd11 100644 --- a/compiled/facebook-www/ReactDOM-profiling.classic.js +++ b/compiled/facebook-www/ReactDOM-profiling.classic.js @@ -67,6 +67,7 @@ var ReactSharedInternals = dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), @@ -3584,9 +3585,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -17781,7 +17784,7 @@ Internals.Events = [ var devToolsConfig$jscomp$inline_1815 = { findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-classic-a7d2fb9b", + version: "19.0.0-www-classic-7a3394e6", rendererPackageName: "react-dom" }; (function (internals) { @@ -17825,7 +17828,7 @@ var devToolsConfig$jscomp$inline_1815 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-classic-a7d2fb9b" + reconcilerVersion: "19.0.0-www-classic-7a3394e6" }); var ReactFiberErrorDialogWWW = require("ReactFiberErrorDialog"); if ("function" !== typeof ReactFiberErrorDialogWWW.showErrorDialog) @@ -18278,7 +18281,7 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-classic-a7d2fb9b"; +exports.version = "19.0.0-www-classic-7a3394e6"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/compiled/facebook-www/ReactDOM-profiling.modern.js b/compiled/facebook-www/ReactDOM-profiling.modern.js index d5c6d25719..77e3550cde 100644 --- a/compiled/facebook-www/ReactDOM-profiling.modern.js +++ b/compiled/facebook-www/ReactDOM-profiling.modern.js @@ -49,6 +49,7 @@ var Scheduler = require("scheduler"), dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler, assign = Object.assign, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, @@ -6189,9 +6190,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -17126,7 +17129,7 @@ Internals.Events = [ var devToolsConfig$jscomp$inline_1808 = { findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-modern-1e359311", + version: "19.0.0-www-modern-6d2a2ca0", rendererPackageName: "react-dom" }; (function (internals) { @@ -17170,7 +17173,7 @@ var devToolsConfig$jscomp$inline_1808 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-modern-1e359311" + reconcilerVersion: "19.0.0-www-modern-6d2a2ca0" }); var ReactFiberErrorDialogWWW = require("ReactFiberErrorDialog"); if ("function" !== typeof ReactFiberErrorDialogWWW.showErrorDialog) @@ -17465,7 +17468,7 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-modern-1e359311"; +exports.version = "19.0.0-www-modern-6d2a2ca0"; "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && diff --git a/compiled/facebook-www/ReactDOMServer-dev.classic.js b/compiled/facebook-www/ReactDOMServer-dev.classic.js index a113b298a1..e30a50ed09 100644 --- a/compiled/facebook-www/ReactDOMServer-dev.classic.js +++ b/compiled/facebook-www/ReactDOMServer-dev.classic.js @@ -19,7 +19,7 @@ if (__DEV__) { var React = require('react'); var ReactDOM = require('react-dom'); -var ReactVersion = '19.0.0-www-classic-54375a5d'; +var ReactVersion = '19.0.0-www-classic-b065c3bc'; // This refers to a WWW module. var warningWWW = require('warning'); @@ -609,7 +609,8 @@ var enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableUseDeferredValueInitialArg = dynamicFeatureFlags.enableUseDeferredValueInitialArg, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, false is used for a new modern build. // $FlowFixMe[method-unbinding] var hasOwnProperty = Object.prototype.hasOwnProperty; diff --git a/compiled/facebook-www/ReactDOMServer-dev.modern.js b/compiled/facebook-www/ReactDOMServer-dev.modern.js index 4a901a83f5..41a0ad6ba1 100644 --- a/compiled/facebook-www/ReactDOMServer-dev.modern.js +++ b/compiled/facebook-www/ReactDOMServer-dev.modern.js @@ -19,7 +19,7 @@ if (__DEV__) { var React = require('react'); var ReactDOM = require('react-dom'); -var ReactVersion = '19.0.0-www-modern-395c21d4'; +var ReactVersion = '19.0.0-www-modern-bdfde0e4'; // This refers to a WWW module. var warningWWW = require('warning'); @@ -609,7 +609,8 @@ var enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableUseDeferredValueInitialArg = dynamicFeatureFlags.enableUseDeferredValueInitialArg, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, true is used for a new modern build. // $FlowFixMe[method-unbinding] var hasOwnProperty = Object.prototype.hasOwnProperty; diff --git a/compiled/facebook-www/ReactDOMServerStreaming-dev.modern.js b/compiled/facebook-www/ReactDOMServerStreaming-dev.modern.js index 10a524cd41..90bffef303 100644 --- a/compiled/facebook-www/ReactDOMServerStreaming-dev.modern.js +++ b/compiled/facebook-www/ReactDOMServerStreaming-dev.modern.js @@ -606,7 +606,8 @@ var enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing, enableUseDeferredValueInitialArg = dynamicFeatureFlags.enableUseDeferredValueInitialArg, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; + // On WWW, true is used for a new modern build. // $FlowFixMe[method-unbinding] var hasOwnProperty = Object.prototype.hasOwnProperty; diff --git a/compiled/facebook-www/ReactDOMTesting-dev.classic.js b/compiled/facebook-www/ReactDOMTesting-dev.classic.js index 8b5898e769..89d67b808a 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.classic.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.classic.js @@ -117,7 +117,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -12174,7 +12175,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -31364,7 +31388,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-classic-36a7a568'; +var ReactVersion = '19.0.0-www-classic-75e45b11'; function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { diff --git a/compiled/facebook-www/ReactDOMTesting-dev.modern.js b/compiled/facebook-www/ReactDOMTesting-dev.modern.js index 54c0708e86..6e687be950 100644 --- a/compiled/facebook-www/ReactDOMTesting-dev.modern.js +++ b/compiled/facebook-www/ReactDOMTesting-dev.modern.js @@ -94,7 +94,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -18310,7 +18311,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -39403,7 +39427,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-modern-2ec60c86'; +var ReactVersion = '19.0.0-www-modern-24100354'; function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. implementation) { diff --git a/compiled/facebook-www/ReactDOMTesting-prod.classic.js b/compiled/facebook-www/ReactDOMTesting-prod.classic.js index 311a27eaac..c8812f9a01 100644 --- a/compiled/facebook-www/ReactDOMTesting-prod.classic.js +++ b/compiled/facebook-www/ReactDOMTesting-prod.classic.js @@ -63,6 +63,7 @@ var ReactSharedInternals = dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -3534,9 +3535,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -12843,7 +12846,7 @@ function injectIntoDevTools(devToolsConfig) { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-classic-8729eb10" + reconcilerVersion: "19.0.0-www-classic-66a7ae3c" }; if ("undefined" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) devToolsConfig = !1; @@ -17406,7 +17409,7 @@ Internals.Events = [ injectIntoDevTools({ findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-classic-8729eb10", + version: "19.0.0-www-classic-66a7ae3c", rendererPackageName: "react-dom" }); var ReactFiberErrorDialogWWW = require("ReactFiberErrorDialog"); @@ -17539,7 +17542,7 @@ assign(Internals, { injectIntoDevTools({ findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-classic-8729eb10", + version: "19.0.0-www-classic-66a7ae3c", rendererPackageName: "react-dom" }); exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = @@ -18006,4 +18009,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-classic-8729eb10"; +exports.version = "19.0.0-www-classic-66a7ae3c"; diff --git a/compiled/facebook-www/ReactDOMTesting-prod.modern.js b/compiled/facebook-www/ReactDOMTesting-prod.modern.js index e8e0529332..fa8501f0a8 100644 --- a/compiled/facebook-www/ReactDOMTesting-prod.modern.js +++ b/compiled/facebook-www/ReactDOMTesting-prod.modern.js @@ -45,6 +45,7 @@ var Scheduler = require("scheduler"), dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, assign = Object.assign, scheduleCallback$3 = Scheduler.unstable_scheduleCallback, cancelCallback$1 = Scheduler.unstable_cancelCallback, @@ -6198,9 +6199,11 @@ function useMemoCache(size) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -16129,7 +16132,7 @@ function injectIntoDevTools(devToolsConfig) { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-modern-375fcad7" + reconcilerVersion: "19.0.0-www-modern-26fdeab5" }; if ("undefined" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) devToolsConfig = !1; @@ -16827,7 +16830,7 @@ Internals.Events = [ injectIntoDevTools({ findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-modern-375fcad7", + version: "19.0.0-www-modern-26fdeab5", rendererPackageName: "react-dom" }); if ("function" !== typeof require("ReactFiberErrorDialog").showErrorDialog) @@ -16835,7 +16838,7 @@ if ("function" !== typeof require("ReactFiberErrorDialog").showErrorDialog) injectIntoDevTools({ findFiberByHostInstance: getClosestInstanceFromNode, bundleType: 0, - version: "19.0.0-www-modern-375fcad7", + version: "19.0.0-www-modern-26fdeab5", rendererPackageName: "react-dom" }); exports.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = @@ -17249,4 +17252,4 @@ exports.useFormState = function (action, initialState, permalink) { exports.useFormStatus = function () { return ReactSharedInternals.H.useHostTransitionStatus(); }; -exports.version = "19.0.0-www-modern-375fcad7"; +exports.version = "19.0.0-www-modern-26fdeab5"; diff --git a/compiled/facebook-www/ReactReconciler-dev.classic.js b/compiled/facebook-www/ReactReconciler-dev.classic.js index d52fcd0c88..0b33e318ca 100644 --- a/compiled/facebook-www/ReactReconciler-dev.classic.js +++ b/compiled/facebook-www/ReactReconciler-dev.classic.js @@ -117,7 +117,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -8977,7 +8978,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -28627,7 +28651,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-classic-2ccc45c8'; +var ReactVersion = '19.0.0-www-classic-ebcd5e46'; /* * The `'' + value` pattern (used in perf-sensitive code) throws for Symbol diff --git a/compiled/facebook-www/ReactReconciler-dev.modern.js b/compiled/facebook-www/ReactReconciler-dev.modern.js index 02d95bec74..5bf2058948 100644 --- a/compiled/facebook-www/ReactReconciler-dev.modern.js +++ b/compiled/facebook-www/ReactReconciler-dev.modern.js @@ -117,7 +117,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing, enableRenderableContext = dynamicFeatureFlags.enableRenderableContext, enableRefAsProp = dynamicFeatureFlags.enableRefAsProp, favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf, - disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build. + disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build. var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableProfilerNestedUpdatePhase = true; @@ -8768,7 +8769,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -27893,7 +27917,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-modern-89f34d9f'; +var ReactVersion = '19.0.0-www-modern-880c3caa'; /* * The `'' + value` pattern (used in perf-sensitive code) throws for Symbol diff --git a/compiled/facebook-www/ReactReconciler-prod.classic.js b/compiled/facebook-www/ReactReconciler-prod.classic.js index 0afebcfb75..9a96e0dc8d 100644 --- a/compiled/facebook-www/ReactReconciler-prod.classic.js +++ b/compiled/facebook-www/ReactReconciler-prod.classic.js @@ -2592,9 +2592,11 @@ module.exports = function ($$$config) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -11503,6 +11505,7 @@ module.exports = function ($$$config) { dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -12629,7 +12632,7 @@ module.exports = function ($$$config) { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-classic-23bf93ce" + reconcilerVersion: "19.0.0-www-classic-873e0310" }; if ("undefined" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) devToolsConfig = !1; diff --git a/compiled/facebook-www/ReactReconciler-prod.modern.js b/compiled/facebook-www/ReactReconciler-prod.modern.js index 075996e545..7c1fa2ad4a 100644 --- a/compiled/facebook-www/ReactReconciler-prod.modern.js +++ b/compiled/facebook-www/ReactReconciler-prod.modern.js @@ -2452,9 +2452,11 @@ module.exports = function ($$$config) { ((current = current.memoCache), null != current && (memoCache = { - data: current.data.map(function (array) { - return array.slice(); - }), + data: enableNoCloningMemoCache + ? current.data + : current.data.map(function (array) { + return array.slice(); + }), index: 0 }))); } @@ -11033,6 +11035,7 @@ module.exports = function ($$$config) { dynamicFeatureFlags.favorSafetyOverHydrationPerf, disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses, + enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache, REACT_ELEMENT_TYPE = Symbol.for("react.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), @@ -12146,7 +12149,7 @@ module.exports = function ($$$config) { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "19.0.0-www-modern-a6fd038b" + reconcilerVersion: "19.0.0-www-modern-944c4921" }; if ("undefined" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) devToolsConfig = !1; diff --git a/compiled/facebook-www/ReactTestRenderer-dev.classic.js b/compiled/facebook-www/ReactTestRenderer-dev.classic.js index b46a23b697..5e026aabaf 100644 --- a/compiled/facebook-www/ReactTestRenderer-dev.classic.js +++ b/compiled/facebook-www/ReactTestRenderer-dev.classic.js @@ -7533,7 +7533,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -23090,7 +23113,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-classic-41085150'; +var ReactVersion = '19.0.0-www-classic-3d1458be'; /* * The `'' + value` pattern (used in perf-sensitive code) throws for Symbol diff --git a/compiled/facebook-www/ReactTestRenderer-dev.modern.js b/compiled/facebook-www/ReactTestRenderer-dev.modern.js index 06d71a952c..d970701fd5 100644 --- a/compiled/facebook-www/ReactTestRenderer-dev.modern.js +++ b/compiled/facebook-www/ReactTestRenderer-dev.modern.js @@ -7533,7 +7533,30 @@ function useMemoCache(size) { if (currentMemoCache != null) { memoCache = { - data: currentMemoCache.data.map(function (array) { + // When enableNoCloningMemoCache is enabled, instead of treating the + // cache as copy-on-write, like we do with fibers, we share the same + // cache instance across all render attempts, even if the component + // is interrupted before it commits. + // + // If an update is interrupted, either because it suspended or + // because of another update, we can reuse the memoized computations + // from the previous attempt. We can do this because the React + // Compiler performs atomic writes to the memo cache, i.e. it will + // not record the inputs to a memoization without also recording its + // output. + // + // This gives us a form of "resuming" within components and hooks. + // + // This only works when updating a component that already mounted. + // It has no impact during initial render, because the memo cache is + // stored on the fiber, and since we have not implemented resuming + // for fibers, it's always a fresh memo cache, anyway. + // + // However, this alone is pretty useful — it happens whenever you + // update the UI with fresh data after a mutation/action, which is + // extremely common in a Suspense-driven (e.g. RSC or Relay) app. + data: // Clone the memo cache before each render (copy-on-write) + currentMemoCache.data.map(function (array) { return array.slice(); }), index: 0 @@ -23090,7 +23113,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition return root; } -var ReactVersion = '19.0.0-www-modern-41085150'; +var ReactVersion = '19.0.0-www-modern-3d1458be'; /* * The `'' + value` pattern (used in perf-sensitive code) throws for Symbol