diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 072df8108fccd..fe23d642a8043 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -58,6 +58,8 @@ import { createStringDecoder, prepareDestinationForModule, bindToConsole, + rendererVersion, + rendererPackageName, } from './ReactFlightClientConfig'; import {createBoundServerReference} from './ReactFlightReplyClient'; @@ -76,6 +78,10 @@ import getComponentNameFromType from 'shared/getComponentNameFromType'; import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack'; +import {injectInternals} from './ReactFlightClientDevToolsHook'; + +import ReactVersion from 'shared/ReactVersion'; + import isArray from 'shared/isArray'; import * as React from 'react'; @@ -2921,3 +2927,21 @@ export function close(response: Response): void { // ref count of pending chunks. reportGlobalError(response, new Error('Connection closed.')); } + +function getCurrentOwnerInDEV(): null | ReactComponentInfo { + return currentOwnerInDEV; +} + +export function injectIntoDevTools(): boolean { + const internals: Object = { + bundleType: __DEV__ ? 1 : 0, // Might add PROFILE later. + version: rendererVersion, + rendererPackageName: rendererPackageName, + currentDispatcherRef: ReactSharedInternals, + // Enables DevTools to detect reconciler version rather than renderer version + // which may not match for third party renderers. + reconcilerVersion: ReactVersion, + getCurrentComponentInfo: getCurrentOwnerInDEV, + }; + return injectInternals(internals); +} diff --git a/packages/react-client/src/ReactFlightClientDevToolsHook.js b/packages/react-client/src/ReactFlightClientDevToolsHook.js new file mode 100644 index 0000000000000..b8ca649d4de45 --- /dev/null +++ b/packages/react-client/src/ReactFlightClientDevToolsHook.js @@ -0,0 +1,43 @@ +/** + * 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 + */ + +declare const __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; + +export function injectInternals(internals: Object): boolean { + if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { + // No DevTools + return false; + } + const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__; + if (hook.isDisabled) { + // This isn't a real property on the hook, but it can be set to opt out + // of DevTools integration and associated warnings and logs. + // https://github.com/facebook/react/issues/3877 + return true; + } + if (!hook.supportsFlight) { + // DevTools exists, even though it doesn't support Flight. + return true; + } + try { + hook.inject(internals); + } catch (err) { + // Catch all errors because it is unsafe to throw during initialization. + if (__DEV__) { + console.error('React instrumentation encountered an error: %s.', err); + } + } + if (hook.checkDCE) { + // This is the real DevTools. + return true; + } else { + // This is likely a hook installed by Fast Refresh runtime. + return false; + } +} diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.custom.js b/packages/react-client/src/forks/ReactFlightClientConfig.custom.js index d9b031c4b5fe1..530d548a590d0 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.custom.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.custom.js @@ -49,3 +49,6 @@ export const readPartialStringChunk = $$$config.readPartialStringChunk; export const readFinalStringChunk = $$$config.readFinalStringChunk; export const bindToConsole = $$$config.bindToConsole; + +export const rendererVersion = $$$config.rendererVersion; +export const rendererPackageName = $$$config.rendererPackageName; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js index 55358ab05d10d..dbc89a2677d57 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-esm.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-esm'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js index c3c511554ee6d..6a071981be9e7 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-turbopack.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-turbopack'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js index 41bb93db386e8..73d27adefa847 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-webpack'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigBrowser'; export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js index 0a8027e3e12aa..7b75983cdd728 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-bun'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigPlain'; export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js index ac6d0933b7818..fbdb9fc683ac6 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-turbopack.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-turbopack'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js index eb17f259d3e19..f328a3e2ed7b1 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-webpack'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js index b992b01803260..05e937abdf82e 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'not-used'; + export * from 'react-client/src/ReactFlightClientStreamConfigWeb'; export * from 'react-client/src/ReactClientConsoleConfigBrowser'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js index 9a17b9269a948..8cb512ea44aee 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-esm.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-esm'; + export * from 'react-client/src/ReactFlightClientStreamConfigNode'; export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-esm/src/client/ReactFlightClientConfigBundlerESM'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js index f4226a93d86bc..ec97d45077b44 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-turbopack.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-turbopack'; + export * from 'react-client/src/ReactFlightClientStreamConfigNode'; export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js index ccc12228d837f..9840d5bc911f7 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node-webpack.js @@ -6,6 +6,8 @@ * * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-webpack'; export * from 'react-client/src/ReactFlightClientStreamConfigNode'; export * from 'react-client/src/ReactClientConsoleConfigServer'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js index 3425787b6434a..65e1252ee5b79 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-server-dom-webpack'; + export * from 'react-client/src/ReactFlightClientStreamConfigNode'; export * from 'react-client/src/ReactClientConsoleConfigServer'; export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode'; diff --git a/packages/react-client/src/forks/ReactFlightClientConfig.markup.js b/packages/react-client/src/forks/ReactFlightClientConfig.markup.js index a90acefccba39..ba86f7631ffce 100644 --- a/packages/react-client/src/forks/ReactFlightClientConfig.markup.js +++ b/packages/react-client/src/forks/ReactFlightClientConfig.markup.js @@ -7,6 +7,9 @@ * @flow */ +export {default as rendererVersion} from 'shared/ReactVersion'; +export const rendererPackageName = 'react-markup'; + import type {Thenable} from 'shared/ReactTypes'; export * from 'react-markup/src/ReactMarkupLegacyClientStreamConfig.js'; diff --git a/packages/react-devtools-shared/src/backend/flight/renderer.js b/packages/react-devtools-shared/src/backend/flight/renderer.js new file mode 100644 index 0000000000000..566ec6ed9c4e9 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/flight/renderer.js @@ -0,0 +1,101 @@ +/** + * 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 {DevToolsHook, ReactRenderer, RendererInterface} from '../types'; + +import { + patchConsoleUsingWindowValues, + registerRenderer as registerRendererWithConsole, +} from '../console'; + +export function attach( + hook: DevToolsHook, + rendererID: number, + renderer: ReactRenderer, + global: Object, +): RendererInterface { + patchConsoleUsingWindowValues(); + registerRendererWithConsole(renderer); + + return { + cleanup() {}, + clearErrorsAndWarnings() {}, + clearErrorsForElementID() {}, + clearWarningsForElementID() {}, + getSerializedElementValueByPath() {}, + deletePath() {}, + findHostInstancesForElementID() { + return null; + }, + flushInitialOperations() {}, + getBestMatchForTrackedPath() { + return null; + }, + getDisplayNameForElementID() { + return null; + }, + getNearestMountedDOMNode() { + return null; + }, + getElementIDForHostInstance() { + return null; + }, + getInstanceAndStyle() { + return { + instance: null, + style: null, + }; + }, + getOwnersList() { + return null; + }, + getPathForElement() { + return null; + }, + getProfilingData() { + throw new Error('getProfilingData not supported by this renderer'); + }, + handleCommitFiberRoot() {}, + handleCommitFiberUnmount() {}, + handlePostCommitFiberRoot() {}, + hasElementWithId() { + return false; + }, + inspectElement( + requestID: number, + id: number, + path: Array | null, + ) { + return { + id, + responseID: requestID, + type: 'not-found', + }; + }, + logElementToConsole() {}, + patchConsoleForStrictMode() {}, + getElementAttributeByPath() {}, + getElementSourceFunctionById() {}, + overrideError() {}, + overrideSuspense() {}, + overrideValueAtPath() {}, + renamePath() {}, + renderer, + setTraceUpdatesEnabled() {}, + setTrackedPath() {}, + startProfiling() {}, + stopProfiling() {}, + storeAsGlobal() {}, + unpatchConsoleForStrictMode() {}, + updateComponentFilters() {}, + getEnvironmentNames() { + return []; + }, + }; +} diff --git a/packages/react-devtools-shared/src/backend/index.js b/packages/react-devtools-shared/src/backend/index.js index f264134b4bb53..03a8f0755d496 100644 --- a/packages/react-devtools-shared/src/backend/index.js +++ b/packages/react-devtools-shared/src/backend/index.js @@ -9,8 +9,10 @@ import Agent from './agent'; -import {attach} from './fiber/renderer'; +import {attach as attachFiber} from './fiber/renderer'; +import {attach as attachFlight} from './flight/renderer'; import {attach as attachLegacy} from './legacy/renderer'; + import {hasAssignedBackend} from './utils'; import type {DevToolsHook, ReactRenderer, RendererInterface} from './types'; @@ -80,7 +82,10 @@ export function initBackend( renderer.currentDispatcherRef != null ) { // react-reconciler v16+ - rendererInterface = attach(hook, id, renderer, global); + rendererInterface = attachFiber(hook, id, renderer, global); + } else if (typeof renderer.getCurrentComponentInfo === 'function') { + // react-flight/client + rendererInterface = attachFlight(hook, id, renderer, global); } else if (renderer.ComponentTree) { // react-dom v15 rendererInterface = attachLegacy(hook, id, renderer, global); diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 3ceec410803e3..f8261c5c7927e 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -14,7 +14,11 @@ * Be mindful of backwards compatibility when making changes. */ -import type {ReactContext, Wakeable} from 'shared/ReactTypes'; +import type { + ReactContext, + Wakeable, + ReactComponentInfo, +} from 'shared/ReactTypes'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { ComponentFilter, @@ -155,6 +159,9 @@ export type ReactRenderer = { // Only injected by React v16.9+ in DEV mode. // Enables DevTools to append owners-only component stack to error messages. getCurrentFiber?: () => Fiber | null, + // Only injected by React Flight Clients in DEV mode. + // Enables DevTools to append owners-only component stack to error messages from Server Components. + getCurrentComponentInfo?: () => ReactComponentInfo | null, // 17.0.2+ reconcilerVersion?: string, // Uniquely identifies React DOM v15. diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index c16409e3d9279..75f27d7fc013f 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -564,6 +564,9 @@ export function installHook(target: any): DevToolsHook | null { // React v16 checks the hook for this to ensure DevTools is new enough. supportsFiber: true, + // React Flight Client checks the hook for this to ensure DevTools is new enough. + supportsFlight: true, + // React calls these methods. checkDCE, onCommitFiberUnmount, diff --git a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js index 3abdac02e8c57..58a36e7023ca2 100644 --- a/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-esm/src/client/ReactFlightDOMClientBrowser.js @@ -22,6 +22,7 @@ import { reportGlobalError, processBinaryChunk, close, + injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; import { @@ -143,3 +144,7 @@ export { encodeReply, createServerReference, }; + +if (__DEV__) { + injectIntoDevTools(); +} diff --git a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js index 5a01a42417f7c..50a4a206ffb44 100644 --- a/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js @@ -22,6 +22,7 @@ import { reportGlobalError, processBinaryChunk, close, + injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; import { @@ -142,3 +143,7 @@ export { encodeReply, createServerReference, }; + +if (__DEV__) { + injectIntoDevTools(); +} diff --git a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js index 5a01a42417f7c..50a4a206ffb44 100644 --- a/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js +++ b/packages/react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js @@ -22,6 +22,7 @@ import { reportGlobalError, processBinaryChunk, close, + injectIntoDevTools, } from 'react-client/src/ReactFlightClient'; import { @@ -142,3 +143,7 @@ export { encodeReply, createServerReference, }; + +if (__DEV__) { + injectIntoDevTools(); +}