From 0d8e9a9485a0a1b709c991341ca31ca0dbdc6559 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Sun, 1 Nov 2020 08:11:15 +0530 Subject: [PATCH 1/7] Function injected into Devtools --- .../react-devtools-extensions/src/main.js | 7 ++ .../InjectHookVariableNamesFunctionContext.js | 12 +++ .../Components/InspectedElementContext.js | 5 +- .../src/devtools/views/DevTools.js | 87 ++++++++++++------- 4 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index 00e3735c72c11..5a9054fdc8ebc 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -202,6 +202,12 @@ function createPanelIfReactLoaded() { } }; + function injectHookVariableNamesFunction(hookLog, source) { + console.log('----main.js----') + console.log('injectHookVariableNamesFunction called with', hookLog, source) + return hookLog.map((log) => ({...log, name: 'State-alter'})) + } + root = createRoot(document.createElement('div')); render = (overrideTab = mostRecentOverrideTab) => { @@ -220,6 +226,7 @@ function createPanelIfReactLoaded() { warnIfUnsupportedVersionDetected: true, viewAttributeSourceFunction, viewElementSourceFunction, + injectHookVariableNamesFunction }), ); }; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js new file mode 100644 index 0000000000000..ae7398d99614d --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js @@ -0,0 +1,12 @@ +// @flow + +import {createContext} from 'react'; +import type {InjectHookVariableNamesFunction} from '../DevTools'; + +export type Context = {| + injectHookVariableNamesFunction: InjectHookVariableNamesFunction | null, + |}; + +const InjectHookVariableNamesFunctionContext = createContext(((null: any): Context)); +InjectHookVariableNamesFunctionContext.displayName = 'InjectHookVariableNamesFunctionContext'; +export default InjectHookVariableNamesFunctionContext; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index f80f099f52479..a453dc9ec90d3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -22,6 +22,7 @@ import {createResource} from '../../cache'; import {BridgeContext, StoreContext} from '../context'; import {hydrate, fillInPath} from 'react-devtools-shared/src/hydration'; import {TreeStateContext} from './TreeContext'; +import InjectHookVariableNamesFunctionContext from './InjectHookVariableNamesFunctionContext'; import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; import type { @@ -101,7 +102,7 @@ type Props = {| function InspectedElementContextController({children}: Props) { const bridge = useContext(BridgeContext); const store = useContext(StoreContext); - + const injectHookVariableNamesFunction = useContext(InjectHookVariableNamesFunctionContext).injectHookVariableNamesFunction const storeAsGlobalCount = useRef(1); // Ask the backend to store the value at the specified path as a global variable. @@ -254,7 +255,7 @@ function InspectedElementContextController({children}: Props) { }; }), context: hydrateHelper(context), - hooks: hydrateHelper(hooks), + hooks: injectHookVariableNamesFunction ? injectHookVariableNamesFunction(hydrateHelper(hooks), source) :hydrateHelper(hooks), props: hydrateHelper(props), state: hydrateHelper(state), }; diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index cb5c36cf680bc..84a93551d9d36 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -22,6 +22,7 @@ import TabBar from './TabBar'; import {SettingsContextController} from './Settings/SettingsContext'; import {TreeContextController} from './Components/TreeContext'; import ViewElementSourceContext from './Components/ViewElementSourceContext'; +import InjectHookVariableNamesFunctionContext from './Components/InjectHookVariableNamesFunctionContext'; import {ProfilerContextController} from './Profiler/ProfilerContext'; import {ModalDialogContextController} from './ModalDialog'; import ReactLogo from './ReactLogo'; @@ -35,13 +36,24 @@ import './root.css'; import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; +import type {Source} from '../../../../shared/ReactElementType' export type BrowserTheme = 'dark' | 'light'; export type TabID = 'components' | 'profiler'; +type HookLogEntry = { + primitive: string, + stackError: Error, + value: mixed, + ... +}; + +type HookLog = Array | null + export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; +export type InjectHookVariableNamesFunction = (hookLog: HookLog, source: Source | null) => HookLog; export type ViewAttributeSource = ( id: number, path: Array, @@ -62,6 +74,8 @@ export type Props = {| warnIfUnsupportedVersionDetected?: boolean, viewAttributeSourceFunction?: ?ViewAttributeSource, viewElementSourceFunction?: ?ViewElementSource, + // Passing HookVariableNamesFunction as a Prop + injectHookVariableNamesFunction?: ?InjectHookVariableNamesFunction, // This property is used only by the web extension target. // The built-in tab UI is hidden in that case, in favor of the browser's own panel tabs. @@ -106,6 +120,7 @@ export default function DevTools({ warnIfUnsupportedVersionDetected = false, viewAttributeSourceFunction, viewElementSourceFunction, + injectHookVariableNamesFunction }: Props) { const [currentTab, setTab] = useLocalStorage( 'React::DevTools::defaultTab', @@ -118,6 +133,13 @@ export default function DevTools({ tab = overrideTab; } + const injectHookVariableNames = useMemo( + () => ({ + injectHookVariableNamesFunction: injectHookVariableNamesFunction || null + }), + [injectHookVariableNamesFunction] + ) + const viewElementSource = useMemo( () => ({ canViewElementSourceFunction: canViewElementSourceFunction || null, @@ -179,7 +201,6 @@ export default function DevTools({ } }; }, [bridge]); - return ( @@ -190,40 +211,42 @@ export default function DevTools({ componentsPortalContainer={componentsPortalContainer} profilerPortalContainer={profilerPortalContainer}> - - -
- {showTabBar && ( -
- - - {process.env.DEVTOOLS_VERSION} - -
- + + +
+ {showTabBar && ( +
+ + + {process.env.DEVTOOLS_VERSION} + +
+ +
+ )} + - )} - - -
- - + + + {warnIfLegacyBackendDetected && } From 5fbf90fd6782a2f4c816a3a1e93792efbfa85085 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Wed, 4 Nov 2020 09:35:29 +0530 Subject: [PATCH 2/7] Passed hookSource with fileName and lineNumber in Hooks object --- .../react-debug-tools/src/ReactDebugHooks.js | 18 ++++++++++++++++++ packages/react-devtools-extensions/src/main.js | 7 +++---- .../Components/InspectedElementContext.js | 2 +- .../src/devtools/views/DevTools.js | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 39f8f5118c265..4dcb03fa9d903 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -323,6 +323,11 @@ export type HooksNode = { name: string, value: mixed, subHooks: Array, + hookSource: { + lineNumber: number | null, + fileName: string | null, + functionName: string | null, + }, ... }; export type HooksTree = Array; @@ -499,6 +504,11 @@ function buildTree(rootStack, readHookLog): HooksTree { name: parseCustomHookName(stack[j - 1].functionName), value: undefined, subHooks: children, + hookSource: { + lineNumber: stack[j-1].lineNumber, + functionName: stack[j-1].functionName, + fileName: stack[j-1].fileName + } }); stackOfChildren.push(levelChildren); levelChildren = children; @@ -516,6 +526,13 @@ function buildTree(rootStack, readHookLog): HooksTree { // For the time being, only State and Reducer hooks support runtime overrides. const isStateEditable = primitive === 'Reducer' || primitive === 'State'; + const hookSource = {lineNumber: null, functionName: null, fileName: null} + if (stack && stack.length === 1) { + const stackFrame = stack[0] + hookSource.lineNumber = stackFrame.lineNumber + hookSource.functionName = stackFrame.functionName + hookSource.fileName = stackFrame.fileName + } levelChildren.push({ id, @@ -523,6 +540,7 @@ function buildTree(rootStack, readHookLog): HooksTree { name: primitive, value: hook.value, subHooks: [], + hookSource }); } diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index 5a9054fdc8ebc..f56ff0ee94621 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -202,10 +202,9 @@ function createPanelIfReactLoaded() { } }; - function injectHookVariableNamesFunction(hookLog, source) { - console.log('----main.js----') - console.log('injectHookVariableNamesFunction called with', hookLog, source) - return hookLog.map((log) => ({...log, name: 'State-alter'})) + function injectHookVariableNamesFunction(hookLog) { + console.log('injectHookVariableNamesFunction called with', hookLog) + return hookLog } root = createRoot(document.createElement('div')); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index a453dc9ec90d3..da7f65e51b9ad 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -255,7 +255,7 @@ function InspectedElementContextController({children}: Props) { }; }), context: hydrateHelper(context), - hooks: injectHookVariableNamesFunction ? injectHookVariableNamesFunction(hydrateHelper(hooks), source) :hydrateHelper(hooks), + hooks: injectHookVariableNamesFunction ? injectHookVariableNamesFunction(hydrateHelper(hooks)) :hydrateHelper(hooks), props: hydrateHelper(props), state: hydrateHelper(state), }; diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 84a93551d9d36..987b4b48f0934 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -53,7 +53,7 @@ export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; -export type InjectHookVariableNamesFunction = (hookLog: HookLog, source: Source | null) => HookLog; +export type InjectHookVariableNamesFunction = (hookLog: HookLog) => HookLog; export type ViewAttributeSource = ( id: number, path: Array, From 0b358c0039066884dc1f40e531cc211212487984 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Wed, 4 Nov 2020 17:32:52 +0530 Subject: [PATCH 3/7] Added columnNumber to HookSource --- .../react-debug-tools/src/ReactDebugHooks.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 4dcb03fa9d903..aac0d7a8eee45 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -317,17 +317,19 @@ const Dispatcher: DispatcherType = { // Inspect +type HookSource = { + lineNumber: number | null, + columnNumber: number | null, + fileName: string | null, + functionName: string | null, +} export type HooksNode = { id: number | null, isStateEditable: boolean, name: string, value: mixed, subHooks: Array, - hookSource: { - lineNumber: number | null, - fileName: string | null, - functionName: string | null, - }, + hookSource: HookSource, ... }; export type HooksTree = Array; @@ -506,6 +508,7 @@ function buildTree(rootStack, readHookLog): HooksTree { subHooks: children, hookSource: { lineNumber: stack[j-1].lineNumber, + columnNumber: stack[j-1].columnNumber, functionName: stack[j-1].functionName, fileName: stack[j-1].fileName } @@ -526,12 +529,13 @@ function buildTree(rootStack, readHookLog): HooksTree { // For the time being, only State and Reducer hooks support runtime overrides. const isStateEditable = primitive === 'Reducer' || primitive === 'State'; - const hookSource = {lineNumber: null, functionName: null, fileName: null} + const hookSource: HookSource = {lineNumber: null, functionName: null, fileName: null, columnNumber: null} if (stack && stack.length === 1) { const stackFrame = stack[0] hookSource.lineNumber = stackFrame.lineNumber hookSource.functionName = stackFrame.functionName hookSource.fileName = stackFrame.fileName + hookSource.columnNumber = stackFrame.columnNumber } levelChildren.push({ From 551508feeba6f7c1eab279b9e44b6f1d8ed6ad78 Mon Sep 17 00:00:00 2001 From: VibhorCodecianGupta Date: Wed, 4 Nov 2020 03:26:27 +0530 Subject: [PATCH 4/7] promisify hook name injection in inspected hook object --- .../views/Components/InspectedElementContext.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index da7f65e51b9ad..c439866303aa2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -255,11 +255,20 @@ function InspectedElementContextController({children}: Props) { }; }), context: hydrateHelper(context), - hooks: injectHookVariableNamesFunction ? injectHookVariableNamesFunction(hydrateHelper(hooks)) :hydrateHelper(hooks), + hooks: hydrateHelper(hooks), props: hydrateHelper(props), state: hydrateHelper(state), }; + // If injectHookVariableNamesFunction prop present, wait on new hook (with variable names) to resolve + // and replace old hooks structure with the new one + if (injectHookVariableNamesFunction) { + const namedHooksPromise = injectHookVariableNamesFunction(hydrateHelper(hooks), source) + namedHooksPromise.then(namedHooks => { + inspectedElement.hooks = namedHooks + }) + } + element = store.getElementByID(id); if (element !== null) { const request = inProgressRequests.get(element); From 12495eae928c291034aa7f35073732bac71e4478 Mon Sep 17 00:00:00 2001 From: Saphal Patro Date: Wed, 4 Nov 2020 09:43:41 +0530 Subject: [PATCH 5/7] Removed SourceofHook interface --- packages/react-devtools-extensions/src/main.js | 12 ++++++++++-- .../views/Components/InspectedElementContext.js | 2 +- .../src/devtools/views/DevTools.js | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index f56ff0ee94621..93b77d8ec7d03 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -203,8 +203,16 @@ function createPanelIfReactLoaded() { }; function injectHookVariableNamesFunction(hookLog) { - console.log('injectHookVariableNamesFunction called with', hookLog) - return hookLog + const newHookLog = new Promise((resolve, reject) => { + setTimeout(() => { + const newHook = hookLog.map((hook) => { + return {...hook, 'variableName':'parsed-variable-name'} + }) + resolve(newHook) + }, 8000) + }) + console.log('injectHookVariableNamesFunction called with', newHookLog, hookLog) + return newHookLog } root = createRoot(document.createElement('div')); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index c439866303aa2..fe77888c20058 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -263,7 +263,7 @@ function InspectedElementContextController({children}: Props) { // If injectHookVariableNamesFunction prop present, wait on new hook (with variable names) to resolve // and replace old hooks structure with the new one if (injectHookVariableNamesFunction) { - const namedHooksPromise = injectHookVariableNamesFunction(hydrateHelper(hooks), source) + const namedHooksPromise = injectHookVariableNamesFunction(hydrateHelper(hooks)) namedHooksPromise.then(namedHooks => { inspectedElement.hooks = namedHooks }) diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 987b4b48f0934..140f096a2207e 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -53,7 +53,7 @@ export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; -export type InjectHookVariableNamesFunction = (hookLog: HookLog) => HookLog; +export type InjectHookVariableNamesFunction = (hookLog: HookLog) => Promise; export type ViewAttributeSource = ( id: number, path: Array, From 73d84b93a053d1f5626baa507b3fd61e9f276eb5 Mon Sep 17 00:00:00 2001 From: VibhorCodecianGupta Date: Tue, 10 Nov 2020 02:25:37 +0530 Subject: [PATCH 6/7] WIP: read/write hook names with suspense resource --- .../react-devtools-extensions/src/main.js | 13 ++++--- .../Components/InspectedElementContext.js | 35 +++++++++++++++---- .../src/devtools/views/DevTools.js | 6 ++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index 93b77d8ec7d03..96342a2995484 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -203,16 +203,15 @@ function createPanelIfReactLoaded() { }; function injectHookVariableNamesFunction(hookLog) { - const newHookLog = new Promise((resolve, reject) => { + const namedHookLogPromise = new Promise((resolve, reject) => { setTimeout(() => { - const newHook = hookLog.map((hook) => { - return {...hook, 'variableName':'parsed-variable-name'} + const newHookLog = hookLog.map((hook) => { + return {...hook, 'name':'hook-variable-name'} }) - resolve(newHook) - }, 8000) + resolve(newHookLog) + }, 2000) }) - console.log('injectHookVariableNamesFunction called with', newHookLog, hookLog) - return newHookLog + return namedHookLogPromise; } root = createRoot(document.createElement('div')); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index fe77888c20058..011b82651e072 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -34,6 +34,7 @@ import type { Element, InspectedElement as InspectedElementFrontend, } from 'react-devtools-shared/src/devtools/views/Components/types'; +import type {HookLog} from 'react-devtools-shared/src/devtools/views/DevTools'; import type {Resource, Thenable} from '../../cache'; export type StoreAsGlobal = (id: number, path: Array) => void; @@ -95,6 +96,21 @@ const resource: Resource< {useWeakMap: true}, ); +const hookResource: Resource< + InspectedElementFrontend, + InspectedElementFrontend, + Thenable, +> = createResource( + (inspectedElement: InspectedElementFrontend) => { + const promise = new Promise(resolve => { + resolve(inspectedElement.hooks); + }); + return promise; + }, + (inspectedElement: InspectedElementFrontend) => inspectedElement, + {useWeakMap: true}, +); + type Props = {| children: React$Node, |}; @@ -147,7 +163,14 @@ function InspectedElementContextController({children}: Props) { (id: number) => { const element = store.getElementByID(id); if (element !== null) { - return resource.read(element); + const inspectedElement = resource.read(element); + + // Read new hook object and set in inspected element + const namedHooks = hookResource.read(inspectedElement); + inspectedElement.hooks = namedHooks; + console.log('=== Inspected ===', inspectedElement); + + return inspectedElement; } else { return null; } @@ -260,13 +283,11 @@ function InspectedElementContextController({children}: Props) { state: hydrateHelper(state), }; - // If injectHookVariableNamesFunction prop present, wait on new hook (with variable names) to resolve - // and replace old hooks structure with the new one if (injectHookVariableNamesFunction) { - const namedHooksPromise = injectHookVariableNamesFunction(hydrateHelper(hooks)) - namedHooksPromise.then(namedHooks => { - inspectedElement.hooks = namedHooks - }) + const namedHooksPromise = injectHookVariableNamesFunction( + hydrateHelper(hooks) + ); + hookResource.write(inspectedElement, namedHooksPromise); } element = store.getElementByID(id); diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 140f096a2207e..4de4c068bd98f 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -37,6 +37,7 @@ import './root.css'; import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; import type {Source} from '../../../../shared/ReactElementType' +import type {Thenable} from '../cache' export type BrowserTheme = 'dark' | 'light'; export type TabID = 'components' | 'profiler'; @@ -46,14 +47,13 @@ type HookLogEntry = { value: mixed, ... }; - -type HookLog = Array | null +export type HookLog = Array | null; export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; -export type InjectHookVariableNamesFunction = (hookLog: HookLog) => Promise; +export type InjectHookVariableNamesFunction = (hookLog: HookLog) => Thenable; export type ViewAttributeSource = ( id: number, path: Array, From 4518c29d1a045e2e9f65886f76f0b19d6f01358b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 10 Nov 2020 10:18:39 -0500 Subject: [PATCH 7/7] Filled in some Suspense/cache scaffolding --- .../react-devtools-extensions/src/main.js | 17 ++--- .../InjectHookVariableNamesFunctionContext.js | 11 +-- .../views/Components/InspectedElement.js | 1 + .../Components/InspectedElementContext.js | 67 +++++++++++-------- .../src/devtools/views/DevTools.js | 19 +++--- 5 files changed, 68 insertions(+), 47 deletions(-) diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index 96342a2995484..1e5f53213057a 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -202,15 +202,16 @@ function createPanelIfReactLoaded() { } }; - function injectHookVariableNamesFunction(hookLog) { + function injectHookVariableNamesFunction(id, hookLog) { + // TODO Load source and source map, parse AST, mix in real names. const namedHookLogPromise = new Promise((resolve, reject) => { setTimeout(() => { - const newHookLog = hookLog.map((hook) => { - return {...hook, 'name':'hook-variable-name'} - }) - resolve(newHookLog) - }, 2000) - }) + const newHookLog = hookLog.map(hook => { + return {...hook, name: 'hook-variable-name'}; + }); + resolve(newHookLog); + }, 2000); + }); return namedHookLogPromise; } @@ -232,7 +233,7 @@ function createPanelIfReactLoaded() { warnIfUnsupportedVersionDetected: true, viewAttributeSourceFunction, viewElementSourceFunction, - injectHookVariableNamesFunction + injectHookVariableNamesFunction, }), ); }; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js index ae7398d99614d..d35e2982b77e3 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InjectHookVariableNamesFunctionContext.js @@ -4,9 +4,12 @@ import {createContext} from 'react'; import type {InjectHookVariableNamesFunction} from '../DevTools'; export type Context = {| - injectHookVariableNamesFunction: InjectHookVariableNamesFunction | null, - |}; + injectHookVariableNamesFunction: InjectHookVariableNamesFunction | null, +|}; -const InjectHookVariableNamesFunctionContext = createContext(((null: any): Context)); -InjectHookVariableNamesFunctionContext.displayName = 'InjectHookVariableNamesFunctionContext'; +const InjectHookVariableNamesFunctionContext = createContext( + ((null: any): Context), +); +InjectHookVariableNamesFunctionContext.displayName = + 'InjectHookVariableNamesFunctionContext'; export default InjectHookVariableNamesFunctionContext; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index f0ecc32be3770..446b9dca5929d 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -52,6 +52,7 @@ export default function InspectedElementWrapper(_: Props) { const inspectedElement = inspectedElementID != null ? getInspectedElement(inspectedElementID) : null; + console.log(' inspectedElement:', inspectedElement); const highlightElement = useCallback(() => { if (element !== null && inspectedElementID !== null) { diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index 011b82651e072..baf01ead94fd1 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -34,7 +34,10 @@ import type { Element, InspectedElement as InspectedElementFrontend, } from 'react-devtools-shared/src/devtools/views/Components/types'; -import type {HookLog} from 'react-devtools-shared/src/devtools/views/DevTools'; +import type { + InjectHookVariableNamesFunction, + HookLog, +} from 'react-devtools-shared/src/devtools/views/DevTools'; import type {Resource, Thenable} from '../../cache'; export type StoreAsGlobal = (id: number, path: Array) => void; @@ -72,7 +75,7 @@ type InProgressRequest = {| |}; const inProgressRequests: WeakMap = new WeakMap(); -const resource: Resource< +const inspectedElementResource: Resource< Element, Element, InspectedElementFrontend, @@ -96,18 +99,25 @@ const resource: Resource< {useWeakMap: true}, ); -const hookResource: Resource< - InspectedElementFrontend, +type NamedHooksResourceKey = [ + Element, InspectedElementFrontend, - Thenable, + InjectHookVariableNamesFunction, +]; + +const namedHooksResource: Resource< + NamedHooksResourceKey, + Element, + HookLog, > = createResource( - (inspectedElement: InspectedElementFrontend) => { - const promise = new Promise(resolve => { - resolve(inspectedElement.hooks); - }); - return promise; + (key: NamedHooksResourceKey) => { + // eslint-disable-next-line no-unused-vars + const [element, inspectedElement, injectHookVariableNamesFunction] = key; + const {id, hooks} = inspectedElement; + return injectHookVariableNamesFunction(id, hooks); }, - (inspectedElement: InspectedElementFrontend) => inspectedElement, + // Key the WeakMap on the Element (in the Store) since it's stable. + (key: NamedHooksResourceKey) => key[0], {useWeakMap: true}, ); @@ -118,7 +128,9 @@ type Props = {| function InspectedElementContextController({children}: Props) { const bridge = useContext(BridgeContext); const store = useContext(StoreContext); - const injectHookVariableNamesFunction = useContext(InjectHookVariableNamesFunctionContext).injectHookVariableNamesFunction + const {injectHookVariableNamesFunction} = useContext( + InjectHookVariableNamesFunctionContext, + ); const storeAsGlobalCount = useRef(1); // Ask the backend to store the value at the specified path as a global variable. @@ -163,12 +175,20 @@ function InspectedElementContextController({children}: Props) { (id: number) => { const element = store.getElementByID(id); if (element !== null) { - const inspectedElement = resource.read(element); - - // Read new hook object and set in inspected element - const namedHooks = hookResource.read(inspectedElement); - inspectedElement.hooks = namedHooks; - console.log('=== Inspected ===', inspectedElement); + const inspectedElement = inspectedElementResource.read(element); + + // Mix additional hook name data into the inspected resource if we can. + if ( + inspectedElement.hooks !== null && + injectHookVariableNamesFunction !== null + ) { + inspectedElement.hooks = namedHooksResource.read([ + element, + inspectedElement, + injectHookVariableNamesFunction, + ]); + inspectedElementResource.write(element, inspectedElement); + } return inspectedElement; } else { @@ -210,7 +230,7 @@ function InspectedElementContextController({children}: Props) { fillInPath(inspectedElement, data.value, data.path, value); - resource.write(element, inspectedElement); + inspectedElementResource.write(element, inspectedElement); // Schedule update with React if the currently-selected element has been invalidated. if (id === selectedElementID) { @@ -283,13 +303,6 @@ function InspectedElementContextController({children}: Props) { state: hydrateHelper(state), }; - if (injectHookVariableNamesFunction) { - const namedHooksPromise = injectHookVariableNamesFunction( - hydrateHelper(hooks) - ); - hookResource.write(inspectedElement, namedHooksPromise); - } - element = store.getElementByID(id); if (element !== null) { const request = inProgressRequests.get(element); @@ -300,7 +313,7 @@ function InspectedElementContextController({children}: Props) { setCurrentlyInspectedElement(inspectedElement); }); } else { - resource.write(element, inspectedElement); + inspectedElementResource.write(element, inspectedElement); // Schedule update with React if the currently-selected element has been invalidated. if (id === selectedElementID) { diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 4de4c068bd98f..64f140c232593 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -36,8 +36,7 @@ import './root.css'; import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; -import type {Source} from '../../../../shared/ReactElementType' -import type {Thenable} from '../cache' +import type {Thenable} from '../cache'; export type BrowserTheme = 'dark' | 'light'; export type TabID = 'components' | 'profiler'; @@ -53,7 +52,10 @@ export type ViewElementSource = ( id: number, inspectedElement: InspectedElement, ) => void; -export type InjectHookVariableNamesFunction = (hookLog: HookLog) => Thenable; +export type InjectHookVariableNamesFunction = ( + id: number, + hookLog: HookLog, +) => Thenable; export type ViewAttributeSource = ( id: number, path: Array, @@ -120,7 +122,7 @@ export default function DevTools({ warnIfUnsupportedVersionDetected = false, viewAttributeSourceFunction, viewElementSourceFunction, - injectHookVariableNamesFunction + injectHookVariableNamesFunction, }: Props) { const [currentTab, setTab] = useLocalStorage( 'React::DevTools::defaultTab', @@ -135,10 +137,10 @@ export default function DevTools({ const injectHookVariableNames = useMemo( () => ({ - injectHookVariableNamesFunction: injectHookVariableNamesFunction || null + injectHookVariableNamesFunction: injectHookVariableNamesFunction || null, }), - [injectHookVariableNamesFunction] - ) + [injectHookVariableNamesFunction], + ); const viewElementSource = useMemo( () => ({ @@ -211,7 +213,8 @@ export default function DevTools({ componentsPortalContainer={componentsPortalContainer} profilerPortalContainer={profilerPortalContainer}> - +