From 40c7a7f6cac62bd6721328524cc787475ce026eb Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann"
Date: Sat, 18 Oct 2025 12:54:05 +0200
Subject: [PATCH 01/25] [DevTools] Use same Suspense naming heuristics when
reconnecting (#34898)
---
.../src/__tests__/store-test.js | 57 +++++++++++++++++++
.../src/backend/fiber/renderer.js | 2 +-
.../src/devtools/store.js | 11 +---
.../src/devtools/utils.js | 6 +-
.../views/SuspenseTab/SuspenseBreadcrumbs.js | 2 +-
.../views/SuspenseTab/SuspenseRects.js | 2 +-
6 files changed, 64 insertions(+), 16 deletions(-)
diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index c02d8130c3..07e8204049 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -3243,4 +3243,61 @@ describe('Store', () => {
`);
});
+
+ // @reactVersion >= 19.0
+ it('guesses a Suspense name based on the owner', async () => {
+ let resolve;
+ const promise = new Promise(_resolve => {
+ resolve = _resolve;
+ });
+ function Inner() {
+ return (
+ Loading inner
}>
+ {promise}
+
+ );
+ }
+
+ function Outer({children}) {
+ return (
+ Loading outer}>
+ {promise}
+ {children}
+
+ );
+ }
+
+ await actAsync(() => {
+ render(
+
+
+ ,
+ );
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ [suspense-root] rects={[{x:1,y:2,width:13,height:1}]}
+
+ `);
+
+ console.log('...........................');
+
+ await actAsync(() => {
+ resolve('loaded');
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+ ▾
+ ▾
+
+ [suspense-root] rects={[{x:1,y:2,width:6,height:1}, {x:1,y:2,width:6,height:1}]}
+
+
+ `);
+ });
});
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index de178909b2..4dd4a619cb 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -2664,7 +2664,7 @@ export function attach(
const fiber = fiberInstance.data;
const props = fiber.memoizedProps;
- // TODO: Compute a fallback name based on Owner, key etc.
+ // The frontend will guess a name based on heuristics (e.g. owner) if no explicit name is given.
const name =
fiber.tag !== SuspenseComponent || props === null
? null
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index b75e30d9c4..aaf0584a4c 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -1646,10 +1646,6 @@ export default class Store extends EventEmitter<{
parentSuspense.children.push(id);
}
- if (name === null) {
- name = 'Unknown';
- }
-
this._idToSuspense.set(id, {
id,
parentID,
@@ -2170,13 +2166,12 @@ export default class Store extends EventEmitter<{
throw error;
}
- _guessSuspenseName(element: Element): string {
+ _guessSuspenseName(element: Element): string | null {
const owner = this._idToElement.get(element.ownerID);
- let name = 'Unknown';
if (owner !== undefined && owner.displayName !== null) {
- name = owner.displayName;
+ return owner.displayName;
}
- return name;
+ return null;
}
}
diff --git a/packages/react-devtools-shared/src/devtools/utils.js b/packages/react-devtools-shared/src/devtools/utils.js
index d5078679f4..640f0d19b5 100644
--- a/packages/react-devtools-shared/src/devtools/utils.js
+++ b/packages/react-devtools-shared/src/devtools/utils.js
@@ -63,11 +63,7 @@ function printRects(rects: SuspenseNode['rects']): string {
}
function printSuspense(suspense: SuspenseNode): string {
- let name = '';
- if (suspense.name !== null) {
- name = ` name="${suspense.name}"`;
- }
-
+ const name = ` name="${suspense.name || 'Unknown'}"`;
const printedRects = printRects(suspense.rects);
return ``;
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
index d7112ec7d3..b27e0f5987 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
@@ -72,7 +72,7 @@ export default function SuspenseBreadcrumbs(): React$Node {
className={styles.SuspenseBreadcrumbsButton}
onClick={handleClick.bind(null, id)}
type="button">
- {node === null ? 'Unknown' : node.name}
+ {node === null ? 'Unknown' : node.name || 'Unknown'}
);
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
index e6beca7c22..67a6bf2773 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
@@ -196,7 +196,7 @@ function SuspenseRects({
onPointerOver={handlePointerOver}
onPointerLeave={handlePointerLeave}
// Reach-UI tooltip will go out of bounds of parent scroll container.
- title={suspense.name}
+ title={suspense.name || 'Unknown'}
/>
);
})}
From ec7d9a7249e84e841fbe1e4c22e1be2c0c15dae4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 11:56:25 -0700
Subject: [PATCH 02/25] Resolve the .default export of a React.lazy as the
canonical value (#34906)
For debug purposes this is the value that the `React.lazy` resolves to.
It also lets us look at that value for descriptions like its name.
---
packages/react/src/ReactLazy.js | 31 +++++++++++++++++++++++++++----
1 file changed, 27 insertions(+), 4 deletions(-)
diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js
index 55b1690b7c..47f37afbae 100644
--- a/packages/react/src/ReactLazy.js
+++ b/packages/react/src/ReactLazy.js
@@ -20,6 +20,8 @@ import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';
+import noop from 'shared/noop';
+
const Uninitialized = -1;
const Pending = 0;
const Resolved = 1;
@@ -67,12 +69,20 @@ export type LazyComponent = {
function lazyInitializer(payload: Payload): T {
if (payload._status === Uninitialized) {
+ let resolveDebugValue: (void | T) => void = (null: any);
+ let rejectDebugValue: mixed => void = (null: any);
if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Mark when we first kicked off the lazy request.
// $FlowFixMe[cannot-write]
ioInfo.start = ioInfo.end = performance.now();
+ // Stash a Promise for introspection of the value later.
+ // $FlowFixMe[cannot-write]
+ ioInfo.value = new Promise((resolve, reject) => {
+ resolveDebugValue = resolve;
+ rejectDebugValue = reject;
+ });
}
}
const ctor = payload._result;
@@ -92,12 +102,20 @@ function lazyInitializer(payload: Payload): T {
const resolved: ResolvedPayload = (payload: any);
resolved._status = Resolved;
resolved._result = moduleObject;
- if (__DEV__) {
+ if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Mark the end time of when we resolved.
// $FlowFixMe[cannot-write]
ioInfo.end = performance.now();
+ // Surface the default export as the resolved "value" for debug purposes.
+ const debugValue =
+ moduleObject == null ? undefined : moduleObject.default;
+ resolveDebugValue(debugValue);
+ // $FlowFixMe
+ ioInfo.value.status = 'fulfilled';
+ // $FlowFixMe
+ ioInfo.value.value = debugValue;
}
// Make the thenable introspectable
if (thenable.status === undefined) {
@@ -124,6 +142,14 @@ function lazyInitializer(payload: Payload): T {
// Mark the end time of when we rejected.
// $FlowFixMe[cannot-write]
ioInfo.end = performance.now();
+ // Hide unhandled rejections.
+ // $FlowFixMe
+ ioInfo.value.then(noop, noop);
+ rejectDebugValue(error);
+ // $FlowFixMe
+ ioInfo.value.status = 'rejected';
+ // $FlowFixMe
+ ioInfo.value.reason = error;
}
// Make the thenable introspectable
if (thenable.status === undefined) {
@@ -139,9 +165,6 @@ function lazyInitializer(payload: Payload): T {
if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
- // Stash the thenable for introspection of the value later.
- // $FlowFixMe[cannot-write]
- ioInfo.value = thenable;
const displayName = thenable.displayName;
if (typeof displayName === 'string') {
// $FlowFixMe[cannot-write]
From bf11d2fb2f01174974b7e1fa5b1c01d34936724b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 11:56:40 -0700
Subject: [PATCH 03/25] [DevTools] Infer name from stack if it's the generic
"lazy" name (#34907)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Stacked on #34906.
Infer name from stack if it's the generic "lazy" name. It might be
wrapped in an abstraction. E.g. `next/dynamic`.
Also use the function name as a description of a resolved function
value.
---
.../react-devtools-shared/src/devtools/views/useInferredName.js | 2 +-
packages/shared/ReactIODescription.js | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/react-devtools-shared/src/devtools/views/useInferredName.js b/packages/react-devtools-shared/src/devtools/views/useInferredName.js
index f0822e126d..2c93ded469 100644
--- a/packages/react-devtools-shared/src/devtools/views/useInferredName.js
+++ b/packages/react-devtools-shared/src/devtools/views/useInferredName.js
@@ -16,7 +16,7 @@ export default function useInferredName(
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const name = asyncInfo.awaited.name;
let inferNameFromStack = null;
- if (!name || name === 'Promise') {
+ if (!name || name === 'Promise' || name === 'lazy') {
// If all we have is a generic name, we can try to infer a better name from
// the stack. We only do this if the stack has more than one frame since
// otherwise it's likely to just be the name of the component which isn't better.
diff --git a/packages/shared/ReactIODescription.js b/packages/shared/ReactIODescription.js
index e1a0fce2c4..6d7bf648fc 100644
--- a/packages/shared/ReactIODescription.js
+++ b/packages/shared/ReactIODescription.js
@@ -13,6 +13,8 @@ export function getIODescription(value: mixed): string {
}
try {
switch (typeof value) {
+ case 'function':
+ return value.name || '';
case 'object':
// Test the object for a bunch of common property names that are useful identifiers.
// While we only have the return value here, it should ideally be a name that
From 58bdc0bb967098f14562cd76af0668f2056459a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 11:56:56 -0700
Subject: [PATCH 04/25] [Flight] Ignore bound-anonymous-fn resources as they're
not considered I/O (#34911)
When you create a snapshot from an AsyncLocalStorage in Node.js, that
creates a new bound AsyncResource which everything runs inside of.
https://github.com/nodejs/node/blob/3437e1c4bd529e51d96ea581b6435bbeb77ef524/lib/internal/async_local_storage/async_hooks.js#L61-L67
This resource is itself tracked by our async debug tracking as I/O. We
can't really distinguish these in general from other AsyncResources
which are I/O.
However, by default they're given the name `"bound-anonymous-fn"` if you
pass it an anonymous function or in the case of a snapshot, that's
built-in:
https://github.com/nodejs/node/blob/3437e1c4bd529e51d96ea581b6435bbeb77ef524/lib/async_hooks.js#L262-L263
We can at least assume that these are non-I/O. If you want to ensure
that a bound resource is not considered I/O, you can ensure your
function isn't assigned a name or give it this explicit name.
The other issue here is that, the sequencing here is that we track the
callsite of the `.snapshot()` or `.bind()` call as the trigger. So if
that was outside of render for example, then it would be considered
non-I/O. However, this might miss stuff if you resolve promises inside
the `.run()` of the snapshot if the `.run()` call itself was spawned by
I/O which should be tracked. Time will tell if those patterns appear.
However, in cases like nested renders (e.g. Next.js's "use cache") then
restoring it as if it was outside the parent render is what you do want.
---
.../src/ReactFlightServerConfigDebugNode.js | 31 +++++++++++++------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js
index a78297ad83..e79c19cc73 100644
--- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js
+++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js
@@ -142,10 +142,28 @@ export function initAsyncDebugInfo(): void {
}: UnresolvedPromiseNode);
}
} else if (
- type !== 'Microtask' &&
- type !== 'TickObject' &&
- type !== 'Immediate'
+ // bound-anonymous-fn is the default name for snapshots and .bind() without a name.
+ // This isn't I/O by itself but likely just a continuation. If the bound function
+ // has a name, we might treat it as I/O but we can't tell the difference.
+ type === 'bound-anonymous-fn' ||
+ // queueMicroTask, process.nextTick and setImmediate aren't considered new I/O
+ // for our purposes but just continuation of existing I/O.
+ type === 'Microtask' ||
+ type === 'TickObject' ||
+ type === 'Immediate'
) {
+ // Treat the trigger as the node to carry along the sequence.
+ // For "bound-anonymous-fn" this will be the callsite of the .bind() which may not
+ // be the best if the callsite of the .run() call is within I/O which should be
+ // tracked. It might be better to track the execution context of "before()" as the
+ // execution context for anything spawned from within the run(). Basically as if
+ // it wasn't an AsyncResource at all.
+ if (trigger === undefined) {
+ return;
+ }
+ node = trigger;
+ } else {
+ // New I/O
if (trigger === undefined) {
// We have begun a new I/O sequence.
const owner = resolveOwner();
@@ -181,13 +199,6 @@ export function initAsyncDebugInfo(): void {
// Otherwise, this is just a continuation of the same I/O sequence.
node = trigger;
}
- } else {
- // Ignore nextTick and microtasks as they're not considered I/O operations.
- // we just treat the trigger as the node to carry along the sequence.
- if (trigger === undefined) {
- return;
- }
- node = trigger;
}
pendingOperations.set(asyncId, node);
},
From 2cfb221937eac48209d01d5dda5664de473b1953 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 13:38:33 -0700
Subject: [PATCH 05/25] [Flight] Allow passing DEV only startTime as an option
(#34912)
When you use the `createFromFetch` API we assume that the start time of
the request is the same time as when you call `createFromFetch` but in
principle you could use it with a Promise that starts earlier and just
happens to resolve to a `Response`.
When you use `createFromReadableStream` that is almost definitely the
case. E.g. you might have started it way earlier and you don't call
`createFromReadableStream` until you get the headers back (the fetch
promise resolves).
This adds an option to pass in the start time for debug purposes if you
started the request before starting to parse it.
---
packages/react-client/src/ReactFlightClient.js | 6 +++++-
packages/react-markup/src/ReactMarkupServer.js | 3 +++
.../src/client/ReactFlightDOMClientBrowser.js | 4 ++++
.../src/client/ReactFlightDOMClientNode.js | 4 ++++
.../src/client/ReactFlightDOMClientBrowser.js | 4 ++++
.../src/client/ReactFlightDOMClientEdge.js | 4 ++++
.../src/client/ReactFlightDOMClientNode.js | 4 ++++
.../src/client/ReactFlightDOMClientBrowser.js | 4 ++++
.../src/client/ReactFlightDOMClientEdge.js | 4 ++++
.../src/client/ReactFlightDOMClientNode.js | 4 ++++
.../src/client/ReactFlightDOMClientBrowser.js | 4 ++++
.../src/client/ReactFlightDOMClientEdge.js | 4 ++++
.../src/client/ReactFlightDOMClientNode.js | 4 ++++
13 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js
index 1c2151362d..8bcabfb556 100644
--- a/packages/react-client/src/ReactFlightClient.js
+++ b/packages/react-client/src/ReactFlightClient.js
@@ -2561,6 +2561,7 @@ function ResponseInstance(
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
replayConsole: boolean, // DEV-only
environmentName: void | string, // DEV-only
+ debugStartTime: void | number, // DEV-only
debugChannel: void | DebugChannel, // DEV-only
) {
const chunks: Map> = new Map();
@@ -2621,7 +2622,8 @@ function ResponseInstance(
// Note: createFromFetch allows this to be marked at the start of the fetch
// where as if you use createFromReadableStream from the body of the fetch
// then the start time is when the headers resolved.
- this._debugStartTime = performance.now();
+ this._debugStartTime =
+ debugStartTime == null ? performance.now() : debugStartTime;
this._debugIOStarted = false;
// We consider everything before the first setTimeout task to be cached data
// and is not considered I/O required to load the stream.
@@ -2669,6 +2671,7 @@ export function createResponse(
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
replayConsole: boolean, // DEV-only
environmentName: void | string, // DEV-only
+ debugStartTime: void | number, // DEV-only
debugChannel: void | DebugChannel, // DEV-only
): WeakResponse {
return getWeakResponse(
@@ -2684,6 +2687,7 @@ export function createResponse(
findSourceMapURL,
replayConsole,
environmentName,
+ debugStartTime,
debugChannel,
),
);
diff --git a/packages/react-markup/src/ReactMarkupServer.js b/packages/react-markup/src/ReactMarkupServer.js
index 0b35404cb8..6f1e35e615 100644
--- a/packages/react-markup/src/ReactMarkupServer.js
+++ b/packages/react-markup/src/ReactMarkupServer.js
@@ -91,6 +91,9 @@ export function experimental_renderToHTML(
undefined,
undefined,
false,
+ undefined,
+ undefined,
+ undefined,
);
const streamState = createFlightStreamState(flightResponse, null);
const flightDestination = {
diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js
index cfc8dcf5f1..371f08abc9 100644
--- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js
+++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js
@@ -52,6 +52,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
};
function createDebugCallbackFromWritableStream(
@@ -103,6 +104,9 @@ function createResponseFromOptions(options: void | Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js
index 2bf3272947..78dce93615 100644
--- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js
+++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientNode.js
@@ -57,6 +57,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Node.js client we only support a single-direction debug channel.
debugChannel?: Readable,
};
@@ -112,6 +113,9 @@ function createFromNodeStream(
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js
index 2f71ce2fa9..0f0141e640 100644
--- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js
+++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientBrowser.js
@@ -129,6 +129,9 @@ function createResponseFromOptions(options: void | Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
@@ -205,6 +208,7 @@ export type Options = {
temporaryReferences?: TemporaryReferenceSet,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
};
export function createFromReadableStream(
diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js
index f9c63ccbc3..5c8d1023b2 100644
--- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js
+++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientEdge.js
@@ -79,6 +79,7 @@ export type Options = {
temporaryReferences?: TemporaryReferenceSet,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Edge client we only support a single-direction debug channel.
debugChannel?: {readable?: ReadableStream, ...},
};
@@ -107,6 +108,9 @@ function createResponseFromOptions(options?: Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js
index b874bcd7d4..fbc633a175 100644
--- a/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js
+++ b/packages/react-server-dom-parcel/src/client/ReactFlightDOMClientNode.js
@@ -52,6 +52,7 @@ export type Options = {
encodeFormAction?: EncodeFormActionCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Node.js client we only support a single-direction debug channel.
debugChannel?: Readable,
};
@@ -103,6 +104,9 @@ export function createFromNodeStream(
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js
index b4b84f1c41..b3d31bd1bb 100644
--- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js
+++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js
@@ -51,6 +51,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
};
function createDebugCallbackFromWritableStream(
@@ -102,6 +103,9 @@ function createResponseFromOptions(options: void | Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js
index b994841be1..5517e0f73c 100644
--- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js
+++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js
@@ -79,6 +79,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Edge client we only support a single-direction debug channel.
debugChannel?: {readable?: ReadableStream, ...},
};
@@ -109,6 +110,9 @@ function createResponseFromOptions(options: Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js
index f174f10c0b..6d117929df 100644
--- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js
+++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js
@@ -60,6 +60,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Node.js client we only support a single-direction debug channel.
debugChannel?: Readable,
};
@@ -114,6 +115,9 @@ function createFromNodeStream(
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js
index b4b84f1c41..b3d31bd1bb 100644
--- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js
+++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js
@@ -51,6 +51,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
};
function createDebugCallbackFromWritableStream(
@@ -102,6 +103,9 @@ function createResponseFromOptions(options: void | Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js
index 63dae49545..bc4caac767 100644
--- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js
+++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js
@@ -79,6 +79,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Edge client we only support a single-direction debug channel.
debugChannel?: {readable?: ReadableStream, ...},
};
@@ -109,6 +110,9 @@ function createResponseFromOptions(options: Options) {
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
}
diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js
index f174f10c0b..6d117929df 100644
--- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js
+++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js
@@ -60,6 +60,7 @@ export type Options = {
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
+ startTime?: number,
// For the Node.js client we only support a single-direction debug channel.
debugChannel?: Readable,
};
@@ -114,6 +115,9 @@ function createFromNodeStream(
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
+ __DEV__ && options && options.startTime != null
+ ? options.startTime
+ : undefined,
debugChannel,
);
From b485f7cf64118fc8729181f46fe5e2edd47bea43 Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann"
Date: Mon, 20 Oct 2025 00:47:27 +0200
Subject: [PATCH 06/25] [DevTools] Don't attach filtered IO to grandparent
Suspense (#34916)
---
packages/react-devtools-shared/src/backend/fiber/renderer.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 4dd4a619cb..4a61fba652 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -2862,7 +2862,10 @@ export function attach(
let parentInstance = reconcilingParent;
while (
parentInstance.kind === FILTERED_FIBER_INSTANCE &&
- parentInstance.parent !== null
+ parentInstance.parent !== null &&
+ // We can't move past the parent Suspense node.
+ // The Suspense node holding async info must be a parent of the devtools instance (or the instance itself)
+ parentInstance !== parentSuspenseNode.instance
) {
parentInstance = parentInstance.parent;
}
From f6a4882859e6e894a39e9216d01005212855689e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 19:17:45 -0700
Subject: [PATCH 07/25] [DevTools] Show the Suspense boundary name in the rect
if there's no overlap (#34918)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This shows the title in the top corner of the rect if there's enough
space.
The complex bit here is that it can be noisy if too many boundaries
occupy the same space to overlap or partially overlap.
This uses an R-tree to store all the rects to find overlapping
boundaries to cut the available space to draw inside the rect. We use
this to compute the rectangle within the rect which doesn't have any
overlapping boundaries.
The roots don't count as overlapping. Similarly, a parent rect is not
consider overlapping a child. However, if two sibling boundaries occupy
the same space, no title will be drawn.
We might also consider drawing the "Initial Paint" title at the root but
that's less interesting. It's interesting in the beginning before you
know about the special case at the root but after that it's just always
the same value so just adds noise.
---
packages/react-devtools-shared/package.json | 3 +-
.../src/devtools/store.js | 59 +++++-
.../views/SuspenseTab/SuspenseRects.css | 20 +-
.../views/SuspenseTab/SuspenseRects.js | 108 +++++++++-
scripts/flow/environment.js | 188 ++++++++++++++++++
scripts/jest/config.build-devtools.js | 2 +-
yarn.lock | 98 ++-------
7 files changed, 387 insertions(+), 91 deletions(-)
diff --git a/packages/react-devtools-shared/package.json b/packages/react-devtools-shared/package.json
index 543ac37e97..721cc9954b 100644
--- a/packages/react-devtools-shared/package.json
+++ b/packages/react-devtools-shared/package.json
@@ -23,6 +23,7 @@
"json5": "^2.2.3",
"local-storage-fallback": "^4.1.1",
"react-virtualized-auto-sizer": "^1.0.23",
- "react-window": "^1.8.10"
+ "react-window": "^1.8.10",
+ "rbush": "4.0.1"
}
}
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index aaf0584a4c..9377fa01df 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -62,6 +62,31 @@ import type {
import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
import type {DevToolsHookSettings} from '../backend/types';
+import RBush from 'rbush';
+
+// Custom version which works with our Rect data structure.
+class RectRBush extends RBush {
+ toBBox(rect: Rect): {
+ minX: number,
+ minY: number,
+ maxX: number,
+ maxY: number,
+ } {
+ return {
+ minX: rect.x,
+ minY: rect.y,
+ maxX: rect.x + rect.width,
+ maxY: rect.y + rect.height,
+ };
+ }
+ compareMinX(a: Rect, b: Rect): number {
+ return a.x - b.x;
+ }
+ compareMinY(a: Rect, b: Rect): number {
+ return a.y - b.y;
+ }
+}
+
const debug = (methodName: string, ...args: Array) => {
if (__DEBUG__) {
console.log(
@@ -194,6 +219,9 @@ export default class Store extends EventEmitter<{
// Renderer ID is needed to support inspection fiber props, state, and hooks.
_rootIDToRendererID: Map = new Map();
+ // Stores all the SuspenseNode rects in an R-tree to make it fast to find overlaps.
+ _rtree: RBush = new RectRBush();
+
// These options may be initially set by a configuration option when constructing the Store.
_supportsInspectMatchingDOMElement: boolean = false;
_supportsClickToInspect: boolean = false;
@@ -1622,7 +1650,12 @@ export default class Store extends EventEmitter<{
const y = operations[i + 1] / 1000;
const width = operations[i + 2] / 1000;
const height = operations[i + 3] / 1000;
- rects.push({x, y, width, height});
+ const rect = {x, y, width, height};
+ if (parentID !== 0) {
+ // Track all rects except the root.
+ this._rtree.insert(rect);
+ }
+ rects.push(rect);
i += 4;
}
}
@@ -1680,13 +1713,20 @@ export default class Store extends EventEmitter<{
i += 1;
- const {children, parentID} = suspense;
+ const {children, parentID, rects} = suspense;
if (children.length > 0) {
this._throwAndEmitError(
Error(`Suspense node "${id}" was removed before its children.`),
);
}
+ if (rects !== null && parentID !== 0) {
+ // Delete all the existing rects from the R-tree
+ for (let j = 0; j < rects.length; j++) {
+ this._rtree.remove(rects[j]);
+ }
+ }
+
this._idToSuspense.delete(id);
removedSuspenseIDs.set(id, parentID);
@@ -1785,6 +1825,14 @@ export default class Store extends EventEmitter<{
break;
}
+ const prevRects = suspense.rects;
+ if (prevRects !== null && suspense.parentID !== 0) {
+ // Delete all the existing rects from the R-tree
+ for (let j = 0; j < prevRects.length; j++) {
+ this._rtree.remove(prevRects[j]);
+ }
+ }
+
let nextRects: SuspenseNode['rects'];
if (numRects === -1) {
nextRects = null;
@@ -1796,7 +1844,12 @@ export default class Store extends EventEmitter<{
const width = operations[i + 2] / 1000;
const height = operations[i + 3] / 1000;
- nextRects.push({x, y, width, height});
+ const rect = {x, y, width, height};
+ if (suspense.parentID !== 0) {
+ // Track all rects except the root.
+ this._rtree.insert(rect);
+ }
+ nextRects.push(rect);
i += 4;
}
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
index db0a84d8d8..d8dd990850 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
@@ -39,6 +39,24 @@
pointer-events: none;
}
+.SuspenseRectsTitle {
+ pointer-events: none;
+ color: var(--color-text);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: var(--font-size-sans-small);
+ line-height: var(--font-size-sans-small);
+ padding: .25rem;
+ container-type: size;
+ container-name: title;
+}
+
+@container title (width < 30px) or (height < 12px) {
+ .SuspenseRectsTitle > span {
+ display: none;
+ }
+}
+
.SuspenseRectsScaledRect[data-visible='false'] > .SuspenseRectsBoundaryChildren {
overflow: initial;
}
@@ -75,7 +93,7 @@
transition: background-color 0.2s ease-out;
}
-.SuspenseRectsBoundary[data-selected='true'] {
+.SuspenseRectsBoundary[data-selected='true'][data-visible='true'] {
box-shadow: var(--elevation-4);
}
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
index 67a6bf2773..d5f44eeb32 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
@@ -31,6 +31,7 @@ import {
SuspenseTreeDispatcherContext,
} from './SuspenseTreeContext';
import {getClassNameForEnvironment} from './SuspenseEnvironmentColors.js';
+import type RBush from 'rbush';
function ScaledRect({
className,
@@ -78,8 +79,10 @@ function ScaledRect({
function SuspenseRects({
suspenseID,
+ parentRects,
}: {
suspenseID: SuspenseNode['id'],
+ parentRects: null | Array,
}): React$Node {
const store = useContext(StoreContext);
const treeDispatch = useContext(TreeDispatcherContext);
@@ -167,7 +170,20 @@ function SuspenseRects({
}
}
- const boundingBox = getBoundingBox(suspense.rects);
+ const rects = suspense.rects;
+ const boundingBox = getBoundingBox(rects);
+
+ // Next we'll try to find a rect within one of our rects that isn't intersecting with
+ // other rects.
+ // TODO: This should probably be memoized based on if any changes to the rtree has been made.
+ const titleBox: null | Rect =
+ rects === null ? null : findTitleBox(store._rtree, rects, parentRects);
+ const nextRects =
+ rects === null || rects.length === 0
+ ? parentRects
+ : parentRects === null || parentRects.length === 0
+ ? rects
+ : parentRects.concat(rects);
return (
{suspense.children.map(childID => {
- return ;
+ return (
+
+ );
})}
)}
- {selected ? (
+ {titleBox && suspense.name && visible ? (
+
+ {suspense.name}
+
+ ) : null}
+ {selected && visible ? (
,
+ rects: Array,
+ parentRects: null | Array,
+): null | Rect {
+ for (let i = 0; i < rects.length; i++) {
+ const rect = rects[i];
+ if (rect.width < 20 || rect.height < 10) {
+ // Skip small rects. They're likely not able to be contain anything useful anyway.
+ continue;
+ }
+ // Find all overlapping rects elsewhere in the tree to limit our rect.
+ const overlappingRects = rtree.search({
+ minX: rect.x,
+ minY: rect.y,
+ maxX: rect.x + rect.width,
+ maxY: rect.y + rect.height,
+ });
+ if (
+ overlappingRects.length === 0 ||
+ (overlappingRects.length === 1 && overlappingRects[0] === rect)
+ ) {
+ // There are no overlapping rects that isn't our own rect, so we can just use
+ // the full space of the rect.
+ return rect;
+ }
+ // We have some overlapping rects but they might not overlap everything. Let's
+ // shrink it up toward the top left corner until it has no more overlap.
+ const minX = rect.x;
+ const minY = rect.y;
+ let maxX = rect.x + rect.width;
+ let maxY = rect.y + rect.height;
+ for (let j = 0; j < overlappingRects.length; j++) {
+ const overlappingRect = overlappingRects[j];
+ if (overlappingRect === rect) {
+ continue;
+ }
+ const x = overlappingRect.x;
+ const y = overlappingRect.y;
+ if (y < maxY && x < maxX) {
+ if (
+ parentRects !== null &&
+ parentRects.indexOf(overlappingRect) !== -1
+ ) {
+ // This rect overlaps but it's part of a parent boundary. We let
+ // title content render if it's on top and not a sibling.
+ continue;
+ }
+ // This rect cuts into the remaining space. Let's figure out if we're
+ // better off cutting on the x or y axis to maximize remaining space.
+ const remainderX = x - minX;
+ const remainderY = y - minY;
+ if (remainderX > remainderY) {
+ maxX = x;
+ } else {
+ maxY = y;
+ }
+ }
+ }
+ if (maxX > minX && maxY > minY) {
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+ }
+ }
+ return null;
+}
+
function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node {
const store = useContext(StoreContext);
const root = store.getSuspenseByID(rootID);
@@ -329,7 +427,9 @@ function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node {
}
return root.children.map(childID => {
- return ;
+ return (
+
+ );
});
}
diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js
index 5556fad1a0..f201c2e0e8 100644
--- a/scripts/flow/environment.js
+++ b/scripts/flow/environment.js
@@ -456,3 +456,191 @@ declare class NavigationDestination {
getState(): mixed;
}
+
+// Ported from definitely-typed
+declare module 'rbush' {
+ declare interface BBox {
+ minX: number;
+ minY: number;
+ maxX: number;
+ maxY: number;
+ }
+
+ declare export default class RBush {
+ /**
+ * Constructs an `RBush`, a high-performance 2D spatial index for points and
+ * rectangles. Based on an optimized __R-tree__ data structure with
+ * __bulk-insertion__ support.
+ *
+ * @param maxEntries An optional argument to RBush defines the maximum
+ * number of entries in a tree node. `9` (used by default)
+ * is a reasonable choice for most applications. Higher
+ * value means faster insertion and slower search, and
+ * vice versa.
+ */
+ constructor(maxEntries?: number): void;
+
+ /**
+ * Inserts an item. To insert many items at once, use `load()`.
+ *
+ * @param item The item to insert.
+ */
+ insert(item: T): RBush;
+
+ /**
+ * Bulk-inserts the given items into the tree.
+ *
+ * Bulk insertion is usually ~2-3 times faster than inserting items one by
+ * one. After bulk loading (bulk insertion into an empty tree), subsequent
+ * query performance is also ~20-30% better.
+ *
+ * Note that when you do bulk insertion into an existing tree, it bulk-loads
+ * the given data into a separate tree and inserts the smaller tree into the
+ * larger tree. This means that bulk insertion works very well for clustered
+ * data (where items in one update are close to each other), but makes query
+ * performance worse if the data is scattered.
+ *
+ * @param items The items to load.
+ */
+ load(items: $ReadOnlyArray): RBush;
+
+ /**
+ * Removes a previously inserted item, comparing by reference.
+ *
+ * To remove all items, use `clear()`.
+ *
+ * @param item The item to remove.
+ * @param equals A custom function that allows comparing by value instead.
+ * Useful when you have only a copy of the object you need
+ * removed (e.g. loaded from server).
+ */
+ remove(item: T, equals?: (a: T, b: T) => boolean): RBush;
+
+ /**
+ * Removes all items.
+ */
+ clear(): RBush;
+
+ /**
+ * Returns an array of data items (points or rectangles) that the given
+ * bounding box intersects.
+ *
+ * Note that the search method accepts a bounding box in `{minX, minY, maxX,
+ * maxY}` format regardless of the data format.
+ *
+ * @param box The bounding box in which to search.
+ */
+ search(box: BBox): T[];
+
+ /**
+ * Returns all items contained in the tree.
+ */
+ all(): T[];
+
+ /**
+ * Returns `true` if there are any items intersecting the given bounding
+ * box, otherwise `false`.
+ *
+ * @param box The bounding box in which to search.
+ */
+ collides(box: BBox): boolean;
+
+ /**
+ * Returns the bounding box for the provided item.
+ *
+ * By default, `RBush` assumes the format of data points to be an object
+ * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
+ * custom item format by overriding `toBBox()`, `compareMinX()`, and
+ * `compareMinY()`.
+ *
+ * @example
+ * class MyRBush extends RBush {
+ * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
+ * compareMinX(a, b) { return a.x - b.x; }
+ * compareMinY(a, b) { return a.y - b.y; }
+ * }
+ * const tree = new MyRBush<[number, number]>();
+ * tree.insert([20, 50]); // accepts [x, y] points
+ *
+ * @param item The item whose bounding box should be returned.
+ */
+ toBBox(item: T): BBox;
+
+ /**
+ * Compares the minimum x coordinate of two items. Returns -1 if `a`'s
+ * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if
+ * they're equal.
+ *
+ * By default, `RBush` assumes the format of data points to be an object
+ * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
+ * custom item format by overriding `toBBox()`, `compareMinX()`, and
+ * `compareMinY()`.
+ *
+ * @example
+ * class MyRBush extends RBush {
+ * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
+ * compareMinX(a, b) { return a.x - b.x; }
+ * compareMinY(a, b) { return a.y - b.y; }
+ * }
+ * const tree = new MyRBush<[number, number]>();
+ * tree.insert([20, 50]); // accepts [x, y] points
+ *
+ * @param a The first item to compare.
+ * @param b The second item to compare.
+ */
+ compareMinX(a: T, b: T): number;
+
+ /**
+ * Compares the minimum y coordinate of two items. Returns -1 if `a`'s
+ * x-coordinate is smaller, 1 if `b`'s x coordinate is smaller, or 0 if
+ * they're equal.
+ *
+ * By default, `RBush` assumes the format of data points to be an object
+ * with `minX`, `minY`, `maxX`, and `maxY`. However, you can specify a
+ * custom item format by overriding `toBBox()`, `compareMinX()`, and
+ * `compareMinY()`.
+ *
+ * @example
+ * class MyRBush extends RBush {
+ * toBBox([x, y]) { return { minX: x, minY: y, maxX: x, maxY: y }; }
+ * compareMinX(a, b) { return a.x - b.x; }
+ * compareMinY(a, b) { return a.y - b.y; }
+ * }
+ * const tree = new MyRBush<[number, number]>();
+ * tree.insert([20, 50]); // accepts [x, y] points
+ *
+ * @param a The first item to compare.
+ * @param b The second item to compare.
+ */
+ compareMinY(a: T, b: T): number;
+
+ /**
+ * Exports the tree's contents as a JSON object.
+ *
+ * Importing and exporting as JSON allows you to use RBush on both the
+ * server (using Node.js) and the browser combined, e.g. first indexing the
+ * data on the server and and then importing the resulting tree data on the
+ * client for searching.
+ *
+ * Note that the `maxEntries` option from the constructor must be the same
+ * in both trees for export/import to work properly.
+ */
+ toJSON(): any;
+
+ /**
+ * Imports previously exported data into the tree (i.e., data that was
+ * emitted by `toJSON()`).
+ *
+ * Importing and exporting as JSON allows you to use RBush on both the
+ * server (using Node.js) and the browser combined, e.g. first indexing the
+ * data on the server and and then importing the resulting tree data on the
+ * client for searching.
+ *
+ * Note that the `maxEntries` option from the constructor must be the same
+ * in both trees for export/import to work properly.
+ *
+ * @param data The previously exported JSON data.
+ */
+ fromJSON(data: any): RBush;
+ }
+}
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index 9baf33a39b..3f03b50c3a 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -63,7 +63,7 @@ module.exports = Object.assign({}, baseConfig, {
testPathIgnorePatterns: ['/node_modules/', '-test.internal.js$'],
// Exclude the build output from transforms
transformIgnorePatterns: [
- '/node_modules/',
+ '/node_modules/(?!(rbush|quickselect)/)',
'/build/',
'/__compiled__/',
'/__untransformed__/',
diff --git a/yarn.lock b/yarn.lock
index ad8a3f0085..2543e4d176 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8271,7 +8271,7 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0:
dependencies:
eslint-visitor-keys "^1.1.0"
-"eslint-v7@npm:eslint@^7.7.0":
+"eslint-v7@npm:eslint@^7.7.0", eslint@^7.7.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
@@ -8470,52 +8470,6 @@ eslint@8.57.0:
strip-ansi "^6.0.1"
text-table "^0.2.0"
-eslint@^7.7.0:
- version "7.32.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
- integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
- dependencies:
- "@babel/code-frame" "7.12.11"
- "@eslint/eslintrc" "^0.4.3"
- "@humanwhocodes/config-array" "^0.5.0"
- ajv "^6.10.0"
- chalk "^4.0.0"
- cross-spawn "^7.0.2"
- debug "^4.0.1"
- doctrine "^3.0.0"
- enquirer "^2.3.5"
- escape-string-regexp "^4.0.0"
- eslint-scope "^5.1.1"
- eslint-utils "^2.1.0"
- eslint-visitor-keys "^2.0.0"
- espree "^7.3.1"
- esquery "^1.4.0"
- esutils "^2.0.2"
- fast-deep-equal "^3.1.3"
- file-entry-cache "^6.0.1"
- functional-red-black-tree "^1.0.1"
- glob-parent "^5.1.2"
- globals "^13.6.0"
- ignore "^4.0.6"
- import-fresh "^3.0.0"
- imurmurhash "^0.1.4"
- is-glob "^4.0.0"
- js-yaml "^3.13.1"
- json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.4.1"
- lodash.merge "^4.6.2"
- minimatch "^3.0.4"
- natural-compare "^1.4.0"
- optionator "^0.9.1"
- progress "^2.0.0"
- regexpp "^3.1.0"
- semver "^7.2.1"
- strip-ansi "^6.0.0"
- strip-json-comments "^3.1.0"
- table "^6.0.9"
- text-table "^0.2.0"
- v8-compile-cache "^2.0.3"
-
espree@10.0.1, espree@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-10.0.1.tgz#600e60404157412751ba4a6f3a2ee1a42433139f"
@@ -14317,7 +14271,7 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-"prettier-2@npm:prettier@^2":
+"prettier-2@npm:prettier@^2", prettier@^2.5.1:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
@@ -14332,11 +14286,6 @@ prettier@^1.19.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
-prettier@^2.5.1:
- version "2.8.8"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
- integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
-
pretty-format@^29.4.1:
version "29.4.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.4.1.tgz#0da99b532559097b8254298da7c75a0785b1751c"
@@ -14602,6 +14551,11 @@ quick-tmp@0.0.0:
first-match "0.0.1"
osenv "0.0.3"
+quickselect@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
+ integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==
+
random-seed@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
@@ -14639,6 +14593,13 @@ raw-loader@^3.1.0:
loader-utils "^1.1.0"
schema-utils "^2.0.1"
+rbush@4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d"
+ integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==
+ dependencies:
+ quickselect "^3.0.0"
+
rc@1.2.8, rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -16211,7 +16172,7 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -16246,15 +16207,6 @@ string-width@^4.0.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -16315,7 +16267,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -16343,13 +16295,6 @@ strip-ansi@^5.1.0:
dependencies:
ansi-regex "^4.1.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -17958,7 +17903,7 @@ workerize-loader@^2.0.2:
dependencies:
loader-utils "^2.0.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -17976,15 +17921,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
From 1440f4f42d59a7de4559dac972b62d9be771d1d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 22:52:50 -0700
Subject: [PATCH 08/25] [DevTools] BuiltInCallSite should have padding-left
(#34922)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We don't normally show this but when we do, it should have the same
padding as other callsites.
---
.../src/devtools/views/Components/StackTraceView.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
index 574ceb0236..bd13a89c6c 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
+++ b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
@@ -2,7 +2,7 @@
padding: 0.25rem;
}
-.CallSite {
+.CallSite, .BuiltInCallSite {
display: block;
padding-left: 1rem;
}
From 21272a680f07cb69873eb3668e7baaebfcf05606 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Sun, 19 Oct 2025 23:42:38 -0700
Subject: [PATCH 09/25] Lower case "rsc stream" debug info (#34921)
This is an aesthetic thing. Most simple I/O entries are things like
"script", "stylesheet", "fetch" etc. which are all a single word and
lower case. The "RSC stream" name sticks out and draws unnecessary
attention to itself where as it's really the least interesting to look
at.
I don't love the name because I'm not sure how to explain it. It's
really mainly the byte size of the payload itself without considering
things like server awaits things which will have their own cause. So I'm
trying to communicate the download size of the stream of downloading the
`.rsc` file or the `"rsc stream"`.
---
packages/internal-test-utils/debugInfo.js | 4 ++--
.../react-client/src/ReactFlightClient.js | 2 +-
.../src/backend/fiber/renderer.js | 10 ++++++--
.../ReactFlightAsyncDebugInfo-test.js | 24 +++++++++----------
4 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/packages/internal-test-utils/debugInfo.js b/packages/internal-test-utils/debugInfo.js
index 7b9c730ba6..75183ce4b6 100644
--- a/packages/internal-test-utils/debugInfo.js
+++ b/packages/internal-test-utils/debugInfo.js
@@ -64,7 +64,7 @@ function normalizeIOInfo(config: DebugInfoConfig, ioInfo) {
if (promise) {
promise.then(); // init
if (promise.status === 'fulfilled') {
- if (ioInfo.name === 'RSC stream') {
+ if (ioInfo.name === 'rsc stream') {
copy.byteSize = 0;
copy.value = {
value: 'stream',
@@ -117,7 +117,7 @@ export function getDebugInfo(config: DebugInfoConfig, obj) {
for (let i = 0; i < debugInfo.length; i++) {
if (
debugInfo[i].awaited &&
- debugInfo[i].awaited.name === 'RSC stream' &&
+ debugInfo[i].awaited.name === 'rsc stream' &&
config.ignoreRscStreamInfo
) {
// Ignore RSC stream I/O info.
diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js
index 8bcabfb556..c7506a13e5 100644
--- a/packages/react-client/src/ReactFlightClient.js
+++ b/packages/react-client/src/ReactFlightClient.js
@@ -2721,7 +2721,7 @@ export function createStreamState(
(debugValuePromise: any).status = 'fulfilled';
(debugValuePromise: any).value = streamDebugValue;
streamState._debugInfo = {
- name: 'RSC stream',
+ name: 'rsc stream',
start: response._debugStartTime,
end: response._debugStartTime, // will be updated once we finish a chunk
byteSize: 0, // will be updated as we resolve a data chunk
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 4a61fba652..fe8722da56 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -6171,7 +6171,10 @@ export function attach(
}
}
const newIO = asyncInfo.awaited;
- if (newIO.name === 'RSC stream' && newIO.value != null) {
+ if (
+ (newIO.name === 'RSC stream' || newIO.name === 'rsc stream') &&
+ newIO.value != null
+ ) {
const streamPromise = newIO.value;
// Special case RSC stream entries to pick the last entry keyed by the stream.
const existingEntry = streamEntries.get(streamPromise);
@@ -6230,7 +6233,10 @@ export function attach(
continue;
}
foundIOEntries.add(ioInfo);
- if (ioInfo.name === 'RSC stream' && ioInfo.value != null) {
+ if (
+ (ioInfo.name === 'RSC stream' || ioInfo.name === 'rsc stream') &&
+ ioInfo.value != null
+ ) {
const streamPromise = ioInfo.value;
// Special case RSC stream entries to pick the last entry keyed by the stream.
const existingEntry = streamEntries.get(streamPromise);
diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
index 00c6f78503..db1d7af3a6 100644
--- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
+++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
@@ -459,7 +459,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -867,7 +867,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -985,7 +985,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -1460,7 +1460,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"byteSize": 0,
"end": 0,
"env": "Server",
- "name": "RSC stream",
+ "name": "rsc stream",
"start": 0,
"value": {
"value": "stream",
@@ -1475,7 +1475,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -1789,7 +1789,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -2083,7 +2083,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -2499,7 +2499,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -2665,7 +2665,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -2845,7 +2845,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -3130,7 +3130,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
@@ -3275,7 +3275,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
"awaited": {
"byteSize": 0,
"end": 0,
- "name": "RSC stream",
+ "name": "rsc stream",
"owner": null,
"start": 0,
"value": {
From 02c80f0d8702cb894f6ef9748e7b18ffdd388a55 Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Mon, 20 Oct 2025 13:39:42 +0100
Subject: [PATCH 10/25] [DevTools] fix: dont ship source maps for css in prod
builds (#34913)
This has been causing some issues with the submission review on Firefox
store: we use OS-level paths in these source maps, which makes the build
artifact different from the one that's been submitted.
Also saves ~100Kb for main.js artifact.
---
.../src/main/cloneStyleTags.js | 33 +++++++++++++++----
.../src/main/index.js | 2 +-
.../webpack.config.js | 2 +-
3 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/packages/react-devtools-extensions/src/main/cloneStyleTags.js b/packages/react-devtools-extensions/src/main/cloneStyleTags.js
index dd84e01fc9..caad2a361c 100644
--- a/packages/react-devtools-extensions/src/main/cloneStyleTags.js
+++ b/packages/react-devtools-extensions/src/main/cloneStyleTags.js
@@ -1,5 +1,14 @@
-function cloneStyleTags() {
- const linkTags = [];
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+export function cloneStyleTags(): Array {
+ const tags: Array = [];
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const linkTag of document.getElementsByTagName('link')) {
@@ -11,11 +20,23 @@ function cloneStyleTags() {
newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue);
}
- linkTags.push(newLinkTag);
+ tags.push(newLinkTag);
}
}
- return linkTags;
-}
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const styleTag of document.getElementsByTagName('style')) {
+ const newStyleTag = document.createElement('style');
-export default cloneStyleTags;
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const attribute of styleTag.attributes) {
+ newStyleTag.setAttribute(attribute.nodeName, attribute.nodeValue);
+ }
+
+ newStyleTag.textContent = styleTag.textContent;
+
+ tags.push(newStyleTag);
+ }
+
+ return tags;
+}
diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js
index cec9fd6df4..bc948d1e6d 100644
--- a/packages/react-devtools-extensions/src/main/index.js
+++ b/packages/react-devtools-extensions/src/main/index.js
@@ -33,7 +33,7 @@ import {
import {viewAttributeSource} from './sourceSelection';
import {startReactPolling} from './reactPolling';
-import cloneStyleTags from './cloneStyleTags';
+import {cloneStyleTags} from './cloneStyleTags';
import fetchFileWithCaching from './fetchFileWithCaching';
import injectBackendManager from './injectBackendManager';
import registerEventsLogger from './registerEventsLogger';
diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js
index 4592363c64..191eabc47c 100644
--- a/packages/react-devtools-extensions/webpack.config.js
+++ b/packages/react-devtools-extensions/webpack.config.js
@@ -285,7 +285,7 @@ module.exports = {
{
loader: 'css-loader',
options: {
- sourceMap: true,
+ sourceMap: __DEV__,
modules: true,
localIdentName: '[local]___[hash:base64:5]',
},
From aaad0ea055ce0c10b263db8505338cb1eedb86de Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Mon, 20 Oct 2025 16:14:47 +0100
Subject: [PATCH 11/25] [DevTools] chore: read from build/COMMIT_SHA fle as
fallback for commit hash (#34915)
This eliminates the gap in a reproducer for the React DevTools browser
extension from the source code that we submit to Firefox extension
stores.
We use the commit hash as part of the Backend version, here:
https://github.com/facebook/react/blob/2cfb221937eac48209d01d5dda5664de473b1953/packages/react-devtools-extensions/utils.js#L26-L38
The problem is that we archive the source code for Mozilla extension
store reviews and there is no git. But since we still download the React
sources from the CI, we could reuse the hash from `build/COMMIT_HASH`
file.
---
packages/react-devtools-extensions/utils.js | 24 ++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/packages/react-devtools-extensions/utils.js b/packages/react-devtools-extensions/utils.js
index 3b06bb71f7..9f02e98d19 100644
--- a/packages/react-devtools-extensions/utils.js
+++ b/packages/react-devtools-extensions/utils.js
@@ -6,7 +6,7 @@
*/
const {execSync} = require('child_process');
-const {readFileSync} = require('fs');
+const {existsSync, readFileSync} = require('fs');
const {resolve} = require('path');
const GITHUB_URL = 'https://github.com/facebook/react';
@@ -18,8 +18,26 @@ function getGitCommit() {
.trim();
} catch (error) {
// Mozilla runs this command from a git archive.
- // In that context, there is no Git revision.
- return null;
+ // In that context, there is no Git context.
+ // Using the commit hash specified to download-experimental-build.js script as a fallback.
+
+ // Try to read from build/COMMIT_SHA file
+ const commitShaPath = resolve(__dirname, '..', '..', 'build', 'COMMIT_SHA');
+ if (!existsSync(commitShaPath)) {
+ throw new Error(
+ 'Could not find build/COMMIT_SHA file. Did you run scripts/release/download-experimental-build.js script?',
+ );
+ }
+
+ try {
+ const commitHash = readFileSync(commitShaPath, 'utf8').trim();
+ // Return short hash (first 7 characters) to match abbreviated commit hash format
+ return commitHash.slice(0, 7);
+ } catch (readError) {
+ throw new Error(
+ `Failed to read build/COMMIT_SHA file: ${readError.message}`,
+ );
+ }
}
}
From 2bcbf254f168ddec567156f802d19315e64e4aa8 Mon Sep 17 00:00:00 2001
From: Joseph Savona <6425824+josephsavona@users.noreply.github.com>
Date: Mon, 20 Oct 2025 08:42:04 -0700
Subject: [PATCH 12/25] [compiler] Fix false positive for useMemo reassigning
context vars (#34904)
Within a function expression local variables may use StoreContext for
local context variables, so the reassignment check here was firing too
often. We should only report an error for variables that are declared
outside the function, ie part of its `context`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34904).
* #34903
* __->__ #34904
---
.../src/Validation/ValidateUseMemo.ts | 31 +++++++------
.../reassign-variable-in-usememo.expect.md | 45 +++++++++++++++++++
.../compiler/reassign-variable-in-usememo.js | 12 +++++
3 files changed, 74 insertions(+), 14 deletions(-)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.js
diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts
index 05a4b4b91f..2bccda3a2e 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts
@@ -184,25 +184,28 @@ function validateNoContextVariableAssignment(
fn: HIRFunction,
errors: CompilerError,
): void {
+ const context = new Set(fn.context.map(place => place.identifier.id));
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
const value = instr.value;
switch (value.kind) {
case 'StoreContext': {
- errors.pushDiagnostic(
- CompilerDiagnostic.create({
- category: ErrorCategory.UseMemo,
- reason:
- 'useMemo() callbacks may not reassign variables declared outside of the callback',
- description:
- 'useMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function',
- suggestions: null,
- }).withDetails({
- kind: 'error',
- loc: value.lvalue.place.loc,
- message: 'Cannot reassign variable',
- }),
- );
+ if (context.has(value.lvalue.place.identifier.id)) {
+ errors.pushDiagnostic(
+ CompilerDiagnostic.create({
+ category: ErrorCategory.UseMemo,
+ reason:
+ 'useMemo() callbacks may not reassign variables declared outside of the callback',
+ description:
+ 'useMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function',
+ suggestions: null,
+ }).withDetails({
+ kind: 'error',
+ loc: value.lvalue.place.loc,
+ message: 'Cannot reassign variable',
+ }),
+ );
+ }
break;
}
}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.expect.md
new file mode 100644
index 0000000000..29dc3afcd6
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.expect.md
@@ -0,0 +1,45 @@
+
+## Input
+
+```javascript
+// @flow
+export hook useItemLanguage(items) {
+ return useMemo(() => {
+ let language: ?string = null;
+ items.forEach(item => {
+ if (item.language != null) {
+ language = item.language;
+ }
+ });
+ return language;
+ }, [items]);
+}
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+export function useItemLanguage(items) {
+ const $ = _c(2);
+ let language;
+ if ($[0] !== items) {
+ language = null;
+ items.forEach((item) => {
+ if (item.language != null) {
+ language = item.language;
+ }
+ });
+ $[0] = items;
+ $[1] = language;
+ } else {
+ language = $[1];
+ }
+ return language;
+}
+
+```
+
+### Eval output
+(kind: exception) Fixture not implemented
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.js
new file mode 100644
index 0000000000..4ed89bfc64
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reassign-variable-in-usememo.js
@@ -0,0 +1,12 @@
+// @flow
+export hook useItemLanguage(items) {
+ return useMemo(() => {
+ let language: ?string = null;
+ items.forEach(item => {
+ if (item.language != null) {
+ language = item.language;
+ }
+ });
+ return language;
+ }, [items]);
+}
From 1d3664665b4e52bd1daee775e1e95feb563799d9 Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann"
Date: Mon, 20 Oct 2025 19:33:47 +0200
Subject: [PATCH 13/25] [DevTools] Text layout fixes for stack traces with
badges (#34925)
---
.../src/devtools/views/Components/StackTraceView.css | 9 ++++++---
.../src/devtools/views/Components/StackTraceView.js | 6 ++++--
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
index bd13a89c6c..98200ee9df 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
+++ b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.css
@@ -3,8 +3,9 @@
}
.CallSite, .BuiltInCallSite {
- display: block;
+ display: flex;
padding-left: 1rem;
+ white-space-collapse: preserve;
}
.IgnoredCallSite, .BuiltInCallSite {
@@ -20,13 +21,15 @@
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
- flex: 1;
cursor: pointer;
border-radius: 0.125rem;
- padding: 0px 2px;
}
.Link:hover {
background-color: var(--color-background-hover);
}
+.ElementBadges {
+ margin-left: 0.25rem;
+}
+
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
index d3d797f781..a3cf91f14b 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/StackTraceView.js
@@ -86,8 +86,10 @@ export function CallSiteView({
>
)}
-
-
+
);
}
From 3cde211b0cb1c707114c27d7fd23683d02086e31 Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Mon, 20 Oct 2025 18:39:28 +0100
Subject: [PATCH 14/25] React DevTools 7.0.0 -> 7.0.1 (#34926)
Full list of changes:
* Text layout fixes for stack traces with badges
([eps1lon](https://github.com/eps1lon) in
[#34925](https://github.com/facebook/react/pull/34925))
* chore: read from build/COMMIT_SHA fle as fallback for commit hash
([hoxyq](https://github.com/hoxyq) in
[#34915](https://github.com/facebook/react/pull/34915))
* fix: dont ship source maps for css in prod builds
([hoxyq](https://github.com/hoxyq) in
[#34913](https://github.com/facebook/react/pull/34913))
* Lower case "rsc stream" debug info
([sebmarkbage](https://github.com/sebmarkbage) in
[#34921](https://github.com/facebook/react/pull/34921))
* BuiltInCallSite should have padding-left
([sebmarkbage](https://github.com/sebmarkbage) in
[#34922](https://github.com/facebook/react/pull/34922))
* Show the Suspense boundary name in the rect if there's no overlap
([sebmarkbage](https://github.com/sebmarkbage) in
[#34918](https://github.com/facebook/react/pull/34918))
* Don't attach filtered IO to grandparent Suspense
([eps1lon](https://github.com/eps1lon) in
[#34916](https://github.com/facebook/react/pull/34916))
* Infer name from stack if it's the generic "lazy" name
([sebmarkbage](https://github.com/sebmarkbage) in
[#34907](https://github.com/facebook/react/pull/34907))
* Use same Suspense naming heuristics when reconnecting
([eps1lon](https://github.com/eps1lon) in
[#34898](https://github.com/facebook/react/pull/34898))
* Assign a different color and label based on environment
([sebmarkbage](https://github.com/sebmarkbage) in
[#34893](https://github.com/facebook/react/pull/34893))
* Compute environment names for the timeline
([sebmarkbage](https://github.com/sebmarkbage) in
[#34892](https://github.com/facebook/react/pull/34892))
* Don't highlight the root rect if no roots has unique suspenders
([sebmarkbage](https://github.com/sebmarkbage) in
[#34885](https://github.com/facebook/react/pull/34885))
* Highlight the rect when the corresponding timeline bean is hovered
([sebmarkbage](https://github.com/sebmarkbage) in
[#34881](https://github.com/facebook/react/pull/34881))
* Repeat the "name" if there's no short description in groups
([sebmarkbage](https://github.com/sebmarkbage) in
[#34894](https://github.com/facebook/react/pull/34894))
* Tweak the rects design and create multi-environment color scheme
([sebmarkbage](https://github.com/sebmarkbage) in
[#34880](https://github.com/facebook/react/pull/34880))
* Adjust the rects size by one pixel smaller
([sebmarkbage](https://github.com/sebmarkbage) in
[#34876](https://github.com/facebook/react/pull/34876))
* Remove steps title from scrubber
([sebmarkbage](https://github.com/sebmarkbage) in
[#34878](https://github.com/facebook/react/pull/34878))
* Include some sub-pixel precision in rects
([sebmarkbage](https://github.com/sebmarkbage) in
[#34873](https://github.com/facebook/react/pull/34873))
* Don't pluralize if already plural
([sebmarkbage](https://github.com/sebmarkbage) in
[#34870](https://github.com/facebook/react/pull/34870))
* Don't try to load anonymous or empty urls
([sebmarkbage](https://github.com/sebmarkbage) in
[#34869](https://github.com/facebook/react/pull/34869))
* Add inspection button to Suspense tab
([sebmarkbage](https://github.com/sebmarkbage) in
[#34867](https://github.com/facebook/react/pull/34867))
* Don't select on hover ([sebmarkbage](https://github.com/sebmarkbage)
in [#34860](https://github.com/facebook/react/pull/34860))
* Don't highlight on timeline
([sebmarkbage](https://github.com/sebmarkbage) in
[#34861](https://github.com/facebook/react/pull/34861))
* The bridge event types should only be defined in one direction
([sebmarkbage](https://github.com/sebmarkbage) in
[#34859](https://github.com/facebook/react/pull/34859))
* Attempt at a better "unique suspender" text
([sebmarkbage](https://github.com/sebmarkbage) in
[#34854](https://github.com/facebook/react/pull/34854))
* Track whether a boundary is currently suspended and make transparent
([sebmarkbage](https://github.com/sebmarkbage) in
[#34853](https://github.com/facebook/react/pull/34853))
* Don't hide overflow rectangles
([sebmarkbage](https://github.com/sebmarkbage) in
[#34852](https://github.com/facebook/react/pull/34852))
* Measure text nodes ([sebmarkbage](https://github.com/sebmarkbage) in
[#34851](https://github.com/facebook/react/pull/34851))
* Don't measure fallbacks when suspended
([sebmarkbage](https://github.com/sebmarkbage) in
[#34850](https://github.com/facebook/react/pull/34850))
* Filter out built-in stack frames
([sebmarkbage](https://github.com/sebmarkbage) in
[#34828](https://github.com/facebook/react/pull/34828))
* Exclude Suspense boundaries in hidden Activity
([eps1lon](https://github.com/eps1lon) in
[#34756](https://github.com/facebook/react/pull/34756))
* Group consecutive suspended by rows by the same name
([sebmarkbage](https://github.com/sebmarkbage) in
[#34830](https://github.com/facebook/react/pull/34830))
* Preserve the original index when sorting suspended by
([sebmarkbage](https://github.com/sebmarkbage) in
[#34829](https://github.com/facebook/react/pull/34829))
* Don't show the root as being non-compliant
([sebmarkbage](https://github.com/sebmarkbage) in
[#34827](https://github.com/facebook/react/pull/34827))
* Ignore suspense boundaries, without visual representation, in the
timeline ([sebmarkbage](https://github.com/sebmarkbage) in
[#34824](https://github.com/facebook/react/pull/34824))
* Explicitly say which id to scroll to and only once
([sebmarkbage](https://github.com/sebmarkbage) in
[#34823](https://github.com/facebook/react/pull/34823))
* devtools: fix ellipsis truncation for key values
([sophiebits](https://github.com/sophiebits) in
[#34796](https://github.com/facebook/react/pull/34796))
* fix(devtools): remove duplicated "Display density" field in General
settings ([Anatole-Godard](https://github.com/Anatole-Godard) in
[#34792](https://github.com/facebook/react/pull/34792))
* Gate SuspenseTab ([hoxyq](https://github.com/hoxyq) in
[#34754](https://github.com/facebook/react/pull/34754))
* Release ` ` to Canary
([eps1lon](https://github.com/eps1lon) in
[#34712](https://github.com/facebook/react/pull/34712))
---
packages/react-devtools-core/package.json | 2 +-
packages/react-devtools-extensions/chrome/manifest.json | 4 ++--
packages/react-devtools-extensions/edge/manifest.json | 4 ++--
packages/react-devtools-extensions/firefox/manifest.json | 2 +-
packages/react-devtools-inline/package.json | 2 +-
packages/react-devtools-timeline/package.json | 2 +-
packages/react-devtools/CHANGELOG.md | 9 +++++++++
packages/react-devtools/package.json | 4 ++--
8 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json
index c8facd1a24..54b89e72e8 100644
--- a/packages/react-devtools-core/package.json
+++ b/packages/react-devtools-core/package.json
@@ -1,6 +1,6 @@
{
"name": "react-devtools-core",
- "version": "7.0.0",
+ "version": "7.0.1",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"main": "./dist/backend.js",
diff --git a/packages/react-devtools-extensions/chrome/manifest.json b/packages/react-devtools-extensions/chrome/manifest.json
index 0b5285e91a..9bb0ab1177 100644
--- a/packages/react-devtools-extensions/chrome/manifest.json
+++ b/packages/react-devtools-extensions/chrome/manifest.json
@@ -2,8 +2,8 @@
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Chrome Developer Tools.",
- "version": "7.0.0",
- "version_name": "7.0.0",
+ "version": "7.0.1",
+ "version_name": "7.0.1",
"minimum_chrome_version": "114",
"icons": {
"16": "icons/16-production.png",
diff --git a/packages/react-devtools-extensions/edge/manifest.json b/packages/react-devtools-extensions/edge/manifest.json
index 342af4abb3..55b7248f25 100644
--- a/packages/react-devtools-extensions/edge/manifest.json
+++ b/packages/react-devtools-extensions/edge/manifest.json
@@ -2,8 +2,8 @@
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
- "version": "7.0.0",
- "version_name": "7.0.0",
+ "version": "7.0.1",
+ "version_name": "7.0.1",
"minimum_chrome_version": "114",
"icons": {
"16": "icons/16-production.png",
diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json
index bc12ac0fcd..f401708c21 100644
--- a/packages/react-devtools-extensions/firefox/manifest.json
+++ b/packages/react-devtools-extensions/firefox/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Firefox Developer Tools.",
- "version": "7.0.0",
+ "version": "7.0.1",
"browser_specific_settings": {
"gecko": {
"id": "@react-devtools",
diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json
index 0363530cd1..cc5fc51a6e 100644
--- a/packages/react-devtools-inline/package.json
+++ b/packages/react-devtools-inline/package.json
@@ -1,6 +1,6 @@
{
"name": "react-devtools-inline",
- "version": "7.0.0",
+ "version": "7.0.1",
"description": "Embed react-devtools within a website",
"license": "MIT",
"main": "./dist/backend.js",
diff --git a/packages/react-devtools-timeline/package.json b/packages/react-devtools-timeline/package.json
index 2f54399ce2..5e4df83552 100644
--- a/packages/react-devtools-timeline/package.json
+++ b/packages/react-devtools-timeline/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "react-devtools-timeline",
- "version": "7.0.0",
+ "version": "7.0.1",
"license": "MIT",
"dependencies": {
"@elg/speedscope": "1.9.0-a6f84db",
diff --git a/packages/react-devtools/CHANGELOG.md b/packages/react-devtools/CHANGELOG.md
index 0fc637b994..6c3ab1baf0 100644
--- a/packages/react-devtools/CHANGELOG.md
+++ b/packages/react-devtools/CHANGELOG.md
@@ -4,6 +4,15 @@
---
+### 7.0.1
+October 20, 2025
+
+* Various UI improvements to experimental Suspense tab ([sebmarkbage](https://github.com/sebmarkbage) & [eps1lon](https://github.com/eps1lon))
+* devtools: fix ellipsis truncation for key values ([sophiebits](https://github.com/sophiebits) in [#34796](https://github.com/facebook/react/pull/34796))
+* fix(devtools): remove duplicated "Display density" field in General settings ([Anatole-Godard](https://github.com/Anatole-Godard) in [#34792](https://github.com/facebook/react/pull/34792))
+
+---
+
### 7.0.0
Oct 2, 2025
diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json
index ea8bab1c95..3d1a1bda2f 100644
--- a/packages/react-devtools/package.json
+++ b/packages/react-devtools/package.json
@@ -1,6 +1,6 @@
{
"name": "react-devtools",
- "version": "7.0.0",
+ "version": "7.0.1",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"repository": {
@@ -26,7 +26,7 @@
"electron": "^23.1.2",
"internal-ip": "^6.2.0",
"minimist": "^1.2.3",
- "react-devtools-core": "7.0.0",
+ "react-devtools-core": "7.0.1",
"update-notifier": "^2.1.0"
}
}
From 031595d720955723a0dab67068abc3db759cbc69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?=
Date: Mon, 20 Oct 2025 11:54:27 -0700
Subject: [PATCH 15/25] [DevTools] Title color tweak (#34927)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/devtools/views/SuspenseTab/SuspenseRects.css | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
index d8dd990850..0af7baf51a 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
@@ -41,17 +41,18 @@
.SuspenseRectsTitle {
pointer-events: none;
- color: var(--color-text);
+ color: color-mix(in srgb, var(--color-suspense) 50%, var(--color-text));
overflow: hidden;
text-overflow: ellipsis;
+ font-family: var(--font-family-sans);
font-size: var(--font-size-sans-small);
- line-height: var(--font-size-sans-small);
- padding: .25rem;
+ line-height: var(--line-height-data);
+ padding: 0 .25rem;
container-type: size;
container-name: title;
}
-@container title (width < 30px) or (height < 12px) {
+@container title (width < 30px) or (height < 18px) {
.SuspenseRectsTitle > span {
display: none;
}
From ea0c17b0952e2b866a1f0dd4b5ac28c7df9d8518 Mon Sep 17 00:00:00 2001
From: Nathan
Date: Mon, 20 Oct 2025 16:52:11 -0400
Subject: [PATCH 16/25] [compiler] loosen computed key restriction for compiler
(#34902)
We have a whole ton of compiler errors due to us using a helper to
return breakpoints for CSS-in-js, which results in code like:
```
const styles = {
[responsive.up('xl')]: { ... }
}
```
this results in TONS of bailouts due to `(BuildHIR::lowerExpression)
Expected Identifier, got CallExpression key in ObjectExpression`.
I was looking into what it would take to fix it and why we don't allow
it, and following the paper trail is seems like the gotchas have been
fixed with the new mutability aliasing model that is fully rolled out.
It looks like this is the same pattern/issue that was fixed (see
https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js
and the old bug in
https://github.com/facebook/react/blob/d58c07b563a79bd706531278cb4afec6292b84a8/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md).
@josephsavona can you confirm if that's the case and if we're able to
drop this restriction now? (or alternatively, is there another case we
can ignore?)
---
.../src/HIR/BuildHIR.ts | 14 ----
...after-construction-sequence-expr.expect.md | 41 ------------
...dified-during-after-construction.expect.md | 41 ------------
...te-key-while-constructing-object.expect.md | 40 -----------
...ject-expression-member-expr-call.expect.md | 42 ------------
...after-construction-sequence-expr.expect.md | 56 ++++++++++++++++
...uring-after-construction-sequence-expr.js} | 3 +-
...dified-during-after-construction.expect.md | 55 +++++++++++++++
...key-modified-during-after-construction.js} | 1 +
...te-key-while-constructing-object.expect.md | 67 +++++++++++++++++++
...y-mutate-key-while-constructing-object.js} | 0
...ject-expression-member-expr-call.expect.md | 54 +++++++++++++++
... => object-expression-member-expr-call.js} | 0
13 files changed, 235 insertions(+), 179 deletions(-)
delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.expect.md
delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.expect.md
delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.expect.md
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.js => object-expression-computed-key-modified-during-after-construction-sequence-expr.js} (79%)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.expect.md
rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-object-expression-computed-key-modified-during-after-construction.js => object-expression-computed-key-modified-during-after-construction.js} (86%)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.expect.md
rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-object-expression-computed-key-mutate-key-while-constructing-object.js => object-expression-computed-key-mutate-key-while-constructing-object.js} (100%)
create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.expect.md
rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.todo-object-expression-member-expr-call.js => object-expression-member-expr-call.js} (100%)
diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
index 0ae338f5c7..f6872da111 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
@@ -1568,20 +1568,6 @@ function lowerObjectPropertyKey(
name: key.node.value,
};
} else if (property.node.computed && key.isExpression()) {
- if (!key.isIdentifier() && !key.isMemberExpression()) {
- /*
- * NOTE: allowing complex key expressions can trigger a bug where a mutation is made conditional
- * see fixture
- * error.object-expression-computed-key-modified-during-after-construction.js
- */
- builder.errors.push({
- reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
- category: ErrorCategory.Todo,
- loc: key.node.loc ?? null,
- suggestions: null,
- });
- return null;
- }
const place = lowerExpressionToTemporary(builder, key);
return {
kind: 'computed',
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
deleted file mode 100644
index bebdd9dcc9..0000000000
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
+++ /dev/null
@@ -1,41 +0,0 @@
-
-## Input
-
-```javascript
-import {identity, mutate, mutateAndReturn} from 'shared-runtime';
-
-function Component(props) {
- const key = {};
- const context = {
- [(mutate(key), key)]: identity([props.value]),
- };
- mutate(key);
- return context;
-}
-
-export const FIXTURE_ENTRYPOINT = {
- fn: Component,
- params: [{value: 42}],
-};
-
-```
-
-
-## Error
-
-```
-Found 1 error:
-
-Todo: (BuildHIR::lowerExpression) Expected Identifier, got SequenceExpression key in ObjectExpression
-
-error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.ts:6:6
- 4 | const key = {};
- 5 | const context = {
-> 6 | [(mutate(key), key)]: identity([props.value]),
- | ^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got SequenceExpression key in ObjectExpression
- 7 | };
- 8 | mutate(key);
- 9 | return context;
-```
-
-
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.expect.md
deleted file mode 100644
index 215e5200a9..0000000000
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.expect.md
+++ /dev/null
@@ -1,41 +0,0 @@
-
-## Input
-
-```javascript
-import {identity, mutate, mutateAndReturn} from 'shared-runtime';
-
-function Component(props) {
- const key = {};
- const context = {
- [mutateAndReturn(key)]: identity([props.value]),
- };
- mutate(key);
- return context;
-}
-
-export const FIXTURE_ENTRYPOINT = {
- fn: Component,
- params: [{value: 42}],
-};
-
-```
-
-
-## Error
-
-```
-Found 1 error:
-
-Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
-
-error.todo-object-expression-computed-key-modified-during-after-construction.ts:6:5
- 4 | const key = {};
- 5 | const context = {
-> 6 | [mutateAndReturn(key)]: identity([props.value]),
- | ^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
- 7 | };
- 8 | mutate(key);
- 9 | return context;
-```
-
-
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.expect.md
deleted file mode 100644
index abe4153e22..0000000000
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.expect.md
+++ /dev/null
@@ -1,40 +0,0 @@
-
-## Input
-
-```javascript
-import {identity, mutate, mutateAndReturn} from 'shared-runtime';
-
-function Component(props) {
- const key = {};
- const context = {
- [mutateAndReturn(key)]: identity([props.value]),
- };
- return context;
-}
-
-export const FIXTURE_ENTRYPOINT = {
- fn: Component,
- params: [{value: 42}],
-};
-
-```
-
-
-## Error
-
-```
-Found 1 error:
-
-Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
-
-error.todo-object-expression-computed-key-mutate-key-while-constructing-object.ts:6:5
- 4 | const key = {};
- 5 | const context = {
-> 6 | [mutateAndReturn(key)]: identity([props.value]),
- | ^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
- 7 | };
- 8 | return context;
- 9 | }
-```
-
-
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.expect.md
deleted file mode 100644
index 560f40b991..0000000000
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.expect.md
+++ /dev/null
@@ -1,42 +0,0 @@
-
-## Input
-
-```javascript
-import {identity, mutate, mutateAndReturn} from 'shared-runtime';
-
-function Component(props) {
- const obj = {mutateAndReturn};
- const key = {};
- const context = {
- [obj.mutateAndReturn(key)]: identity([props.value]),
- };
- mutate(key);
- return context;
-}
-
-export const FIXTURE_ENTRYPOINT = {
- fn: Component,
- params: [{value: 42}],
-};
-
-```
-
-
-## Error
-
-```
-Found 1 error:
-
-Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
-
-error.todo-object-expression-member-expr-call.ts:7:5
- 5 | const key = {};
- 6 | const context = {
-> 7 | [obj.mutateAndReturn(key)]: identity([props.value]),
- | ^^^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
- 8 | };
- 9 | mutate(key);
- 10 | return context;
-```
-
-
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
new file mode 100644
index 0000000000..d46db9c8de
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md
@@ -0,0 +1,56 @@
+
+## Input
+
+```javascript
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const key = {};
+ const context = {
+ [(mutate(key), key)]: identity([props.value]),
+ };
+ mutate(key);
+ return [context, key];
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [{value: 42}, {value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate, mutateAndReturn } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(2);
+ let t0;
+ if ($[0] !== props.value) {
+ const key = {};
+ const context = { [(mutate(key), key)]: identity([props.value]) };
+ mutate(key);
+ t0 = [context, key];
+ $[0] = props.value;
+ $[1] = t0;
+ } else {
+ t0 = $[1];
+ }
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+ sequentialRenders: [{ value: 42 }, { value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
+[{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.js
similarity index 79%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.js
index 59437a9ffd..183c03cf91 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.js
@@ -6,10 +6,11 @@ function Component(props) {
[(mutate(key), key)]: identity([props.value]),
};
mutate(key);
- return context;
+ return [context, key];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
+ sequentialRenders: [{value: 42}, {value: 42}],
};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.expect.md
new file mode 100644
index 0000000000..1d671aa36c
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.expect.md
@@ -0,0 +1,55 @@
+
+## Input
+
+```javascript
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const key = {};
+ const context = {
+ [mutateAndReturn(key)]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+ sequentialRenders: [{value: 42}, {value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate, mutateAndReturn } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(2);
+ let context;
+ if ($[0] !== props.value) {
+ const key = {};
+ context = { [mutateAndReturn(key)]: identity([props.value]) };
+ mutate(key);
+ $[0] = props.value;
+ $[1] = context;
+ } else {
+ context = $[1];
+ }
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+ sequentialRenders: [{ value: 42 }, { value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"[object Object]":[42]}
+{"[object Object]":[42]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.js
similarity index 86%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.js
index 86d9cd7fd9..0176850e98 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.js
@@ -12,4 +12,5 @@ function Component(props) {
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
+ sequentialRenders: [{value: 42}, {value: 42}],
};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.expect.md
new file mode 100644
index 0000000000..4c4b45967e
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.expect.md
@@ -0,0 +1,67 @@
+
+## Input
+
+```javascript
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const key = {};
+ const context = {
+ [mutateAndReturn(key)]: identity([props.value]),
+ };
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate, mutateAndReturn } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(5);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ const key = {};
+
+ t0 = mutateAndReturn(key);
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ let t1;
+ if ($[1] !== props.value) {
+ t1 = identity([props.value]);
+ $[1] = props.value;
+ $[2] = t1;
+ } else {
+ t1 = $[2];
+ }
+ let t2;
+ if ($[3] !== t1) {
+ t2 = { [t0]: t1 };
+ $[3] = t1;
+ $[4] = t2;
+ } else {
+ t2 = $[4];
+ }
+ const context = t2;
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"[object Object]":[42]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.js
similarity index 100%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.js
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.expect.md
new file mode 100644
index 0000000000..ac6cb97b08
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.expect.md
@@ -0,0 +1,54 @@
+
+## Input
+
+```javascript
+import {identity, mutate, mutateAndReturn} from 'shared-runtime';
+
+function Component(props) {
+ const obj = {mutateAndReturn};
+ const key = {};
+ const context = {
+ [obj.mutateAndReturn(key)]: identity([props.value]),
+ };
+ mutate(key);
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 42}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { identity, mutate, mutateAndReturn } from "shared-runtime";
+
+function Component(props) {
+ const $ = _c(2);
+ let context;
+ if ($[0] !== props.value) {
+ const obj = { mutateAndReturn };
+ const key = {};
+ context = { [obj.mutateAndReturn(key)]: identity([props.value]) };
+ mutate(key);
+ $[0] = props.value;
+ $[1] = context;
+ } else {
+ context = $[1];
+ }
+ return context;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ value: 42 }],
+};
+
+```
+
+### Eval output
+(kind: ok) {"[object Object]":[42]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.js
similarity index 100%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.js
From 613cf80f263663f2c9923324ca41591c230c20ad Mon Sep 17 00:00:00 2001
From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com>
Date: Tue, 21 Oct 2025 13:51:44 +0100
Subject: [PATCH 17/25] [DevTools] chore: add useSyncExternalStore examples to
shell (#34932)
Few examples of using `useSyncExternalStore` that can be useful for
debugging hook tree reconstruction logic and hook names parsing feature.
---
.../InspectableElements.js | 2 +
.../UseSyncExternalStore.js | 133 ++++++++++++++++++
2 files changed, 135 insertions(+)
create mode 100644 packages/react-devtools-shell/src/app/InspectableElements/UseSyncExternalStore.js
diff --git a/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js b/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js
index 95f104925b..34f46774b1 100644
--- a/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js
+++ b/packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js
@@ -20,6 +20,7 @@ import SimpleValues from './SimpleValues';
import SymbolKeys from './SymbolKeys';
import UseMemoCache from './UseMemoCache';
import UseEffectEvent from './UseEffectEvent';
+import UseSyncExternalStore from './UseSyncExternalStore';
// TODO Add Immutable JS example
@@ -38,6 +39,7 @@ export default function InspectableElements(): React.Node {
+
);
}
diff --git a/packages/react-devtools-shell/src/app/InspectableElements/UseSyncExternalStore.js b/packages/react-devtools-shell/src/app/InspectableElements/UseSyncExternalStore.js
new file mode 100644
index 0000000000..decb10b2db
--- /dev/null
+++ b/packages/react-devtools-shell/src/app/InspectableElements/UseSyncExternalStore.js
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as React from 'react';
+
+const {useState, useEffect, useSyncExternalStore} = React;
+
+// Create a simple external store for demonstratio
+function createStore(initialValue: T): {
+ subscribe: (cb: () => void) => () => any,
+ getSnapshot: () => T,
+ setValue: (newValue: T) => void,
+} {
+ let value = initialValue;
+ const subscribers = new Set<() => void>();
+
+ return {
+ subscribe(callback) {
+ subscribers.add(callback);
+ return () => subscribers.delete(callback);
+ },
+ getSnapshot() {
+ return value;
+ },
+ setValue(newValue) {
+ value = newValue;
+ subscribers.forEach(callback => callback());
+ },
+ };
+}
+
+const counterStore = createStore(0);
+const themeStore = createStore('light');
+
+export default function UseSyncExternalStore(): React.Node {
+ return (
+ <>
+ useSyncExternalStore()
+
+
+
+ >
+ );
+}
+
+function SingleHookCase(): React.Node {
+ const count = useSyncExternalStore(
+ counterStore.subscribe,
+ counterStore.getSnapshot,
+ );
+
+ return (
+
+
Single hook case
+
Count: {count}
+
counterStore.setValue(count + 1)}>
+ Increment
+
+
counterStore.setValue(count - 1)}>
+ Decrement
+
+
+ );
+}
+
+function useCounter() {
+ const count = useSyncExternalStore(
+ counterStore.subscribe,
+ counterStore.getSnapshot,
+ );
+ const [localState, setLocalState] = useState(0);
+
+ useEffect(() => {
+ // Some effect
+ }, [count]);
+
+ return {count, localState, setLocalState};
+}
+
+function HookTreeCase(): React.Node {
+ const {count, localState, setLocalState} = useCounter();
+
+ return (
+
+
Hook tree case
+
External count: {count}
+
Local state: {localState}
+
counterStore.setValue(count + 1)}>
+ Increment External
+
+
setLocalState(localState + 1)}>
+ Increment Local
+
+
+ );
+}
+
+function useTheme() {
+ const theme = useSyncExternalStore(
+ themeStore.subscribe,
+ themeStore.getSnapshot,
+ );
+
+ return theme;
+}
+
+function MultipleStoresCase() {
+ const count = useSyncExternalStore(
+ counterStore.subscribe,
+ counterStore.getSnapshot,
+ );
+ const theme = useTheme();
+
+ return (
+
+
Multiple stores case
+
Count: {count}
+
Theme: {theme}
+
+ themeStore.setValue(theme === 'light' ? 'dark' : 'light')
+ }>
+ Toggle Theme
+
+
+ );
+}
From 39c6545cef85b5251e519080fd315bff728d87de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?B=C5=82a=C5=BCej=20Kustra?=
<46095609+blazejkustra@users.noreply.github.com>
Date: Tue, 21 Oct 2025 14:59:20 +0200
Subject: [PATCH 18/25] Fix indices of hooks in devtools when using
useSyncExternalStore (#34547)
## Summary
This PR updates getChangedHooksIndices to account for the fact that
useSyncExternalStore internally mounts two hooks, while DevTools should
treat it as a single user-facing hook.
It introduces a helper isUseSyncExternalStoreHook to detect this case
and adjust iteration so the extra internal hook is skipped when counting
changes.
Before:
https://github.com/user-attachments/assets/0db72a4e-21f7-44c7-ba02-669a272631e5
After:
https://github.com/user-attachments/assets/4da71392-0396-408d-86a7-6fbc82d8c4f5
## How did you test this change?
I used this component to reproduce this issue locally (I followed
instructions in `packages/react-devtools/CONTRIBUTING.md`).
```ts
function Test() {
// 1
React.useSyncExternalStore(
() => {},
() => {},
() => {},
);
// 2
const [state, setState] = useState('test');
return (
<>
setState(Math.random())}
style={{backgroundColor: 'red'}}>
{state}
>
);
}
```
---
.../src/backend/fiber/renderer.js | 29 +++++++++++++++----
1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index fe8722da56..9de9037690 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -1913,6 +1913,20 @@ export function attach(
return false;
}
+ function isUseSyncExternalStoreHook(hookObject: any): boolean {
+ const queue = hookObject.queue;
+ if (!queue) {
+ return false;
+ }
+
+ const boundHasOwnProperty = hasOwnProperty.bind(queue);
+ return (
+ boundHasOwnProperty('value') &&
+ boundHasOwnProperty('getSnapshot') &&
+ typeof queue.getSnapshot === 'function'
+ );
+ }
+
function isHookThatCanScheduleUpdate(hookObject: any) {
const queue = hookObject.queue;
if (!queue) {
@@ -1929,12 +1943,7 @@ export function attach(
return true;
}
- // Detect useSyncExternalStore()
- return (
- boundHasOwnProperty('value') &&
- boundHasOwnProperty('getSnapshot') &&
- typeof queue.getSnapshot === 'function'
- );
+ return isUseSyncExternalStoreHook(hookObject);
}
function didStatefulHookChange(prev: any, next: any): boolean {
@@ -1955,10 +1964,18 @@ export function attach(
const indices = [];
let index = 0;
+
while (next !== null) {
if (didStatefulHookChange(prev, next)) {
indices.push(index);
}
+
+ // useSyncExternalStore creates 2 internal hooks, but we only count it as 1 user-facing hook
+ if (isUseSyncExternalStoreHook(next)) {
+ next = next.next;
+ prev = prev.next;
+ }
+
next = next.next;
prev = prev.next;
index++;
From 71b3a03cc936c8eb30a6e6108abf5550f5037f71 Mon Sep 17 00:00:00 2001
From: lauren
Date: Tue, 21 Oct 2025 10:57:18 -0400
Subject: [PATCH 19/25] [forgive] Various fixes to prepare for internal sync
(#34928)
Fixes a few small things:
- Update imports to reference root babel-plugin-react-compiler rather
than from `[...]/src/...`
- Remove unused cosmiconfig options parsing for now
- Update type exports in babel-plugin-react-compiler accordingly
---
.../babel-plugin-react-compiler/src/index.ts | 3 +
compiler/packages/react-forgive/package.json | 3 +-
.../react-forgive/server/package.json | 1 -
.../server/src/compiler/compat.ts | 2 +-
.../server/src/compiler/index.ts | 2 +-
.../server/src/compiler/options.ts | 25 ------
.../react-forgive/server/src/index.ts | 14 ++-
.../src/requests/autodepsdecorations.ts | 2 +-
.../packages/react-forgive/server/yarn.lock | 86 +------------------
9 files changed, 15 insertions(+), 123 deletions(-)
delete mode 100644 compiler/packages/react-forgive/server/src/compiler/options.ts
diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts
index d2abd744d6..ca5b653430 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/index.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts
@@ -29,10 +29,13 @@ export {
ProgramContext,
tryFindDirectiveEnablingMemoization as findDirectiveEnablingMemoization,
findDirectiveDisablingMemoization,
+ defaultOptions,
type CompilerPipelineValue,
type Logger,
type LoggerEvent,
type PluginOptions,
+ type AutoDepsDecorationsEvent,
+ type CompileSuccessEvent,
} from './Entrypoint';
export {
Effect,
diff --git a/compiler/packages/react-forgive/package.json b/compiler/packages/react-forgive/package.json
index 0bf48e232e..fc7d71095c 100644
--- a/compiler/packages/react-forgive/package.json
+++ b/compiler/packages/react-forgive/package.json
@@ -36,13 +36,14 @@
},
"scripts": {
"build": "yarn run compile",
+ "build:compiler": "yarn workspace babel-plugin-react-compiler build --dts",
"compile": "rimraf dist && concurrently -n server,client \"scripts/build.mjs -t server\" \"scripts/build.mjs -t client\"",
"dev": "yarn run package && yarn run install-ext",
"install-ext": "code --install-extension react-forgive-0.0.0.vsix",
"lint": "echo 'no tests'",
"package": "rm -f react-forgive-0.0.0.vsix && vsce package --yarn",
"postinstall": "cd client && yarn install && cd ../server && yarn install && cd ..",
- "pretest": "yarn run compile && yarn run lint",
+ "pretest": "yarn run build:compiler && yarn run compile && yarn run lint",
"test": "vscode-test",
"vscode:prepublish": "yarn run compile",
"watch": "scripts/build.mjs --watch"
diff --git a/compiler/packages/react-forgive/server/package.json b/compiler/packages/react-forgive/server/package.json
index fb6f4feebd..c912586072 100644
--- a/compiler/packages/react-forgive/server/package.json
+++ b/compiler/packages/react-forgive/server/package.json
@@ -18,7 +18,6 @@
"@babel/parser": "^7.26.0",
"@babel/plugin-syntax-typescript": "^7.25.9",
"@babel/types": "^7.26.0",
- "cosmiconfig": "^9.0.0",
"prettier": "^3.3.3",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.12"
diff --git a/compiler/packages/react-forgive/server/src/compiler/compat.ts b/compiler/packages/react-forgive/server/src/compiler/compat.ts
index 10271cbdcd..f467f70ba1 100644
--- a/compiler/packages/react-forgive/server/src/compiler/compat.ts
+++ b/compiler/packages/react-forgive/server/src/compiler/compat.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import {SourceLocation} from 'babel-plugin-react-compiler/src';
+import {type SourceLocation} from 'babel-plugin-react-compiler';
import {type Range} from 'vscode-languageserver';
export function babelLocationToRange(loc: SourceLocation): Range | null {
diff --git a/compiler/packages/react-forgive/server/src/compiler/index.ts b/compiler/packages/react-forgive/server/src/compiler/index.ts
index fe192c6213..b474253f71 100644
--- a/compiler/packages/react-forgive/server/src/compiler/index.ts
+++ b/compiler/packages/react-forgive/server/src/compiler/index.ts
@@ -9,7 +9,7 @@ import type * as BabelCore from '@babel/core';
import {parseAsync, transformFromAstAsync} from '@babel/core';
import BabelPluginReactCompiler, {
type PluginOptions,
-} from 'babel-plugin-react-compiler/src';
+} from 'babel-plugin-react-compiler';
import * as babelParser from 'prettier/plugins/babel.js';
import estreeParser from 'prettier/plugins/estree';
import * as typescriptParser from 'prettier/plugins/typescript';
diff --git a/compiler/packages/react-forgive/server/src/compiler/options.ts b/compiler/packages/react-forgive/server/src/compiler/options.ts
deleted file mode 100644
index 226be799d3..0000000000
--- a/compiler/packages/react-forgive/server/src/compiler/options.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import {
- parsePluginOptions,
- type PluginOptions,
-} from 'babel-plugin-react-compiler/src';
-import {cosmiconfigSync} from 'cosmiconfig';
-
-export function resolveReactConfig(projectPath: string): PluginOptions | null {
- const explorerSync = cosmiconfigSync('react', {
- searchStrategy: 'project',
- cache: true,
- });
- const result = explorerSync.search(projectPath);
- if (result != null) {
- return parsePluginOptions(result.config);
- } else {
- return null;
- }
-}
diff --git a/compiler/packages/react-forgive/server/src/index.ts b/compiler/packages/react-forgive/server/src/index.ts
index fd4ffd9988..7552337cbc 100644
--- a/compiler/packages/react-forgive/server/src/index.ts
+++ b/compiler/packages/react-forgive/server/src/index.ts
@@ -20,13 +20,12 @@ import {
TextDocumentSyncKind,
} from 'vscode-languageserver/node';
import {compile, lastResult} from './compiler';
-import {type PluginOptions} from 'babel-plugin-react-compiler/src';
-import {resolveReactConfig} from './compiler/options';
import {
type CompileSuccessEvent,
type LoggerEvent,
+ type PluginOptions,
defaultOptions,
-} from 'babel-plugin-react-compiler/src/Entrypoint/Options';
+} from 'babel-plugin-react-compiler';
import {babelLocationToRange, getRangeFirstCharacter} from './compiler/compat';
import {
type AutoDepsDecorationsLSPEvent,
@@ -64,8 +63,7 @@ type CodeActionLSPEvent = {
};
connection.onInitialize((_params: InitializeParams) => {
- // TODO(@poteto) get config fr
- compilerOptions = resolveReactConfig('.') ?? defaultOptions;
+ compilerOptions = defaultOptions;
compilerOptions = {
...compilerOptions,
environment: {
@@ -76,21 +74,21 @@ connection.onInitialize((_params: InitializeParams) => {
importSpecifierName: 'useEffect',
source: 'react',
},
- numRequiredArgs: 1,
+ autodepsIndex: 1,
},
{
function: {
importSpecifierName: 'useSpecialEffect',
source: 'shared-runtime',
},
- numRequiredArgs: 2,
+ autodepsIndex: 2,
},
{
function: {
importSpecifierName: 'default',
source: 'useEffectWrapper',
},
- numRequiredArgs: 1,
+ autodepsIndex: 1,
},
],
},
diff --git a/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts b/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts
index 77a568662e..c9c9e8ca01 100644
--- a/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts
+++ b/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import {type AutoDepsDecorationsEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
+import {type AutoDepsDecorationsEvent} from 'babel-plugin-react-compiler';
import {type Position} from 'vscode-languageserver-textdocument';
import {RequestType} from 'vscode-languageserver/node';
import {type Range, sourceLocationToRange} from '../utils/range';
diff --git a/compiler/packages/react-forgive/server/yarn.lock b/compiler/packages/react-forgive/server/yarn.lock
index b72063294f..ee346ce7d3 100644
--- a/compiler/packages/react-forgive/server/yarn.lock
+++ b/compiler/packages/react-forgive/server/yarn.lock
@@ -10,7 +10,7 @@
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
+"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
@@ -188,11 +188,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
-argparse@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
- integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
browserslist@^4.24.0:
version "4.24.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2"
@@ -203,11 +198,6 @@ browserslist@^4.24.0:
node-releases "^2.0.19"
update-browserslist-db "^1.1.1"
-callsites@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
- integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-
caniuse-lite@^1.0.30001688:
version "1.0.30001690"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8"
@@ -218,16 +208,6 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
-cosmiconfig@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d"
- integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==
- dependencies:
- env-paths "^2.2.1"
- import-fresh "^3.3.0"
- js-yaml "^4.1.0"
- parse-json "^5.2.0"
-
debug@^4.1.0, debug@^4.3.1:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
@@ -240,18 +220,6 @@ electron-to-chromium@^1.5.73:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz#cb886b504a6467e4c00bea3317edb38393c53413"
integrity sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==
-env-paths@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
- integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
-
-error-ex@^1.3.1:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
- integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
- dependencies:
- is-arrayish "^0.2.1"
-
escalade@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@@ -267,51 +235,21 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-import-fresh@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
- integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
- dependencies:
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
-
-is-arrayish@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
- integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
-
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-js-yaml@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
- integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
- dependencies:
- argparse "^2.0.1"
-
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
-json-parse-even-better-errors@^2.3.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
- integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
-
json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
-lines-and-columns@^1.1.6:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
- integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
-
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -329,23 +267,6 @@ node-releases@^2.0.19:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
-parent-module@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
- integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
- dependencies:
- callsites "^3.0.0"
-
-parse-json@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
- integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- error-ex "^1.3.1"
- json-parse-even-better-errors "^2.3.0"
- lines-and-columns "^1.1.6"
-
picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -356,11 +277,6 @@ prettier@^3.3.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
-resolve-from@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
- integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-
semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
From 6b344c7c5359d0113389ef92d0a6a4d5cbb76677 Mon Sep 17 00:00:00 2001
From: Karl Horky
Date: Wed, 22 Oct 2025 18:31:09 +0200
Subject: [PATCH 20/25] Switch to `export =` to fix eslint-plugin-react-hooks
types (#34949)
## Summary
Resolve the type error with the types, according to [Are the types
wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%407.0.0),
as an additional
- Last attempt: https://github.com/facebook/react/pull/34746
- Original issue: https://github.com/facebook/react/issues/34745
## How did you test this change?
I edited `node_modules/eslint-plugin-react-hooks/index.d.ts` in my
`"module": "Node16"` + `"type": "module"` project and my error went
away:
- https://github.com/facebook/react/issues/34801#issuecomment-3433053067
cc @poteto @michaelfaith @andrewbranch
---
packages/eslint-plugin-react-hooks/npm/index.d.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/eslint-plugin-react-hooks/npm/index.d.ts b/packages/eslint-plugin-react-hooks/npm/index.d.ts
index c883d6f8ad..7516dc1e84 100644
--- a/packages/eslint-plugin-react-hooks/npm/index.d.ts
+++ b/packages/eslint-plugin-react-hooks/npm/index.d.ts
@@ -5,4 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
-export {default} from './cjs/eslint-plugin-react-hooks';
+import reactHooks from './cjs/eslint-plugin-react-hooks';
+
+export = reactHooks;
From bbb7a1fdf741eed6b7adcd48a441259819d664cc Mon Sep 17 00:00:00 2001
From: lauren
Date: Wed, 22 Oct 2025 13:18:44 -0400
Subject: [PATCH 21/25] [eprh] Type `configs.flat` more strictly (#34950)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Addresses #34801 where `configs.flat` is possibly undefined as it was
typed as a record of arbitrary string keys.
---
packages/eslint-plugin-react-hooks/src/index.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts
index 6d72c1daa5..924299d898 100644
--- a/packages/eslint-plugin-react-hooks/src/index.ts
+++ b/packages/eslint-plugin-react-hooks/src/index.ts
@@ -71,7 +71,10 @@ const configs = {
plugins,
rules: recommendedLatestRuleConfigs,
},
- flat: {} as Record,
+ flat: {} as {
+ recommended: ReactHooksFlatConfig;
+ 'recommended-latest': ReactHooksFlatConfig;
+ },
};
const plugin = {
From 723b25c6444e4dcabe710ae33bc93cea79324428 Mon Sep 17 00:00:00 2001
From: Karl Horky
Date: Wed, 22 Oct 2025 20:05:49 +0200
Subject: [PATCH 22/25] Add hint for Node.js cjs-module-lexer for
eslint-plugin-react-hook types (#34951)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
@andrewbranch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## How did you test this change?
I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs. (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
---
packages/eslint-plugin-react-hooks/index.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/packages/eslint-plugin-react-hooks/index.js b/packages/eslint-plugin-react-hooks/index.js
index ce26a10c31..4c18af7c00 100644
--- a/packages/eslint-plugin-react-hooks/index.js
+++ b/packages/eslint-plugin-react-hooks/index.js
@@ -1 +1,11 @@
module.exports = require('./src/index.ts');
+
+// Hint to Node’s cjs-module-lexer to make named imports work
+// https://github.com/facebook/react/issues/34801#issuecomment-3433478810
+// eslint-disable-next-line ft-flow/no-unused-expressions
+0 &&
+ (module.exports = {
+ meta: true,
+ rules: true,
+ configs: true,
+ });
From eb2f784e752ba690f032db4c3d87daac77a5a2aa Mon Sep 17 00:00:00 2001
From: Karl Horky
Date: Wed, 22 Oct 2025 23:51:01 +0200
Subject: [PATCH 23/25] Add hint for Node.js cjs-module-lexer for
eslint-plugin-react-hook types (#34953)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Supersedes #34951
## Summary
Fix the runtime error with named imports and make the last remaining
[Are The Types
Wrong?](https://arethetypeswrong.github.io/?p=eslint-plugin-react-hooks%400.0.0-experimental-6b344c7c-20251022)
error with `eslint-plugin-react-hooks` go away, thanks to the hint from
Andrew Branch:
- https://github.com/facebook/react/issues/34801#issuecomment-3433478810
## How did you test this change?
I tried adding this to `node_modules` and it fixed the failures when
importing named imports like `import { configs, meta, rules } from
'eslint-plugin-react-hooks'`:
```bash
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
Oops! Something went wrong! :(
ESLint: 9.37.0
file:///Users/k/p/eslint-config-upleveled/index.js:13
import reactHooks, { configs } from 'eslint-plugin-react-hooks';
^^^^^^^
SyntaxError: Named export 'configs' not found. The requested module 'eslint-plugin-react-hooks' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'eslint-plugin-react-hooks';
const { configs } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:228:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:335:5)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:647:26)
at async dynamicImportConfig (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:186:17)
at async loadConfigFile (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:276:9)
at async ConfigLoader.calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:589:23)
at async #calculateConfigArray (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/config/config-loader.js:743:23)
at async directoryFilter (/Users/k/p/eslint-config-upleveled/node_modules/.pnpm/eslint@9.37.0/node_modules/eslint/lib/eslint/eslint-helpers.js:309:5)
at async NodeHfs. (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:586:29)
at async NodeHfs.walk (file:///Users/k/p/eslint-config-upleveled/node_modules/.pnpm/@humanfs+core@0.19.1/node_modules/@humanfs/core/src/hfs.js:614:3)
➜ eslint-config-upleveled git:(renovate/react-monorepo) pnpm eslint . --max-warnings 0
➜ eslint-config-upleveled git:(renovate/react-monorepo) # no error
```
The named imports identifiers `configs`, `meta`, and `rules` also
contain values, as a sanity check:
- https://github.com/facebook/react/pull/34951#issuecomment-3433555636
cc @poteto
---
packages/eslint-plugin-react-hooks/index.js | 10 ----------
packages/eslint-plugin-react-hooks/npm/index.js | 10 ++++++++++
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/packages/eslint-plugin-react-hooks/index.js b/packages/eslint-plugin-react-hooks/index.js
index 4c18af7c00..ce26a10c31 100644
--- a/packages/eslint-plugin-react-hooks/index.js
+++ b/packages/eslint-plugin-react-hooks/index.js
@@ -1,11 +1 @@
module.exports = require('./src/index.ts');
-
-// Hint to Node’s cjs-module-lexer to make named imports work
-// https://github.com/facebook/react/issues/34801#issuecomment-3433478810
-// eslint-disable-next-line ft-flow/no-unused-expressions
-0 &&
- (module.exports = {
- meta: true,
- rules: true,
- configs: true,
- });
diff --git a/packages/eslint-plugin-react-hooks/npm/index.js b/packages/eslint-plugin-react-hooks/npm/index.js
index b819fc7023..3e8352af1e 100644
--- a/packages/eslint-plugin-react-hooks/npm/index.js
+++ b/packages/eslint-plugin-react-hooks/npm/index.js
@@ -14,3 +14,13 @@ if (process.env.NODE_ENV === 'production') {
} else {
module.exports = require('./cjs/eslint-plugin-react-hooks.development.js');
}
+
+// Hint to Node’s cjs-module-lexer to make named imports work
+// https://github.com/facebook/react/issues/34801#issuecomment-3433478810
+// eslint-disable-next-line ft-flow/no-unused-expressions
+0 &&
+ (module.exports = {
+ meta: true,
+ rules: true,
+ configs: true,
+ });
From 6160773f309b7fc734767649d47140d04fa1f568 Mon Sep 17 00:00:00 2001
From: Timothy Lau
Date: Thu, 23 Oct 2025 23:13:18 +0800
Subject: [PATCH 24/25] [playground] Refactor ConfigEditor to use
component (#34958)
## Summary
This PR addresses a pending TODO comment left in
https://github.com/facebook/react/pull/34499
https://github.com/facebook/react/blame/eb2f784e752ba690f032db4c3d87daac77a5a2aa/compiler/apps/playground/components/Editor/ConfigEditor.tsx#L37
This change removes the temporary workaround and replaces it with
``, as originally intended.
## How did you test this change?
- Updated the component to use `` directly
- Verified the editor renders correctly in both development and
production builds.
- The `` UI updates as expected.
https://github.com/user-attachments/assets/ce976123-da59-4579-b063-b308a9167b21
---
.../components/Editor/ConfigEditor.tsx | 19 +++----
compiler/apps/playground/package.json | 14 ++---
compiler/apps/playground/styles/globals.css | 9 +++
compiler/apps/playground/yarn.lock | 57 ++++++++++---------
4 files changed, 54 insertions(+), 45 deletions(-)
diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx
index 18f904d225..cf80b48dbf 100644
--- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx
+++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx
@@ -14,6 +14,7 @@ import React, {
unstable_ViewTransition as ViewTransition,
unstable_addTransitionType as addTransitionType,
startTransition,
+ Activity,
} from 'react';
import {Resizable} from 're-resizable';
import {useStore, useStoreDispatch} from '../StoreContext';
@@ -34,12 +35,8 @@ export default function ConfigEditor({
const [isExpanded, setIsExpanded] = useState(false);
return (
- // TODO: Use when it is compatible with Monaco: https://github.com/suren-atoyan/monaco-react/issues/753
<>
-
+
{
startTransition(() => {
@@ -49,11 +46,8 @@ export default function ConfigEditor({
}}
formattedAppliedConfig={formattedAppliedConfig}
/>
-
-
+
+
{
startTransition(() => {
@@ -62,7 +56,7 @@ export default function ConfigEditor({
});
}}
/>
-
+
>
);
}
@@ -122,7 +116,8 @@ function ExpandedEditor({
return (
+ enter={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}
+ exit={{[CONFIG_PANEL_TRANSITION]: 'slide-out', default: 'none'}}>
Date: Thu, 23 Oct 2025 10:21:33 -0700
Subject: [PATCH 25/25] [compiler] Improve display of errors on multi-line
expressions
When a longer function or expression is identified as the source of an error, we currently print the entire expression in our error message. This is because we delegate to a Babel helper to print codeframes. Here, we add some checking and abbreviate the result if it spans too many lines.
---
.../src/CompilerError.ts | 37 ++++++++++++++++++-
...ession-with-conditional-optional.expect.md | 13 +------
...mber-expression-with-conditional.expect.md | 13 +------
...as-memo-dep-non-optional-in-body.expect.md | 7 +---
...es-memoizes-with-captures-values.expect.md | 30 +--------------
...ken-as-dependency-later-mutation.expect.md | 5 +--
...p-with-context-variable-iterator.expect.md | 13 +------
...p-with-context-variable-iterator.expect.md | 13 +------
...-catch-in-outer-try-with-finally.expect.md | 17 +--------
...-invalid-jsx-in-try-with-finally.expect.md | 7 +---
.../compiler/error.todo-kitchensink.expect.md | 9 +----
...ional-nonoptional-property-chain.expect.md | 21 +----------
...from-inferred-mutation-in-logger.expect.md | 24 +-----------
.../bailout-retry/error.todo-syntax.expect.md | 19 +---------
.../error.wrong-index.expect.md | 10 +----
...ack-conditional-access-own-scope.expect.md | 11 +-----
...ck-infer-conditional-value-block.expect.md | 30 +--------------
...ve-use-memo-ref-missing-reactive.expect.md | 7 +---
...lback-conditional-access-noAlloc.expect.md | 9 +----
...less-specific-conditional-access.expect.md | 14 +------
...less-specific-conditional-access.expect.md | 14 +------
...specific-conditional-value-block.expect.md | 28 +-------------
...emo-property-call-chained-object.expect.md | 6 +--
...ession-with-conditional-optional.expect.md | 13 +------
...mber-expression-with-conditional.expect.md | 13 +------
25 files changed, 63 insertions(+), 320 deletions(-)
diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
index 3c131ea653..29dc346d00 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
@@ -12,6 +12,24 @@ import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
import invariant from 'invariant';
+// Number of context lines to display above the source of an error
+const CODEFRAME_LINES_ABOVE = 2;
+// Number of context lines to display below the source of an error
+const CODEFRAME_LINES_BELOW = 3;
+// Max number of lines for the _source_ of an error, before we abbreviate
+// the display of the source portion
+const CODEFRAME_MAX_LINES = 3;
+// When the error source exceeds the above threshold, how many lines of
+// the source should be displayed? We show:
+// - CODEFRAME_LINES_ABOVE context lines
+// - CODEFRAME_ABBREVIATED_SOURCE_LINES of the error
+// - '...' ellipsis
+// - CODEFRAME_ABBREVIATED_SOURCE_LINES of the error
+// - CODEFRAME_LINES_BELOW context lines
+//
+// This value must be at least 2 or else we'll cut off important parts of the error message
+const CODEFRAME_ABBREVIATED_SOURCE_LINES = 2;
+
export enum ErrorSeverity {
/**
* An actionable error that the developer can fix. For example, product code errors should be
@@ -496,7 +514,7 @@ function printCodeFrame(
loc: t.SourceLocation,
message: string,
): string {
- return codeFrameColumns(
+ const printed = codeFrameColumns(
source,
{
start: {
@@ -510,8 +528,25 @@ function printCodeFrame(
},
{
message,
+ linesAbove: CODEFRAME_LINES_ABOVE,
+ linesBelow: CODEFRAME_LINES_BELOW,
},
);
+ const lines = printed.split(/\r?\n/);
+ if (loc.end.line - loc.start.line < CODEFRAME_MAX_LINES) {
+ return printed;
+ }
+ const pipeIndex = lines[0].indexOf('|');
+ return [
+ ...lines.slice(
+ 0,
+ CODEFRAME_LINES_ABOVE + CODEFRAME_ABBREVIATED_SOURCE_LINES,
+ ),
+ '> ' + ' '.repeat(pipeIndex - 2) + '…',
+ ...lines.slice(
+ -(CODEFRAME_LINES_BELOW + CODEFRAME_ABBREVIATED_SOURCE_LINES),
+ ),
+ ].join('\n');
}
function printErrorSummary(category: ErrorCategory, message: string): string {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md
index 7913666aa3..bd3d513a8f 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md
@@ -35,18 +35,7 @@ error.hoist-optional-member-expression-with-conditional-optional.ts:4:23
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
-> 5 | const x = [];
- | ^^^^^^^^^^^^^^^^^
-> 6 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 7 | if (props.cond) {
- | ^^^^^^^^^^^^^^^^^
-> 8 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 9 | }
- | ^^^^^^^^^^^^^^^^^
-> 10 | return x;
- | ^^^^^^^^^^^^^^^^^
+> …
> 11 | }, [props?.items, props.cond]);
| ^^^^ Could not preserve existing manual memoization
12 | return (
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md
index b60a911875..5a512f9c7e 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md
@@ -35,18 +35,7 @@ error.hoist-optional-member-expression-with-conditional.ts:4:23
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
-> 5 | const x = [];
- | ^^^^^^^^^^^^^^^^^
-> 6 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 7 | if (props.cond) {
- | ^^^^^^^^^^^^^^^^^
-> 8 | x.push(props.items);
- | ^^^^^^^^^^^^^^^^^
-> 9 | }
- | ^^^^^^^^^^^^^^^^^
-> 10 | return x;
- | ^^^^^^^^^^^^^^^^^
+> …
> 11 | }, [props?.items, props.cond]);
| ^^^^ Could not preserve existing manual memoization
12 | return (
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md
index fa5cc6d53f..697a786f49 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md
@@ -29,12 +29,7 @@ error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.ts:3:2
2 | function Component(props) {
> 3 | const data = useMemo(() => {
| ^^^^^^^
-> 4 | // actual code is non-optional
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 5 | return props.items.edges.nodes ?? [];
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 6 | // deps are optional
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> …
> 7 | }, [props.items?.edges?.nodes]);
| ^^^^ Could not preserve existing manual memoization
8 | return ;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md
index 8592ae65e4..ee69f200ac 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md
@@ -57,35 +57,7 @@ This argument is a function which may reassign or mutate `cache` after render, w
20 | ): TInput => TOutput {
> 21 | return useMemo(() => {
| ^^^^^^^^^^^^^^^
-> 22 | // The original issue is that `cache` was not memoized together with the returned
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 23 | // function. This was because neither appears to ever be mutated — the function
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 24 | // is known to mutate `cache` but the function isn't called.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 25 | //
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 26 | // The fix is to detect cases like this — functions that are mutable but not called -
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 27 | // and ensure that their mutable captures are aliased together into the same scope.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 28 | const cache = new WeakMap();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 29 | return input => {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 30 | let output = cache.get(input);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 31 | if (output == null) {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 32 | output = map(input);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 33 | cache.set(input, output);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 34 | }
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 35 | return output;
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 36 | };
+> …
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 37 | }, [map]);
| ^^^^^^^^^^^^ This function may (indirectly) reassign or modify `cache` after render
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md
index 8d603c629b..32b062d891 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md
@@ -45,10 +45,7 @@ error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency
18 | function useInputValue(input) {
> 19 | const object = React.useMemo(() => {
| ^^^^^^^^^^^^^^^^^^^^^
-> 20 | const {value} = transform(input);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 21 | return {value};
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> …
> 22 | }, [input]);
| ^^^^^^^^^^^^^^ Could not preserve existing memoization
23 | mutate(object);
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-in-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-in-loop-with-context-variable-iterator.expect.md
index e5bcf704d1..775306e348 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-in-loop-with-context-variable-iterator.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-in-loop-with-context-variable-iterator.expect.md
@@ -40,18 +40,7 @@ error.todo-for-in-loop-with-context-variable-iterator.ts:8:2
7 | // within a closure, the `onClick` handler of each item
> 8 | for (let key in props.data) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 9 | key = key ?? null; // no-op reassignment to force a context variable
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 10 | items.push(
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 11 | data.set(key)}>
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 12 | {key}
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 13 |
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 14 | );
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> …
> 15 | }
| ^^^^ Support non-trivial for..in inits
16 | return {items}
;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-of-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-of-loop-with-context-variable-iterator.expect.md
index 800822b674..f487025bcb 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-of-loop-with-context-variable-iterator.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-of-loop-with-context-variable-iterator.expect.md
@@ -40,18 +40,7 @@ error.todo-for-of-loop-with-context-variable-iterator.ts:8:2
7 | // within a closure, the `onClick` handler of each item
> 8 | for (let item of props.data) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 9 | item = item ?? {}; // reassignment to force a context variable
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 10 | items.push(
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 11 | data.set(item)}>
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 12 | {item.id}
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 13 |
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 14 | );
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> …
> 15 | }
| ^^^^ Support non-trivial for..of inits
16 | return {items}
;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.expect.md
index d82575b8c3..e6bb7274e2 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.expect.md
@@ -35,22 +35,7 @@ error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.ts:6:2
5 | let el;
> 6 | try {
| ^^^^^
-> 7 | let value;
- | ^^^^^^^^^^^^^^
-> 8 | try {
- | ^^^^^^^^^^^^^^
-> 9 | value = identity(props.foo);
- | ^^^^^^^^^^^^^^
-> 10 | } catch {
- | ^^^^^^^^^^^^^^
-> 11 | el =
;
- | ^^^^^^^^^^^^^^
-> 12 | }
- | ^^^^^^^^^^^^^^
-> 13 | } finally {
- | ^^^^^^^^^^^^^^
-> 14 | console.log(el);
- | ^^^^^^^^^^^^^^
+> …
> 15 | }
| ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
16 | return el;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.expect.md
index e8a2920564..7b4307bdf2 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.expect.md
@@ -28,12 +28,7 @@ error.todo-invalid-jsx-in-try-with-finally.ts:4:2
3 | let el;
> 4 | try {
| ^^^^^
-> 5 | el =
;
- | ^^^^^^^^^^^^^^^^^
-> 6 | } finally {
- | ^^^^^^^^^^^^^^^^^
-> 7 | console.log(el);
- | ^^^^^^^^^^^^^^^^^
+> …
> 8 | }
| ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
9 | return el;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
index 32db5b2e7c..f11fa57689 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
@@ -101,14 +101,7 @@ error.todo-kitchensink.ts:5:2
4 |
> 5 | class Bar {
| ^^^^^^^^^^^
-> 6 | #secretSauce = 42;
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 7 | constructor() {
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 8 | console.log(this.#secretSauce);
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 9 | }
- | ^^^^^^^^^^^^^^^^^^^^^^
+> …
> 10 | }
| ^^^^ Inline `class` declarations are not supported
11 |
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md
index e9772e6799..9b61883725 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md
@@ -61,26 +61,7 @@ error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.ts:7:25
6 | function Component({x}) {
> 7 | const object = useMemo(() => {
| ^^^^^^^
-> 8 | return identity({
- | ^^^^^^^^^^^^^^^^^^^^^
-> 9 | callback: () => {
- | ^^^^^^^^^^^^^^^^^^^^^
-> 10 | // This is a bug in our dependency inference: we stop capturing dependencies
- | ^^^^^^^^^^^^^^^^^^^^^
-> 11 | // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b`
- | ^^^^^^^^^^^^^^^^^^^^^
-> 12 | // was non-nullish, then we can access `.c.d?.e`. Thus we should take the
- | ^^^^^^^^^^^^^^^^^^^^^
-> 13 | // full property chain, exactly as-is with optionals/non-optionals, as a
- | ^^^^^^^^^^^^^^^^^^^^^
-> 14 | // dependency
- | ^^^^^^^^^^^^^^^^^^^^^
-> 15 | return identity(x.a.b?.c.d?.e);
- | ^^^^^^^^^^^^^^^^^^^^^
-> 16 | },
- | ^^^^^^^^^^^^^^^^^^^^^
-> 17 | });
- | ^^^^^^^^^^^^^^^^^^^^^
+> …
> 18 | }, [x.a.b?.c.d?.e]);
| ^^^^ Could not preserve existing manual memoization
19 | const result = useMemo(() => {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md
index be31341d15..e356b92e08 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md
@@ -62,14 +62,7 @@ React Compiler has skipped optimizing this component because the existing manual
10 |
> 11 | const logData = useMemo(() => {
| ^^^^^^^^^^^^^^^
-> 12 | const item = items[index];
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 13 | return {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 14 | key: item.key,
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-> 15 | };
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+> …
> 16 | }, [index, items]);
| ^^^^^^^^^^^^^^^^^^^^^ Could not preserve existing memoization
17 |
@@ -96,20 +89,7 @@ React Compiler has skipped optimizing this component because the existing manual
18 | const setCurrentIndex = useCallback(
> 19 | (index: number) => {
| ^^^^^^^^^^^^^^^^^^^^
-> 20 | const object = {
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 21 | tracking: logData.key,
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 22 | };
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 23 | // We infer that this may mutate `object`, which in turn aliases
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 24 | // data from `logData`, such that `logData` may be mutated.
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 25 | LogEvent.log(() => object);
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 26 | setIndex(index);
- | ^^^^^^^^^^^^^^^^^^^^^^
+> …
> 27 | },
| ^^^^^^ Could not preserve existing memoization
28 | [index, logData, items]
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md
index 38d10ee0d1..d080efff9b 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md
@@ -43,24 +43,7 @@ error.todo-syntax.ts:11:2
10 | 'use memo';
> 11 | useSpecialEffect(
| ^^^^^^^^^^^^^^^^^
-> 12 | () => {
- | ^^^^^^^^^^^
-> 13 | try {
- | ^^^^^^^^^^^
-> 14 | console.log(prop1);
- | ^^^^^^^^^^^
-> 15 | } finally {
- | ^^^^^^^^^^^
-> 16 | console.log('exiting');
- | ^^^^^^^^^^^
-> 17 | }
- | ^^^^^^^^^^^
-> 18 | },
- | ^^^^^^^^^^^
-> 19 | [prop1],
- | ^^^^^^^^^^^
-> 20 | AUTODEPS
- | ^^^^^^^^^^^
+> …
> 21 | );
| ^^^^ Cannot infer dependencies
22 | return {prop1}
;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md
index 0a5cde5cd6..f18cc75143 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md
@@ -33,15 +33,7 @@ error.wrong-index.ts:6:2
5 | function Component({foo}) {
> 6 | useEffectWrapper(
| ^^^^^^^^^^^^^^^^^
-> 7 | () => {
- | ^^^^^^^^^^^
-> 8 | console.log(foo);
- | ^^^^^^^^^^^
-> 9 | },
- | ^^^^^^^^^^^
-> 10 | [foo],
- | ^^^^^^^^^^^
-> 11 | AUTODEPS
+> …
| ^^^^^^^^^^^
> 12 | );
| ^^^^ Cannot infer dependencies
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md
index ed2e61d8ee..26eff22ed7 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md
@@ -37,16 +37,7 @@ error.hoist-useCallback-conditional-access-own-scope.ts:5:21
4 | function Component({propA, propB}) {
> 5 | return useCallback(() => {
| ^^^^^^^
-> 6 | if (propA) {
- | ^^^^^^^^^^^^^^^^
-> 7 | return {
- | ^^^^^^^^^^^^^^^^
-> 8 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^
-> 9 | };
- | ^^^^^^^^^^^^^^^^
-> 10 | }
- | ^^^^^^^^^^^^^^^^
+> …
> 11 | }, [propA, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
12 | }
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md
index a16ef317b0..440478ee07 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md
@@ -40,20 +40,7 @@ error.hoist-useCallback-infer-conditional-value-block.ts:6:21
5 | function useHook(propA, propB) {
> 6 | return useCallback(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (identity(null) ?? propA.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
- | ^^^^^^^^^^^^^^^^^
+> …
> 14 | }, [propA.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
15 | }
@@ -69,20 +56,7 @@ error.hoist-useCallback-infer-conditional-value-block.ts:6:21
5 | function useHook(propA, propB) {
> 6 | return useCallback(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (identity(null) ?? propA.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
- | ^^^^^^^^^^^^^^^^^
+> …
> 14 | }, [propA.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
15 | }
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.preserve-use-memo-ref-missing-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.preserve-use-memo-ref-missing-reactive.expect.md
index 3eb8e6cb26..4f1597cdf7 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.preserve-use-memo-ref-missing-reactive.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.preserve-use-memo-ref-missing-reactive.expect.md
@@ -39,12 +39,7 @@ error.preserve-use-memo-ref-missing-reactive.ts:9:21
8 |
> 9 | return useCallback(() => {
| ^^^^^^^
-> 10 | if (ref != null) {
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 11 | ref.current();
- | ^^^^^^^^^^^^^^^^^^^^^^
-> 12 | }
- | ^^^^^^^^^^^^^^^^^^^^^^
+> …
> 13 | }, []);
| ^^^^ Could not preserve existing manual memoization
14 | }
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md
index 075458831b..07d37fb156 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md
@@ -36,14 +36,7 @@ error.useCallback-conditional-access-noAlloc.ts:5:21
4 | function Component({propA, propB}) {
> 5 | return useCallback(() => {
| ^^^^^^^
-> 6 | return {
- | ^^^^^^^^^^^^
-> 7 | value: propB?.x.y,
- | ^^^^^^^^^^^^
-> 8 | other: propA,
- | ^^^^^^^^^^^^
-> 9 | };
- | ^^^^^^^^^^^^
+> …
> 10 | }, [propA, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
11 | }
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md
index 077b9aa9f6..0d81b0cf7a 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md
@@ -35,19 +35,7 @@ error.useCallback-infer-less-specific-conditional-access.ts:6:21
5 | function Component({propA, propB}) {
> 6 | return useCallback(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (propA?.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
+> …
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA?.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md
index d93c52a10c..5cb50ad525 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md
@@ -35,19 +35,7 @@ error.useMemo-infer-less-specific-conditional-access.ts:6:17
5 | function Component({propA, propB}) {
> 6 | return useMemo(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (propA?.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
+> …
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA?.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md
index 9d35f52504..943f64720a 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md
@@ -35,19 +35,7 @@ error.useMemo-infer-less-specific-conditional-value-block.ts:6:17
5 | function Component({propA, propB}) {
> 6 | return useMemo(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (identity(null) ?? propA.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
+> …
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
@@ -63,19 +51,7 @@ error.useMemo-infer-less-specific-conditional-value-block.ts:6:17
5 | function Component({propA, propB}) {
> 6 | return useMemo(() => {
| ^^^^^^^
-> 7 | const x = {};
- | ^^^^^^^^^^^^^^^^^
-> 8 | if (identity(null) ?? propA.a) {
- | ^^^^^^^^^^^^^^^^^
-> 9 | mutate(x);
- | ^^^^^^^^^^^^^^^^^
-> 10 | return {
- | ^^^^^^^^^^^^^^^^^
-> 11 | value: propB.x.y,
- | ^^^^^^^^^^^^^^^^^
-> 12 | };
- | ^^^^^^^^^^^^^^^^^
-> 13 | }
+> …
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA.a, propB.x.y]);
| ^^^^ Could not preserve existing manual memoization
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-property-call-chained-object.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-property-call-chained-object.expect.md
index 00cc3fb839..fe8bbd64b0 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-property-call-chained-object.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-property-call-chained-object.expect.md
@@ -30,11 +30,7 @@ error.useMemo-property-call-chained-object.ts:5:17
4 | function Component({propA}) {
> 5 | return useMemo(() => {
| ^^^^^^^
-> 6 | return {
- | ^^^^^^^^^^^^
-> 7 | value: propA.x().y,
- | ^^^^^^^^^^^^
-> 8 | };
+> …
| ^^^^^^^^^^^^
> 9 | }, [propA.x]);
| ^^^^ Could not preserve existing manual memoization
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional-optional.expect.md
index 14ea4e7593..adf726b370 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional-optional.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional-optional.expect.md
@@ -35,18 +35,7 @@ error.todo-optional-member-expression-with-conditional-optional.ts:4:23
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
-> 5 | const x = [];
- | ^^^^^^^^^^^^^^^^^
-> 6 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 7 | if (props.cond) {
- | ^^^^^^^^^^^^^^^^^
-> 8 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 9 | }
- | ^^^^^^^^^^^^^^^^^
-> 10 | return x;
- | ^^^^^^^^^^^^^^^^^
+> …
> 11 | }, [props?.items, props.cond]);
| ^^^^ Could not preserve existing manual memoization
12 | return (
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional.expect.md
index f3fdb07697..dc2e11fd78 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/propagate-scope-deps-hir-fork/error.todo-optional-member-expression-with-conditional.expect.md
@@ -35,18 +35,7 @@ error.todo-optional-member-expression-with-conditional.ts:4:23
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
-> 5 | const x = [];
- | ^^^^^^^^^^^^^^^^^
-> 6 | x.push(props?.items);
- | ^^^^^^^^^^^^^^^^^
-> 7 | if (props.cond) {
- | ^^^^^^^^^^^^^^^^^
-> 8 | x.push(props.items);
- | ^^^^^^^^^^^^^^^^^
-> 9 | }
- | ^^^^^^^^^^^^^^^^^
-> 10 | return x;
- | ^^^^^^^^^^^^^^^^^
+> …
> 11 | }, [props?.items, props.cond]);
| ^^^^ Could not preserve existing manual memoization
12 | return (