diff --git a/package-lock.json b/package-lock.json index 3c97fb90eab..52c08246788 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,12 +50,13 @@ "parse-script-tags": "^0.1.7", "prop-types": "^15.8.1", "properties-parser": "^0.3.1", - "react": "^0.0.0-experimental-edfe50510-20210823", + "react": "^0.0.0-experimental-e7d0053e6-20220325", "react-canvas-confetti": "^1.3.0", "react-circular-progressbar": "^2.0.4", "react-codemirror2": "^7.2.1", - "react-devtools-inline": "^4.17.0", - "react-dom": "^0.0.0-experimental-edfe50510-20210823", + "react-devtools-inline": "^4.24.3", + "react-devtools-inline_4_17_0": "npm:react-devtools-inline@4.17.0", + "react-dom": "^0.0.0-experimental-e7d0053e6-20220325", "react-dom-factories": "^1.0.2", "react-json-view": "^1.21.3", "react-lazyload": "^3.2.0", @@ -38002,7 +38003,7 @@ } }, "node_modules/react": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -38059,9 +38060,15 @@ } }, "node_modules/react-devtools-inline": { - "version": "4.17.0", + "version": "4.24.3", "license": "MIT" }, + "node_modules/react-devtools-inline_4_17_0": { + "name": "react-devtools-inline", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.17.0.tgz", + "integrity": "sha512-Ffj+Q4gRFinS3RnvMdai6oZYh5PxCaaheu7L9rv5Xo3jh4y+Khg0Y16/cpW/65tBWnHfOwENVx456qms6CEVMg==" + }, "node_modules/react-docgen": { "version": "5.4.0", "dev": true, @@ -38099,15 +38106,15 @@ "license": "MIT" }, "node_modules/react-dom": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "scheduler": "0.0.0-experimental-edfe50510-20210823" + "scheduler": "0.0.0-experimental-e7d0053e6-20220325" }, "peerDependencies": { - "react": "0.0.0-experimental-edfe50510-20210823" + "react": "0.0.0-experimental-e7d0053e6-20220325" } }, "node_modules/react-dom-factories": { @@ -39449,7 +39456,7 @@ } }, "node_modules/scheduler": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -70141,7 +70148,7 @@ } }, "react": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -70179,7 +70186,12 @@ "requires": {} }, "react-devtools-inline": { - "version": "4.17.0" + "version": "4.24.3" + }, + "react-devtools-inline_4_17_0": { + "version": "npm:react-devtools-inline@4.17.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.17.0.tgz", + "integrity": "sha512-Ffj+Q4gRFinS3RnvMdai6oZYh5PxCaaheu7L9rv5Xo3jh4y+Khg0Y16/cpW/65tBWnHfOwENVx456qms6CEVMg==" }, "react-docgen": { "version": "5.4.0", @@ -70209,11 +70221,11 @@ "requires": {} }, "react-dom": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "scheduler": "0.0.0-experimental-edfe50510-20210823" + "scheduler": "0.0.0-experimental-e7d0053e6-20220325" } }, "react-dom-factories": { @@ -71136,7 +71148,7 @@ } }, "scheduler": { - "version": "0.0.0-experimental-edfe50510-20210823", + "version": "0.0.0-experimental-e7d0053e6-20220325", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index 2de78a6a015..42eb95c7bc1 100644 --- a/package.json +++ b/package.json @@ -69,12 +69,13 @@ "parse-script-tags": "^0.1.7", "prop-types": "^15.8.1", "properties-parser": "^0.3.1", - "react": "^0.0.0-experimental-edfe50510-20210823", + "react": "^0.0.0-experimental-e7d0053e6-20220325", "react-canvas-confetti": "^1.3.0", "react-circular-progressbar": "^2.0.4", "react-codemirror2": "^7.2.1", - "react-devtools-inline": "^4.17.0", - "react-dom": "^0.0.0-experimental-edfe50510-20210823", + "react-devtools-inline_4_17_0": "npm:react-devtools-inline@4.17.0", + "react-devtools-inline": "^4.24.3", + "react-dom": "^0.0.0-experimental-e7d0053e6-20220325", "react-dom-factories": "^1.0.2", "react-json-view": "^1.21.3", "react-lazyload": "^3.2.0", diff --git a/src/ui/components/SecondaryToolbox/ReactDevTools.tsx b/src/ui/components/SecondaryToolbox/ReactDevTools.tsx index 37de009f317..d352eaad8ab 100644 --- a/src/ui/components/SecondaryToolbox/ReactDevTools.tsx +++ b/src/ui/components/SecondaryToolbox/ReactDevTools.tsx @@ -1,6 +1,6 @@ import React from "react"; +import { useEffect, useState } from "react"; import { connect, ConnectedProps, useSelector } from "react-redux"; -import { createBridge, createStore, initialize, Store, Wall } from "react-devtools-inline/frontend"; import { ExecutionPoint, ObjectId } from "@recordreplay/protocol"; import { ThreadFront } from "protocol/thread"; import { compareNumericStrings } from "protocol/utils"; @@ -18,6 +18,10 @@ import Highlighter from "highlighter/highlighter"; import NodePicker, { NodePickerOpts } from "ui/utils/nodePicker"; import { sendTelemetryEvent, trackEvent } from "ui/utils/telemetry"; +import type { Store, Wall } from "react-devtools-inline/frontend"; + +type ReactDevToolsInlineModule = typeof import("react-devtools-inline/frontend"); + const getDOMNodes = `((rendererID, id) => __REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces.get(rendererID).findNativeNodesForFiberID(id))`; // used by the frontend to communicate with the backend @@ -207,12 +211,15 @@ class ReplayWall implements Wall { } function createReactDevTools( + reactDevToolsInlineModule: ReactDevToolsInlineModule, annotations: Annotation[], currentPoint: ExecutionPoint, enablePicker: (opts: NodePickerOpts) => void, disablePicker: () => void, onShutdown: () => void ) { + const { createBridge, createStore, initialize } = reactDevToolsInlineModule; + const target = { postMessage() {} }; const wall = new ReplayWall(enablePicker, disablePicker, onShutdown); const bridge = createBridge(target, wall); @@ -229,6 +236,28 @@ function createReactDevTools( return ReactDevTools; } +// React DevTools (RD) changed its internal data structure slightly in a minor update. +// The result is that Replay sessions recorded with older versions of RD don't play well in newer versions. +// We can work around this by checking RD's "bridge protocol" version (which we also store) +// and loading the appropriate frontend version to match. +// For more information see https://github.com/facebook/react/issues/24219 +async function loadReactDevToolsInlineModuleFromProtocol(stateUpdaterCallback: Function) { + const response = await ThreadFront.evaluate({ + asyncIndex: 0, + text: ` __RECORD_REPLAY_REACT_DEVTOOLS_SEND_MESSAGE__("getBridgeProtocol", undefined)`, + }); + const json: any = await response?.returned?.getJSON(); + const version = json?.data?.version; + + // We should only load the DevTools module once we know which protocol version it requires. + // If we don't have a version yet, it probably means we're too early in the Replay session. + if (version >= 2) { + stateUpdaterCallback(await import("react-devtools-inline/frontend")); + } else if (version === 1) { + stateUpdaterCallback(await import("react-devtools-inline_4_17_0/frontend")); + } +} + function ReactDevtoolsPanel({ annotations, currentPoint, @@ -239,6 +268,19 @@ function ReactDevtoolsPanel({ }: PropsFromRedux) { const theme = useSelector(getTheme); + // Once we've obtained the protocol version, we'll dynamically load the correct module/version. + const [reactDevToolsInlineModule, setReactDevToolsInlineModule] = + useState(null); + + // Try to load the DevTools module whenever the current point changes. + // Eventually we'll reach a point that has the DevTools protocol embedded. + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => { + if (reactDevToolsInlineModule === null) { + loadReactDevToolsInlineModuleFromProtocol(setReactDevToolsInlineModule); + } + }); + if (currentPoint === null) { return null; } @@ -270,6 +312,7 @@ function ReactDevtoolsPanel({ } const isReady = + reactDevToolsInlineModule !== null && reactInitPoint !== null && currentPoint !== null && compareNumericStrings(reactInitPoint, currentPoint) <= 0; @@ -284,6 +327,7 @@ function ReactDevtoolsPanel({ } const ReactDevTools = createReactDevTools( + reactDevToolsInlineModule, annotations, currentPoint, enablePicker, diff --git a/src/ui/components/SecondaryToolbox/react-devtools-inline.d.ts b/src/ui/components/SecondaryToolbox/react-devtools-inline.d.ts index e4b69567da1..0183111566c 100644 --- a/src/ui/components/SecondaryToolbox/react-devtools-inline.d.ts +++ b/src/ui/components/SecondaryToolbox/react-devtools-inline.d.ts @@ -187,3 +187,7 @@ https: declare module "react-devtools-inline/frontend" { bridgeAndStore: { bridge?: FrontentBridge; store?: Store } ): typeof ReactDevTools; } + +https: declare module "react-devtools-inline_4_17_0/frontend" { + export * from "@types/react-devtools-inline/frontend"; +}