diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js index 7f70586bd6f71..891e4f3e73834 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js @@ -2765,6 +2765,7 @@ describe('InspectedElement', () => { expect(inspectedElement.owners).toMatchInlineSnapshot(` [ { + "compiledWithForget": false, "displayName": "Child", "hocDisplayNames": null, "id": 3, @@ -2772,6 +2773,7 @@ describe('InspectedElement', () => { "type": 5, }, { + "compiledWithForget": false, "displayName": "App", "hocDisplayNames": null, "id": 2, diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index 80fcf1c5fb505..aefc8d5e078bc 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -968,6 +968,7 @@ describe('ProfilingCache', () => { "timestamp": 0, "updaters": [ { + "compiledWithForget": false, "displayName": "render()", "hocDisplayNames": null, "id": 1, @@ -1010,6 +1011,7 @@ describe('ProfilingCache', () => { "timestamp": 0, "updaters": [ { + "compiledWithForget": false, "displayName": "render()", "hocDisplayNames": null, "id": 1, diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 79260e9870e81..a097f850072dd 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -424,7 +424,10 @@ export function getInternalReactConstants(version: string): { } // NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods - function getDisplayNameForFiber(fiber: Fiber): string | null { + function getDisplayNameForFiber( + fiber: Fiber, + shouldSkipForgetCheck: boolean = false, + ): string | null { const {elementType, type, tag} = fiber; let resolvedType = type; @@ -433,6 +436,18 @@ export function getInternalReactConstants(version: string): { } let resolvedContext: any = null; + // $FlowFixMe[incompatible-type] fiber.updateQueue is mixed + if (!shouldSkipForgetCheck && fiber.updateQueue?.memoCache != null) { + const displayNameWithoutForgetWrapper = getDisplayNameForFiber( + fiber, + true, + ); + if (displayNameWithoutForgetWrapper == null) { + return null; + } + + return `Forget(${displayNameWithoutForgetWrapper})`; + } switch (tag) { case CacheComponent: diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index 27700c27c71cd..397019bb373c6 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -8,7 +8,7 @@ */ import {hydrate, fillInPath} from 'react-devtools-shared/src/hydration'; -import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; +import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils'; import Store from 'react-devtools-shared/src/devtools/store'; import TimeoutError from 'react-devtools-shared/src/errors/TimeoutError'; import ElementPollingCancellationError from 'react-devtools-shared/src/errors/ElementPollingCancellationError'; @@ -266,17 +266,7 @@ export function convertInspectedElementBackendToFrontend( owners: owners === null ? null - : owners.map(owner => { - const [displayName, hocDisplayNames] = separateDisplayNameAndHOCs( - owner.displayName, - owner.type, - ); - return { - ...owner, - displayName, - hocDisplayNames, - }; - }), + : owners.map(backendToFrontendSerializedElementMapper), context: hydrateHelper(context), hooks: hydrateHelper(hooks), props: hydrateHelper(props), diff --git a/packages/react-devtools-shared/src/devtools/constants.js b/packages/react-devtools-shared/src/devtools/constants.js index 6783eadbbd3a8..bc5323c6389de 100644 --- a/packages/react-devtools-shared/src/devtools/constants.js +++ b/packages/react-devtools-shared/src/devtools/constants.js @@ -77,6 +77,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = { '--color-error-border': 'hsl(0, 100%, 92%)', '--color-error-text': '#ff0000', '--color-expand-collapse-toggle': '#777d88', + '--color-forget-badge': '#2683E2', '--color-link': '#0000ff', '--color-modal-background': 'rgba(255, 255, 255, 0.75)', '--color-bridge-version-npm-background': '#eff0f1', @@ -221,6 +222,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = { '--color-error-border': '#900', '--color-error-text': '#f55', '--color-expand-collapse-toggle': '#8f949d', + '--color-forget-badge': '#2683E2', '--color-link': '#61dafb', '--color-modal-background': 'rgba(0, 0, 0, 0.75)', '--color-bridge-version-npm-background': 'rgba(0, 0, 0, 0.25)', diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 5ae3d3cdc4fd2..1d0632072d92e 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -25,9 +25,9 @@ import {ElementTypeRoot} from '../frontend/types'; import { getSavedComponentFilters, setSavedComponentFilters, - separateDisplayNameAndHOCs, shallowDiffers, utfDecodeStringWithRanges, + parseElementDisplayNameFromBackend, } from '../utils'; import {localStorageGetItem, localStorageSetItem} from '../storage'; import {__DEBUG__} from '../constants'; @@ -1033,6 +1033,7 @@ export default class Store extends EventEmitter<{ parentID: 0, type, weight: 0, + compiledWithForget: false, }); haveRootsChanged = true; @@ -1071,8 +1072,11 @@ export default class Store extends EventEmitter<{ parentElement.children.push(id); - const [displayNameWithoutHOCs, hocDisplayNames] = - separateDisplayNameAndHOCs(displayName, type); + const { + formattedDisplayName: displayNameWithoutHOCs, + hocDisplayNames, + compiledWithForget, + } = parseElementDisplayNameFromBackend(displayName, type); const element: Element = { children: [], @@ -1087,6 +1091,7 @@ export default class Store extends EventEmitter<{ parentID, type, weight: 1, + compiledWithForget, }; this._idToElement.set(id, element); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Badge.css b/packages/react-devtools-shared/src/devtools/views/Components/Badge.css index 22f749cc5b1a1..370409aeb9afe 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Badge.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/Badge.css @@ -9,9 +9,3 @@ font-family: var(--font-family-monospace); font-size: var(--font-size-monospace-small); } - -.ExtraLabel { - font-family: var(--font-family-monospace); - font-size: var(--font-size-monospace-small); - color: var(--color-component-badge-count); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Badge.js b/packages/react-devtools-shared/src/devtools/views/Components/Badge.js index b58818e39a3ad..dc18b631ec31a 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Badge.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Badge.js @@ -8,36 +8,14 @@ */ import * as React from 'react'; -import {Fragment} from 'react'; -import styles from './Badge.css'; -import type {ElementType} from 'react-devtools-shared/src/frontend/types'; +import styles from './Badge.css'; type Props = { className?: string, - hocDisplayNames: Array | null, - type: ElementType, children: React$Node, }; -export default function Badge({ - className, - hocDisplayNames, - type, - children, -}: Props): React.Node { - if (hocDisplayNames === null || hocDisplayNames.length === 0) { - return null; - } - - const totalBadgeCount = hocDisplayNames.length; - - return ( - -
{children}
- {totalBadgeCount > 1 && ( -
+{totalBadgeCount - 1}
- )} -
- ); +export default function Badge({className = '', children}: Props): React.Node { + return
{children}
; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.css b/packages/react-devtools-shared/src/devtools/views/Components/Element.css index 7d89010d72ece..ff53776a17597 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Element.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.css @@ -65,7 +65,7 @@ color: var(--color-expand-collapse-toggle); } -.Badge { +.BadgesBlock { margin-left: 0.25rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Element.js b/packages/react-devtools-shared/src/devtools/views/Components/Element.js index a48ebf76650f6..f6a68b652ca5c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Element.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Element.js @@ -10,14 +10,14 @@ import * as React from 'react'; import {Fragment, useContext, useMemo, useState} from 'react'; import Store from 'react-devtools-shared/src/devtools/store'; -import Badge from './Badge'; import ButtonIcon from '../ButtonIcon'; -import {createRegExp} from '../utils'; import {TreeDispatcherContext, TreeStateContext} from './TreeContext'; import {SettingsContext} from '../Settings/SettingsContext'; import {StoreContext} from '../context'; import {useSubscription} from '../hooks'; import {logEvent} from 'react-devtools-shared/src/Logger'; +import IndexableElementBadges from './IndexableElementBadges'; +import IndexableDisplayName from './IndexableDisplayName'; import type {ItemData} from './Tree'; import type {Element as ElementType} from 'react-devtools-shared/src/frontend/types'; @@ -121,7 +121,7 @@ export default function Element({data, index, style}: Props): React.Node { hocDisplayNames, isStrictModeNonCompliant, key, - type, + compiledWithForget, } = element; // Only show strict mode non-compliance badges for top level elements. @@ -155,11 +155,11 @@ export default function Element({data, index, style}: Props): React.Node { // We must use padding rather than margin/left because of the selected background color. transform: `translateX(calc(${depth} * var(--indentation-size)))`, }}> - {ownerID === null ? ( + {ownerID === null && ( - ) : null} + )} - + {key && ( @@ -174,14 +174,12 @@ export default function Element({data, index, style}: Props): React.Node { )} - {hocDisplayNames !== null && hocDisplayNames.length > 0 ? ( - - - - ) : null} + {showInlineWarningsAndErrors && errorCount > 0 && ( ); } - -type DisplayNameProps = { - displayName: string | null, - id: number, -}; - -function DisplayName({displayName, id}: DisplayNameProps) { - const {searchIndex, searchResults, searchText} = useContext(TreeStateContext); - const isSearchResult = useMemo(() => { - return searchResults.includes(id); - }, [id, searchResults]); - const isCurrentResult = - searchIndex !== null && id === searchResults[searchIndex]; - - if (!isSearchResult || displayName === null) { - return displayName; - } - - const match = createRegExp(searchText).exec(displayName); - - if (match === null) { - return displayName; - } - - const startIndex = match.index; - const stopIndex = startIndex + match[0].length; - - const children = []; - if (startIndex > 0) { - children.push({displayName.slice(0, startIndex)}); - } - children.push( - - {displayName.slice(startIndex, stopIndex)} - , - ); - if (stopIndex < displayName.length) { - children.push({displayName.slice(stopIndex)}); - } - - return children; -} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.css b/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.css new file mode 100644 index 0000000000000..058ee241866d7 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.css @@ -0,0 +1,14 @@ +.Root { + display: inline-flex; + align-items: center; +} + +.Root *:not(:first-child) { + margin-left: 0.25rem; +} + +.ExtraLabel { + font-family: var(--font-family-monospace); + font-size: var(--font-size-monospace-small); + color: var(--color-component-badge-count); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.js b/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.js new file mode 100644 index 0000000000000..a829ad0153aa7 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/ElementBadges.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as React from 'react'; + +import Badge from './Badge'; +import ForgetBadge from './ForgetBadge'; + +import styles from './ElementBadges.css'; + +type Props = { + hocDisplayNames: Array | null, + compiledWithForget: boolean, + className?: string, +}; + +export default function ElementBadges({ + compiledWithForget, + hocDisplayNames, + className = '', +}: Props): React.Node { + if ( + !compiledWithForget && + (hocDisplayNames == null || hocDisplayNames.length === 0) + ) { + return null; + } + + return ( +
+ {compiledWithForget && } + + {hocDisplayNames != null && hocDisplayNames.length > 0 && ( + {hocDisplayNames[0]} + )} + + {hocDisplayNames != null && hocDisplayNames.length > 1 && ( +
+{hocDisplayNames.length - 1}
+ )} +
+ ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.css b/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.css new file mode 100644 index 0000000000000..653a073deaeb3 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.css @@ -0,0 +1,3 @@ +.Root { + background-color: var(--color-forget-badge); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.js b/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.js new file mode 100644 index 0000000000000..c31fd70eade2f --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/ForgetBadge.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 + */ + +import * as React from 'react'; + +import Badge from './Badge'; +import IndexableDisplayName from './IndexableDisplayName'; + +import styles from './ForgetBadge.css'; + +type CommonProps = { + className?: string, +}; + +type PropsForIndexable = CommonProps & { + indexable: true, + elementID: number, +}; + +type PropsForNonIndexable = CommonProps & { + indexable: false | void, + elementID?: number, +}; + +type Props = PropsForIndexable | PropsForNonIndexable; + +export default function ForgetBadge(props: Props): React.Node { + const {className = ''} = props; + + const innerView = props.indexable ? ( + + ) : ( + 'Forget' + ); + + return {innerView}; +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.css b/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.css deleted file mode 100644 index 4b37be0325508..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.css +++ /dev/null @@ -1,16 +0,0 @@ -.HocBadges { - padding: 0.125rem 0.25rem; - user-select: none; -} - -.Badge { - display: inline-block; - background-color: var(--color-component-badge-background); - color: var(--color-text); - padding: 0.125rem 0.25rem; - line-height: normal; - border-radius: 0.125rem; - margin-right: 0.25rem; - font-family: var(--font-family-monospace); - font-size: var(--font-size-monospace-small); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js b/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js deleted file mode 100644 index e91a9db747536..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import * as React from 'react'; -import styles from './HocBadges.css'; - -import type {Element} from 'react-devtools-shared/src/frontend/types'; - -type Props = { - element: Element, -}; - -export default function HocBadges({element}: Props): React.Node { - const {hocDisplayNames} = ((element: any): Element); - - if (hocDisplayNames === null) { - return null; - } - - return ( -
- {hocDisplayNames.map(hocDisplayName => ( -
- {hocDisplayName} -
- ))} -
- ); -} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/IndexableDisplayName.js b/packages/react-devtools-shared/src/devtools/views/Components/IndexableDisplayName.js new file mode 100644 index 0000000000000..9148067e9dceb --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/IndexableDisplayName.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as React from 'react'; + +import {createRegExp} from '../utils'; + +import {TreeStateContext} from './TreeContext'; +import styles from './Element.css'; + +const {useMemo, useContext} = React; + +type Props = { + displayName: string | null, + id: number, +}; + +function IndexableDisplayName({displayName, id}: Props): React.Node { + const {searchIndex, searchResults, searchText} = useContext(TreeStateContext); + const isSearchResult = useMemo(() => { + return searchResults.includes(id); + }, [id, searchResults]); + const isCurrentResult = + searchIndex !== null && id === searchResults[searchIndex]; + + if (!isSearchResult || displayName === null) { + return displayName; + } + + const match = createRegExp(searchText).exec(displayName); + + if (match === null) { + return displayName; + } + + const startIndex = match.index; + const stopIndex = startIndex + match[0].length; + + const children = []; + if (startIndex > 0) { + children.push({displayName.slice(0, startIndex)}); + } + children.push( + + {displayName.slice(startIndex, stopIndex)} + , + ); + if (stopIndex < displayName.length) { + children.push({displayName.slice(stopIndex)}); + } + + return children; +} + +export default IndexableDisplayName; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.css b/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.css new file mode 100644 index 0000000000000..058ee241866d7 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.css @@ -0,0 +1,14 @@ +.Root { + display: inline-flex; + align-items: center; +} + +.Root *:not(:first-child) { + margin-left: 0.25rem; +} + +.ExtraLabel { + font-family: var(--font-family-monospace); + font-size: var(--font-size-monospace-small); + color: var(--color-component-badge-count); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.js b/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.js new file mode 100644 index 0000000000000..75db37c283dda --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/IndexableElementBadges.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as React from 'react'; + +import Badge from './Badge'; +import ForgetBadge from './ForgetBadge'; +import IndexableDisplayName from './IndexableDisplayName'; + +import styles from './IndexableElementBadges.css'; + +type Props = { + hocDisplayNames: Array | null, + compiledWithForget: boolean, + elementID: number, + className?: string, +}; + +export default function IndexableElementBadges({ + compiledWithForget, + hocDisplayNames, + elementID, + className = '', +}: Props): React.Node { + if ( + !compiledWithForget && + (hocDisplayNames == null || hocDisplayNames.length === 0) + ) { + return null; + } + + return ( +
+ {compiledWithForget && ( + + )} + + {hocDisplayNames != null && hocDisplayNames.length > 0 && ( + + + + )} + + {hocDisplayNames != null && hocDisplayNames.length > 1 && ( +
+{hocDisplayNames.length - 1}
+ )} +
+ ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css new file mode 100644 index 0000000000000..11f6a23d3b472 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.css @@ -0,0 +1,9 @@ +.Root { + padding: 0.25rem; + user-select: none; + display: inline-flex; +} + +.Root *:not(:first-child) { + margin-left: 0.25rem; +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js new file mode 100644 index 0000000000000..e7ddfb2a216f5 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js @@ -0,0 +1,36 @@ +/** + * 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} from 'react-devtools-shared/src/frontend/types'; + +import * as React from 'react'; + +import Badge from './Badge'; +import ForgetBadge from './ForgetBadge'; + +import styles from './InspectedElementBadges.css'; + +type Props = { + element: Element, +}; + +export default function InspectedElementBadges({element}: Props): React.Node { + const {hocDisplayNames, compiledWithForget} = element; + + return ( +
+ {compiledWithForget && } + + {hocDisplayNames !== null && + hocDisplayNames.map(hocDisplayName => ( + {hocDisplayName} + ))} +
+ ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js index 462677457c928..2f1503c65e487 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -17,7 +17,7 @@ import ContextMenuItem from '../../ContextMenu/ContextMenuItem'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; import Icon from '../Icon'; -import HocBadges from './HocBadges'; +import InspectedElementBadges from './InspectedElementBadges'; import InspectedElementContextTree from './InspectedElementContextTree'; import InspectedElementErrorsAndWarningsTree from './InspectedElementErrorsAndWarningsTree'; import InspectedElementHooksTree from './InspectedElementHooksTree'; @@ -26,7 +26,7 @@ import InspectedElementStateTree from './InspectedElementStateTree'; import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin'; import InspectedElementSuspenseToggle from './InspectedElementSuspenseToggle'; import NativeStyleEditor from './NativeStyleEditor'; -import Badge from './Badge'; +import ElementBadges from './ElementBadges'; import {useHighlightNativeElement} from '../hooks'; import { copyInspectedElementPath as copyInspectedElementPathAPI, @@ -41,12 +41,8 @@ import type {ContextMenuContextType} from '../context'; import type { Element, InspectedElement, - SerializedElement, -} from 'react-devtools-shared/src/frontend/types'; -import type { - ElementType, - HookNames, } from 'react-devtools-shared/src/frontend/types'; +import type {HookNames} from 'react-devtools-shared/src/frontend/types'; import type {ToggleParseHookNames} from './InspectedElementContext'; export type CopyPath = (path: Array) => void; @@ -90,7 +86,7 @@ export default function InspectedElementView({ return (
- +
rendered by
+ {showOwnersList && - ((owners: any): Array).map(owner => ( + owners?.map(owner => ( ))} + {rootType !== null && (
{rootType}
)} @@ -286,17 +285,17 @@ function Source({fileName, lineNumber}: SourceProps) { type OwnerViewProps = { displayName: string, hocDisplayNames: Array | null, + compiledWithForget: boolean, id: number, isInStore: boolean, - type: ElementType, }; function OwnerView({ displayName, hocDisplayNames, + compiledWithForget, id, isInStore, - type, }: OwnerViewProps) { const dispatch = useContext(TreeDispatcherContext); const {highlightNativeElement, clearHighlightNativeElement} = @@ -313,25 +312,25 @@ function OwnerView({ }); }, [dispatch, id]); - const onMouseEnter = () => highlightNativeElement(id); - - const onMouseLeave = clearHighlightNativeElement; - return ( ); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js index 760c490d72513..f88bc7e9724be 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js @@ -14,7 +14,7 @@ import {createContext, useCallback, useContext, useEffect} from 'react'; import {createResource} from '../../cache'; import {BridgeContext, StoreContext} from '../context'; import {TreeStateContext} from './TreeContext'; -import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; +import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils'; import type {OwnersList} from 'react-devtools-shared/src/backend/types'; import type { @@ -100,16 +100,7 @@ function OwnersListContextController({children}: Props): React.Node { request.resolveFn( ownersList.owners === null ? null - : ownersList.owners.map(owner => { - const [displayNameWithoutHOCs, hocDisplayNames] = - separateDisplayNameAndHOCs(owner.displayName, owner.type); - - return { - ...owner, - displayName: displayNameWithoutHOCs, - hocDisplayNames, - }; - }), + : ownersList.owners.map(backendToFrontendSerializedElementMapper), ); } } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.css b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.css index 5fed9e61d4e3f..293faa98376a7 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.css @@ -99,6 +99,6 @@ color: var(--color-dimmest); } -.Badge { +.BadgesBlock { margin-left: 0.25rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js index fda6efcc0d846..d4e177e30ac5f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js @@ -19,7 +19,7 @@ import { import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; import Toggle from '../Toggle'; -import Badge from './Badge'; +import ElementBadges from './ElementBadges'; import {OwnersListContext} from './OwnersListContext'; import {TreeDispatcherContext, TreeStateContext} from './TreeContext'; import {useIsOverflowing} from '../hooks'; @@ -204,11 +204,7 @@ type ElementsDropdownProps = { selectOwner: SelectOwner, ... }; -function ElementsDropdown({ - owners, - selectedIndex, - selectOwner, -}: ElementsDropdownProps) { +function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) { const store = useContext(StoreContext); const menuItems = []; @@ -222,10 +218,10 @@ function ElementsDropdown({ onSelect={() => (isInStore ? selectOwner(owner) : null)}> {owner.displayName} - , ); @@ -254,7 +250,7 @@ type ElementViewProps = { function ElementView({isSelected, owner, selectOwner}: ElementViewProps) { const store = useContext(StoreContext); - const {displayName, hocDisplayNames, type} = owner; + const {displayName, hocDisplayNames, compiledWithForget} = owner; const isInStore = store.containsElement(owner.id); const handleChange = useCallback(() => { @@ -270,10 +266,10 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) { onChange={handleChange}> {displayName} - ); diff --git a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js index 21cfea44dd825..6d4dec6472b04 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js @@ -993,10 +993,13 @@ function recursivelySearchTree( regExp: RegExp, searchResults: Array, ): void { - const {children, displayName, hocDisplayNames} = ((store.getElementByID( - elementID, - ): any): Element); + const element = store.getElementByID(elementID); + if (element == null) { + return; + } + + const {children, displayName, hocDisplayNames, compiledWithForget} = element; if (displayName != null && regExp.test(displayName) === true) { searchResults.push(elementID); } else if ( @@ -1005,6 +1008,8 @@ function recursivelySearchTree( hocDisplayNames.some(name => regExp.test(name)) === true ) { searchResults.push(elementID); + } else if (compiledWithForget && regExp.test('Forget')) { + searchResults.push(elementID); } children.forEach(childID => diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js index 0738870c56ac9..193ce766d705f 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/utils.js @@ -8,7 +8,7 @@ */ import {PROFILER_EXPORT_VERSION} from 'react-devtools-shared/src/constants'; -import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; +import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils'; import type {ProfilingDataBackend} from 'react-devtools-shared/src/backend/types'; import type { @@ -106,20 +106,9 @@ export function prepareProfilingDataFrontendFromBackendAndStore( timestamp: commitDataBackend.timestamp, updaters: commitDataBackend.updaters !== null - ? commitDataBackend.updaters.map(serializedElement => { - const [ - serializedElementDisplayName, - serializedElementHocDisplayNames, - ] = separateDisplayNameAndHOCs( - serializedElement.displayName, - serializedElement.type, - ); - return { - ...serializedElement, - displayName: serializedElementDisplayName, - hocDisplayNames: serializedElementHocDisplayNames, - }; - }) + ? commitDataBackend.updaters.map( + backendToFrontendSerializedElementMapper, + ) : null, }), ); diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js index 3d85a0109570d..76a6a741b7975 100644 --- a/packages/react-devtools-shared/src/frontend/types.js +++ b/packages/react-devtools-shared/src/frontend/types.js @@ -151,6 +151,10 @@ export type Element = { // This element is not in a StrictMode compliant subtree. // Only true for React versions supporting StrictMode. isStrictModeNonCompliant: boolean, + + // If component is compiled with Forget, the backend will send its name as Forget(...) + // Later, on the frontend side, we will strip HOC names and Forget prefix. + compiledWithForget: boolean, }; export type SerializedElement = { @@ -158,6 +162,7 @@ export type SerializedElement = { id: number, key: number | string | null, hocDisplayNames: Array | null, + compiledWithForget: boolean, type: ElementType, }; diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 52de92b5d6489..cd66740abba05 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -60,8 +60,10 @@ import type { ComponentFilter, ElementType, BrowserTheme, -} from './frontend/types'; -import type {LRUCache} from 'react-devtools-shared/src/frontend/types'; + SerializedElement as SerializedElementFrontend, + LRUCache, +} from 'react-devtools-shared/src/frontend/types'; +import type {SerializedElement as SerializedElementBackend} from 'react-devtools-shared/src/backend/types'; // $FlowFixMe[method-unbinding] const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -415,16 +417,35 @@ export function getOpenInEditorURL(): string { return getDefaultOpenInEditorURL(); } -export function separateDisplayNameAndHOCs( +type ParseElementDisplayNameFromBackendReturn = { + formattedDisplayName: string | null, + hocDisplayNames: Array | null, + compiledWithForget: boolean, +}; +export function parseElementDisplayNameFromBackend( displayName: string | null, type: ElementType, -): [string | null, Array | null] { +): ParseElementDisplayNameFromBackendReturn { if (displayName === null) { - return [null, null]; + return { + formattedDisplayName: null, + hocDisplayNames: null, + compiledWithForget: false, + }; } - let hocDisplayNames = null; + if (displayName.startsWith('Forget(')) { + const displayNameWithoutForgetWrapper = displayName.slice( + 7, + displayName.length - 1, + ); + const {formattedDisplayName, hocDisplayNames} = + parseElementDisplayNameFromBackend(displayNameWithoutForgetWrapper, type); + return {formattedDisplayName, hocDisplayNames, compiledWithForget: true}; + } + + let hocDisplayNames = null; switch (type) { case ElementTypeClass: case ElementTypeForwardRef: @@ -442,7 +463,11 @@ export function separateDisplayNameAndHOCs( break; } - return [displayName, hocDisplayNames]; + return { + formattedDisplayName: displayName, + hocDisplayNames, + compiledWithForget: false, + }; } // Pulled from react-compat @@ -897,3 +922,17 @@ export const isPlainObject = (object: Object): boolean => { const objectParentPrototype = Object.getPrototypeOf(objectPrototype); return !objectParentPrototype; }; + +export function backendToFrontendSerializedElementMapper( + element: SerializedElementBackend, +): SerializedElementFrontend { + const {formattedDisplayName, hocDisplayNames, compiledWithForget} = + parseElementDisplayNameFromBackend(element.displayName, element.type); + + return { + ...element, + displayName: formattedDisplayName, + hocDisplayNames, + compiledWithForget, + }; +}