From 67e743fba576efc66e32d2d12b25552e316e24ce Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Mon, 25 Aug 2025 16:39:23 +0200 Subject: [PATCH 1/5] [compiler] Fix missing dependency in eslint-plugin-react-hooks (#34287) --- packages/eslint-plugin-react-hooks/package.json | 2 +- yarn.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 25215d71e530f..8f7cfc361d1ff 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -41,7 +41,7 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-transform-private-methods": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" diff --git a/yarn.lock b/yarn.lock index c6d9d03cead57..1621ef16b21e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1122,6 +1122,14 @@ "@babel/helper-create-class-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" @@ -1882,7 +1890,7 @@ "@babel/helper-create-class-features-plugin" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1" -"@babel/plugin-transform-private-methods@^7.24.4", "@babel/plugin-transform-private-methods@^7.25.9": +"@babel/plugin-transform-private-methods@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== From e42f3d30cae0f3b9c6205ba37fd9497c6fc199e8 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Mon, 25 Aug 2025 16:40:56 +0200 Subject: [PATCH 2/5] [DevTools] Include `name` prop when highlighting host instances (#34258) --- packages/react-devtools-shared/src/devtools/views/hooks.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index 5bdc93fb24575..da69d6b493a19 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -355,8 +355,12 @@ export function useHighlightHostInstance(): { const element = store.getElementByID(id); const rendererID = store.getRendererIDForElement(id); if (element !== null && rendererID !== null) { + let displayName = element.displayName; + if (displayName !== null && element.nameProp !== null) { + displayName += ` name="${element.nameProp}"`; + } bridge.send('highlightHostInstance', { - displayName: element.displayName, + displayName, hideAfterTimeout: false, id, openBuiltinElementsPanel: false, From df10309e2b884868d2032a4d6506714b3953b027 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Mon, 25 Aug 2025 11:02:56 -0400 Subject: [PATCH 3/5] Update Flow to 0.279 (#34277) Multiple of these version upgrades required minor additional annotations. --- .eslintrc.js | 3 ++ package.json | 8 ++-- .../src/ReactFlightPerformanceTrack.js | 4 +- .../react-debug-tools/src/ReactDebugHooks.js | 2 +- .../src/backend/fiber/renderer.js | 2 +- .../Components/InspectedElementHooksTree.js | 5 ++- .../src/devtools/views/ModalDialog.js | 2 +- .../src/devtools/views/Profiler/Tooltip.js | 10 ++++- .../src/hooks/SourceMapConsumer.js | 12 +++++- .../src/app/InspectableElements/Contexts.js | 2 +- .../src/import-worker/preprocessData.js | 3 +- .../withVerticalScrollbarLayout.js | 6 +-- .../src/client/ReactFiberConfigDOM.js | 1 + .../src/ReactCapturedValue.js | 2 +- .../src/ReactFiberPerformanceTrack.js | 8 ++-- .../src/ReactFlightESMReferences.js | 8 ++-- .../src/ReactFlightParcelReferences.js | 8 ++-- .../src/server/ReactFlightDOMServerBrowser.js | 2 +- .../src/server/ReactFlightDOMServerEdge.js | 2 +- .../src/server/ReactFlightDOMServerNode.js | 2 +- .../src/ReactFlightTurbopackReferences.js | 10 ++--- .../src/ReactFlightWebpackReferences.js | 14 +++---- packages/react-server/src/ReactFizzServer.js | 18 +++++++-- .../ReactFlightServerTemporaryReferences.js | 2 +- yarn.lock | 38 +++++++++---------- 25 files changed, 104 insertions(+), 70 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index e8ace6311d8dc..18a3112e7382b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -583,8 +583,11 @@ module.exports = { mixin$Animatable: 'readonly', MouseEventHandler: 'readonly', NavigateEvent: 'readonly', + PerformanceMeasureOptions: 'readonly', PropagationPhases: 'readonly', PropertyDescriptor: 'readonly', + PropertyDescriptorMap: 'readonly', + Proxy$traps: 'readonly', React$Component: 'readonly', React$Config: 'readonly', React$Context: 'readonly', diff --git a/package.json b/package.json index f5b3052010c30..3439ed756a346 100644 --- a/package.json +++ b/package.json @@ -74,15 +74,15 @@ "eslint-plugin-react-internal": "link:./scripts/eslint-rules", "fbjs-scripts": "^3.0.1", "filesize": "^6.0.1", - "flow-bin": "^0.274", - "flow-remove-types": "^2.274", + "flow-bin": "^0.279.0", + "flow-remove-types": "^2.279.0", "flow-typed": "^4.1.1", "glob": "^7.1.6", "glob-stream": "^6.1.0", "google-closure-compiler": "^20230206.0.0", "gzip-size": "^5.1.1", - "hermes-eslint": "^0.25.1", - "hermes-parser": "^0.25.1", + "hermes-eslint": "^0.32.0", + "hermes-parser": "^0.32.0", "jest": "^29.4.2", "jest-cli": "^29.4.2", "jest-diff": "^29.4.2", diff --git a/packages/react-client/src/ReactFlightPerformanceTrack.js b/packages/react-client/src/ReactFlightPerformanceTrack.js index 717d536dc94a1..81474767363fd 100644 --- a/packages/react-client/src/ReactFlightPerformanceTrack.js +++ b/packages/react-client/src/ReactFlightPerformanceTrack.js @@ -153,7 +153,7 @@ export function logComponentAborted( const entryName = isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']'; if (__DEV__) { - const properties = [ + const properties: Array<[string, string]> = [ [ 'Aborted', 'The stream was aborted before this Component finished rendering.', @@ -215,7 +215,7 @@ export function logComponentErrored( String(error.message) : // eslint-disable-next-line react-internal/safe-string-coercion String(error); - const properties = [['Error', message]]; + const properties: Array<[string, string]> = [['Error', message]]; if (componentInfo.key != null) { addValueToProperties('key', componentInfo.key, properties, 0, ''); } diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 5a49b2e073c0f..db9495a97dd4d 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -789,7 +789,7 @@ const Dispatcher: DispatcherType = { // create a proxy to throw a custom error // in case future versions of React adds more hooks -const DispatcherProxyHandler = { +const DispatcherProxyHandler: Proxy$traps = { get(target: DispatcherType, prop: string) { if (target.hasOwnProperty(prop)) { // $FlowFixMe[invalid-computed-prop] diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 88a9b70093fd1..45ff28a43cbd6 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2217,7 +2217,7 @@ export function attach( } if (typeof instance.getClientRects === 'function') { // DOM - const result = []; + const result: Array = []; const doc = instance.ownerDocument; const win = doc && doc.defaultView; const scrollX = win ? win.scrollX : 0; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js index 2739325749786..80c9f2f92d0b1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js @@ -223,7 +223,10 @@ function HookView({ const hookDisplayName = hookName ? ( <> {name} - {!!hookName && ({hookName})} + { + // $FlowFixMe[constant-condition] + !!hookName && ({hookName}) + } ) : ( name diff --git a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js index 542961b4c932b..a584d9a9e3d14 100644 --- a/packages/react-devtools-shared/src/devtools/views/ModalDialog.js +++ b/packages/react-devtools-shared/src/devtools/views/ModalDialog.js @@ -75,7 +75,7 @@ function dialogReducer(state: State, action: Action) { content: action.content, id: action.id, title: action.title || null, - }, + } as Dialog, ], }; default: diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js index 6b92ef26c791c..124be4286d1a8 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/Tooltip.js @@ -64,7 +64,7 @@ function getTooltipPosition( mouseY: number, width: number, }, -) { +): {left: string, top: string} { const {height, mouseX, mouseY, width} = mousePosition; let top: number | string = 0; let left: number | string = 0; @@ -108,7 +108,13 @@ function getMousePosition( } } - const {height, left, top, width} = targetContainer.getBoundingClientRect(); + const {height, left, top, width} = + targetContainer.getBoundingClientRect() as { + height: number, + left: number, + top: number, + width: number, + }; const mouseX = mouseEvent.clientX - left; const mouseY = mouseEvent.clientY - top; diff --git a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js index 3fcf991480ac0..d06038ad93e38 100644 --- a/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js +++ b/packages/react-devtools-shared/src/hooks/SourceMapConsumer.js @@ -145,13 +145,21 @@ type Section = { }; function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) { - let lastOffset = { + let lastOffset: { + line: number, + column: number, + ... + } = { line: -1, column: 0, }; const sections: Array
= sourceMapJSON.sections.map(section => { - const offset = section.offset; + const offset: { + line: number, + column: number, + ... + } = section.offset; const offsetLine = offset.line; const offsetColumn = offset.column; diff --git a/packages/react-devtools-shell/src/app/InspectableElements/Contexts.js b/packages/react-devtools-shell/src/app/InspectableElements/Contexts.js index ace7af6d55d3b..ebd2470ac60f7 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/Contexts.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/Contexts.js @@ -28,7 +28,7 @@ const contextData = { bool: true, func: someNamedFunction, number: 123, - object: {outer: {inner: {}}}, + object: {outer: {inner: {} as {...}}}, string: 'abc', symbol: Symbol.for('symbol'), null: null, diff --git a/packages/react-devtools-timeline/src/import-worker/preprocessData.js b/packages/react-devtools-timeline/src/import-worker/preprocessData.js index f3186e65a745f..8d45be544d742 100644 --- a/packages/react-devtools-timeline/src/import-worker/preprocessData.js +++ b/packages/react-devtools-timeline/src/import-worker/preprocessData.js @@ -28,6 +28,7 @@ import type { TimelineData, SchedulingEvent, SuspenseEvent, + Snapshot, } from '../types'; import { REACT_TOTAL_NUM_LANES, @@ -350,7 +351,7 @@ function processScreenshot( ) { const encodedSnapshot = event.args.snapshot; // Base 64 encoded - const snapshot = { + const snapshot: Snapshot = { height: 0, image: null, imageSource: `data:image/png;base64,${encodedSnapshot}`, diff --git a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js index 7fd3ea2d26e83..2e3a8fa8d225f 100644 --- a/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js +++ b/packages/react-devtools-timeline/src/view-base/vertical-scroll-overflow/withVerticalScrollbarLayout.js @@ -7,7 +7,7 @@ * @flow */ -import type {Layouter} from '../layouter'; +import type {LayoutInfo, Layouter} from '../layouter'; /** * Assumes {@param layout} will only contain 2 views. @@ -25,7 +25,7 @@ export const withVerticalScrollbarLayout: Layouter = ( ? scrollbarLayoutInfo.view.desiredSize().width : 0; - const laidOutContentLayoutInfo = { + const laidOutContentLayoutInfo: LayoutInfo = { ...contentLayoutInfo, frame: { origin: contentLayoutInfo.view.frame.origin, @@ -35,7 +35,7 @@ export const withVerticalScrollbarLayout: Layouter = ( }, }, }; - const laidOutScrollbarLayoutInfo = { + const laidOutScrollbarLayoutInfo: LayoutInfo = { ...scrollbarLayoutInfo, frame: { origin: { diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index ffb50de0d9d8b..8155704d689ee 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -2274,6 +2274,7 @@ function mergeTranslate(translateA: ?string, translateB: ?string): string { return translateB || ''; } if (!translateB || translateB === 'none') { + // $FlowFixMe[constant-condition] return translateA || ''; } const partsA = translateA.split(' '); diff --git a/packages/react-reconciler/src/ReactCapturedValue.js b/packages/react-reconciler/src/ReactCapturedValue.js index c1c5822bebe26..d53489cc3b725 100644 --- a/packages/react-reconciler/src/ReactCapturedValue.js +++ b/packages/react-reconciler/src/ReactCapturedValue.js @@ -30,7 +30,7 @@ export function createCapturedValueAtFiber( if (existing !== undefined) { return existing; } - const captured = { + const captured: CapturedValue = { value, source, stack: getStackByFiberInDevAndProd(source), diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 57f121f6ec0cd..00ae21d6476c8 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -179,7 +179,7 @@ const reusableComponentDevToolDetails = { track: COMPONENTS_TRACK, }; -const reusableComponentOptions = { +const reusableComponentOptions: PerformanceMeasureOptions = { start: -0, end: -0, detail: { @@ -351,7 +351,7 @@ export function logComponentErrored( // error boundary itself. debugTask = fiber._debugTask; } - const options = { + const options: PerformanceMeasureOptions = { start: startTime, end: endTime, detail: { @@ -992,7 +992,7 @@ export function logRecoveredRenderPhase( String(error); properties.push(['Recoverable Error', message]); } - const options = { + const options: PerformanceMeasureOptions = { start: startTime, end: endTime, detail: { @@ -1199,7 +1199,7 @@ export function logCommitErrored( String(error); properties.push(['Error', message]); } - const options = { + const options: PerformanceMeasureOptions = { start: startTime, end: endTime, detail: { diff --git a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js index b8a8749c10277..6c2737e89e726 100644 --- a/packages/react-server-dom-esm/src/ReactFlightESMReferences.js +++ b/packages/react-server-dom-esm/src/ReactFlightESMReferences.js @@ -66,7 +66,7 @@ function bind(this: ServerReference): any { const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( (newFn: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -82,7 +82,7 @@ function bind(this: ServerReference): any { $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } return newFn; @@ -101,7 +101,7 @@ export function registerServerReference( const $$bound = {value: null, configurable: true}; return Object.defineProperties( (reference: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -117,6 +117,6 @@ export function registerServerReference( $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } diff --git a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js index da7e1c0a00bbc..3e7b603288e6a 100644 --- a/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js +++ b/packages/react-server-dom-parcel/src/ReactFlightParcelReferences.js @@ -73,7 +73,7 @@ function bind(this: ServerReference): any { const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( (newFn: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -89,7 +89,7 @@ function bind(this: ServerReference): any { $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } return newFn; @@ -108,7 +108,7 @@ export function registerServerReference( const $$bound = {value: null, configurable: true}; return Object.defineProperties( (reference: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -124,6 +124,6 @@ export function registerServerReference( $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js index c2e7e6062eddc..b55b47688746b 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerBrowser.js @@ -240,7 +240,7 @@ export function prerender( }); } -let serverManifest = {}; +let serverManifest: ServerManifest = {}; export function registerServerActions(manifest: ServerManifest) { // This function is called by the bundler to register the manifest. serverManifest = manifest; diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js index ba66282f1b064..627124225c4ea 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerEdge.js @@ -245,7 +245,7 @@ export function prerender( }); } -let serverManifest = {}; +let serverManifest: ServerManifest = {}; export function registerServerActions(manifest: ServerManifest) { // This function is called by the bundler to register the manifest. serverManifest = manifest; diff --git a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js index 79f783ea4701e..d51cf3bc0cea3 100644 --- a/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js +++ b/packages/react-server-dom-parcel/src/server/ReactFlightDOMServerNode.js @@ -554,7 +554,7 @@ export function prerender( }); } -let serverManifest = {}; +let serverManifest: ServerManifest = {}; export function registerServerActions(manifest: ServerManifest) { // This function is called by the bundler to register the manifest. serverManifest = manifest; diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js index 707a68a117e5c..1b4525a354905 100644 --- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js +++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js @@ -80,7 +80,7 @@ function bind(this: ServerReference): any { const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( (newFn: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -96,7 +96,7 @@ function bind(this: ServerReference): any { $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } return newFn; @@ -115,7 +115,7 @@ export function registerServerReference( const $$bound = {value: null, configurable: true}; return Object.defineProperties( (reference: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -131,13 +131,13 @@ export function registerServerReference( $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } const PROMISE_PROTOTYPE = Promise.prototype; -const deepProxyHandlers = { +const deepProxyHandlers: Proxy$traps = { get: function ( target: Function, name: string | symbol, diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index 707a68a117e5c..de437414ef158 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -80,7 +80,7 @@ function bind(this: ServerReference): any { const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args}; return Object.defineProperties( (newFn: any), - __DEV__ + (__DEV__ ? { $$typeof, $$id, @@ -96,7 +96,7 @@ function bind(this: ServerReference): any { $$id, $$bound, bind: {value: bind, configurable: true}, - }, + }) as PropertyDescriptorMap, ); } return newFn; @@ -116,7 +116,7 @@ export function registerServerReference( return Object.defineProperties( (reference: any), __DEV__ - ? { + ? ({ $$typeof, $$id, $$bound, @@ -125,19 +125,19 @@ export function registerServerReference( configurable: true, }, bind: {value: bind, configurable: true}, - } - : { + } as PropertyDescriptorMap) + : ({ $$typeof, $$id, $$bound, bind: {value: bind, configurable: true}, - }, + } as PropertyDescriptorMap), ); } const PROMISE_PROTOTYPE = Promise.prototype; -const deepProxyHandlers = { +const deepProxyHandlers: Proxy$traps = { get: function ( target: Function, name: string | symbol, diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 8681d03b22907..7d54dff3d080a 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1375,7 +1375,11 @@ function renderSuspenseBoundary( // where we try to skip rendering the fallback if the content itself can render synchronously const trackedPostpones = request.trackedPostpones; - const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]]; + const fallbackKeyPath: KeyNode = [ + keyPath[0], + 'Suspense Fallback', + keyPath[2], + ]; const fallbackReplayNode: ReplayNode = [ fallbackKeyPath[1], fallbackKeyPath[2], @@ -1575,7 +1579,11 @@ function renderSuspenseBoundary( task.row = prevRow; } - const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]]; + const fallbackKeyPath: KeyNode = [ + keyPath[0], + 'Suspense Fallback', + keyPath[2], + ]; // We create suspended task for the fallback because we don't want to actually work // on it yet in case we finish the main content, so we queue for later. const suspendedFallbackTask = createRenderTask( @@ -1744,7 +1752,11 @@ function replaySuspenseBoundary( task.row = prevRow; } - const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]]; + const fallbackKeyPath: KeyNode = [ + keyPath[0], + 'Suspense Fallback', + keyPath[2], + ]; // We create suspended task for the fallback because we don't want to actually work // on it yet in case we finish the main content, so we queue for later. diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js index 1ea89c81980c1..581fdc70feb3d 100644 --- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js +++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js @@ -32,7 +32,7 @@ export function resolveTemporaryReference( return temporaryReferences.get(temporaryReference); } -const proxyHandlers = { +const proxyHandlers: Proxy$traps = { get: function ( target: Function, name: string | symbol, diff --git a/yarn.lock b/yarn.lock index 1621ef16b21e0..f58b4979fa33d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9306,12 +9306,12 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flow-bin@^0.274: - version "0.274.2" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.274.2.tgz#79cff569aab38eb04f6b1e64b899c932a51444a4" - integrity sha512-vTF+5hiC5qymweFKj7xs5ABlDqMWbB2fPkYC4MI32m652dFYsq/zvEVNc2M09udwAzplLcMIK2O9VswPfd8Isw== +flow-bin@^0.279.0: + version "0.279.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.279.0.tgz#06e502a51d735083d715ef769f43bdcb0fc2bb61" + integrity sha512-Xf0T82atOcEf5auHvJfUF+wWIxieBuUJZBu2hlAizdhAzwqSJic74ZLaL6N5SsE0SY9PxPf3Z/lBU7iRpRa9Lw== -flow-remove-types@^2.274: +flow-remove-types@^2.279.0: version "2.279.0" resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.279.0.tgz#3a3388d9158eba0f82c40d80d31d9640b883a3f5" integrity sha512-bPFloMR/A2b/r/sIsf7Ix0LaMicCJNjwhXc4xEEQVzJCIz5u7C7XDaEOXOiqveKlCYK7DcBNn6R01Cbbc9gsYA== @@ -10208,14 +10208,14 @@ hasown@^2.0.0, hasown@^2.0.2: dependencies: function-bind "^1.1.2" -hermes-eslint@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.25.1.tgz#e7d2d845256705d5e2d5cf69dc79032ac3921bb3" - integrity sha512-nPz9+oyejT1zsIwoJ2pWdUvLcN1i+tbaWCOD8PpNBYQtnHXaPXImZp/6zZHnm3bo/DoFcAgh8+SNcxLFxh7m/A== +hermes-eslint@^0.32.0: + version "0.32.0" + resolved "https://registry.yarnpkg.com/hermes-eslint/-/hermes-eslint-0.32.0.tgz#a23bcaece522f356cb1b8e990e57117dca13852d" + integrity sha512-f/gnFD3Nl7QNrclG6otkHnHsUbwYrJGO76AMtoDeIYs2+i7fFgqJgSg7DKwejTtAKBoXQg51hAQuo9cgcp1R1w== dependencies: esrecurse "^4.3.0" - hermes-estree "0.25.1" - hermes-parser "0.25.1" + hermes-estree "0.32.0" + hermes-parser "0.32.0" hermes-estree@0.25.1: version "0.25.1" @@ -10232,13 +10232,6 @@ hermes-estree@0.32.0: resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.0.tgz#bb7da6613ab8e67e334a1854ea1e209f487d307b" integrity sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ== -hermes-parser@0.25.1, hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - hermes-parser@0.29.1: version "0.29.1" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.29.1.tgz#436b24bcd7bb1e71f92a04c396ccc0716c288d56" @@ -10246,13 +10239,20 @@ hermes-parser@0.29.1: dependencies: hermes-estree "0.29.1" -hermes-parser@0.32.0: +hermes-parser@0.32.0, hermes-parser@^0.32.0: version "0.32.0" resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.32.0.tgz#7916984ef6fdce62e7415d354cf35392061cd303" integrity sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw== dependencies: hermes-estree "0.32.0" +hermes-parser@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + dependencies: + hermes-estree "0.25.1" + homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" From 75dc0026d665bd3c92e677c91252e6bf18303e45 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Mon, 25 Aug 2025 17:47:29 +0200 Subject: [PATCH 4/5] [DevTools] Initial version of Suspense timeline (#34233) --- .../src/backend/fiber/renderer.js | 7 + .../views/SuspenseTab/SuspenseTab.css | 1 + .../devtools/views/SuspenseTab/SuspenseTab.js | 9 +- .../views/SuspenseTab/SuspenseTimeline.css | 20 ++ .../views/SuspenseTab/SuspenseTimeline.js | 212 ++++++++++++++++++ .../views/SuspenseTab/SuspenseTreeList.js | 78 +------ 6 files changed, 245 insertions(+), 82 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.css create mode 100644 packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 45ff28a43cbd6..d2f5c801aab7a 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -7455,6 +7455,13 @@ export function attach( } function overrideSuspense(id: number, forceFallback: boolean) { + if (!supportsTogglingSuspense) { + // TODO:: Add getter to decide if overrideSuspense is available. + // Currently only available on inspectElement. + // Probably need a different affordance to batch since the timeline + // fallback is not the same as resuspending. + return; + } if ( typeof setSuspenseHandler !== 'function' || typeof scheduleUpdate !== 'function' diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css index 5a153545fecf6..3a7bb0735012b 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.css @@ -110,6 +110,7 @@ padding: 0.25rem; display: flex; flex-direction: row; + align-items: flex-start; } .Timeline { diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js index 8a53365df5cef..9df107feab354 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js @@ -20,6 +20,7 @@ import InspectedElement from '../Components/InspectedElement'; import portaledContent from '../portaledContent'; import styles from './SuspenseTab.css'; import SuspenseRects from './SuspenseRects'; +import SuspenseTimeline from './SuspenseTimeline'; import SuspenseTreeList from './SuspenseTreeList'; import Button from '../Button'; import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent'; @@ -46,10 +47,6 @@ type LayoutState = { }; type LayoutDispatch = (action: LayoutAction) => void; -function SuspenseTimeline() { - return
timeline
; -} - function ToggleTreeList({ dispatch, state, @@ -309,7 +306,9 @@ function SuspenseTab(_: {}) {
- +
+ +
* { + flex: 1 1 0; + overflow: visible; + visibility: hidden; + width: 0 +} + +.SuspenseTimelineActiveMarker { + visibility: visible; +} diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js new file mode 100644 index 0000000000000..df5c76b0a3727 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js @@ -0,0 +1,212 @@ +/** + * 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 type {Element, SuspenseNode} from '../../../frontend/types'; +import type Store from '../../store'; + +import * as React from 'react'; +import { + useContext, + useId, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; +import {BridgeContext, StoreContext} from '../context'; +import {TreeDispatcherContext} from '../Components/TreeContext'; +import {useHighlightHostInstance} from '../hooks'; +import {SuspenseTreeStateContext} from './SuspenseTreeContext'; +import styles from './SuspenseTimeline.css'; + +// TODO: This returns the roots which would mean we attempt to suspend the shell. +// Suspending the shell is currently not supported and we don't have a good view +// for inspecting the root. But we probably should? +function getDocumentOrderSuspense( + store: Store, + roots: $ReadOnlyArray, +): Array { + const suspenseTreeList: SuspenseNode[] = []; + for (let i = 0; i < roots.length; i++) { + const root = store.getElementByID(roots[i]); + if (root === null) { + continue; + } + const suspense = store.getSuspenseByID(root.id); + if (suspense !== null) { + const stack = [suspense]; + while (stack.length > 0) { + const current = stack.pop(); + if (current === undefined) { + continue; + } + suspenseTreeList.push(current); + // Add children in reverse order to maintain document order + for (let j = current.children.length - 1; j >= 0; j--) { + const childSuspense = store.getSuspenseByID(current.children[j]); + if (childSuspense !== null) { + stack.push(childSuspense); + } + } + } + } + } + + return suspenseTreeList; +} + +export default function SuspenseTimeline(): React$Node { + const bridge = useContext(BridgeContext); + const store = useContext(StoreContext); + const dispatch = useContext(TreeDispatcherContext); + const {shells} = useContext(SuspenseTreeStateContext); + + const timeline = useMemo(() => { + return getDocumentOrderSuspense(store, shells); + }, [store, shells]); + + const {highlightHostInstance, clearHighlightHostInstance} = + useHighlightHostInstance(); + + const inputRef = useRef(null); + const inputBBox = useRef(null); + useLayoutEffect(() => { + const input = inputRef.current; + if (input === null) { + throw new Error('Expected an input HTML element to be present.'); + } + + inputBBox.current = input.getBoundingClientRect(); + const observer = new ResizeObserver(entries => { + inputBBox.current = input.getBoundingClientRect(); + }); + observer.observe(input); + return () => { + inputBBox.current = null; + observer.disconnect(); + }; + }, []); + + const min = 0; + const max = timeline.length > 0 ? timeline.length - 1 : 0; + + const [value, setValue] = useState(max); + if (value > max) { + // TODO: Handle timeline changes + setValue(max); + } + + const markersID = useId(); + const markers: React.Node[] = useMemo(() => { + return timeline.map((suspense, index) => { + const takesUpSpace = + suspense.rects !== null && + suspense.rects.some(rect => { + return rect.width > 0 && rect.height > 0; + }); + + return takesUpSpace ? ( + + ) : ( +