From e2a3ab67d52e43bcb2ed1474d3cf14d34099cebd Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 22 Nov 2021 16:41:23 -0500 Subject: [PATCH 1/4] StyleX plug-in for resolving atomic styles to values for props.xstyle --- .../backend/StyleX/__tests__/utils-test.js | 182 ++++++++++++++++++ .../src/backend/StyleX/utils.js | 81 ++++++++ .../src/backend/renderer.js | 10 +- 3 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js create mode 100644 packages/react-devtools-shared/src/backend/StyleX/utils.js diff --git a/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js b/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js new file mode 100644 index 00000000000..201a31c0de5 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +describe('Stylex plugin utils', () => { + let getStyleXValues; + let styleElements; + + function defineStyles(style) { + const styleElement = document.createElement('style'); + styleElement.type = 'text/css'; + styleElement.appendChild(document.createTextNode(style)); + + styleElements.push(styleElement); + + document.head.appendChild(styleElement); + } + + beforeEach(() => { + getStyleXValues = require('../utils').getStyleXValues; + + styleElements = []; + }); + + afterEach(() => { + styleElements.forEach(styleElement => { + document.head.removeChild(styleElement); + }); + }); + + it('should support simple style objects', () => { + defineStyles(` + .foo { + display: flex; + } + .bar: { + align-items: center; + } + .baz { + flex-direction: center; + } + `); + + expect( + getStyleXValues({ + display: 'foo', + flexDirection: 'baz', + alignItems: 'bar', + }), + ).toMatchInlineSnapshot(` + Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + } + `); + }); + + it('should support multiple style objects', () => { + defineStyles(` + .foo { + display: flex; + } + .bar: { + align-items: center; + } + .baz { + flex-direction: center; + } + `); + + expect( + getStyleXValues([ + {display: 'foo'}, + {flexDirection: 'baz', alignItems: 'bar'}, + ]), + ).toMatchInlineSnapshot(` + Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + } + `); + }); + + it('should filter empty rules', () => { + defineStyles(` + .foo { + display: flex; + } + .bar: { + align-items: center; + } + .baz { + flex-direction: center; + } + `); + + expect( + getStyleXValues([ + false, + {display: 'foo'}, + false, + false, + {flexDirection: 'baz', alignItems: 'bar'}, + false, + ]), + ).toMatchInlineSnapshot(` + Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + } + `); + }); + + it('should support pseudo-classes', () => { + defineStyles(` + .foo { + color: black; + } + .bar: { + color: blue; + } + .baz { + text-decoration: none; + } + `); + + expect( + getStyleXValues({ + color: 'foo', + ':hover': { + color: 'bar', + textDecoration: 'baz', + }, + }), + ).toMatchInlineSnapshot(` + Object { + ":hover": Object { + "color": "blue", + "textDecoration": "none", + }, + "color": "black", + } + `); + }); + + it('should support nested selectors', () => { + defineStyles(` + .foo { + display: flex; + } + .bar: { + align-items: center; + } + .baz { + flex-direction: center; + } + `); + + expect( + getStyleXValues([ + {display: 'foo'}, + false, + [false, {flexDirection: 'baz'}, {alignItems: 'bar'}], + false, + ]), + ).toMatchInlineSnapshot(` + Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + } + `); + }); +}); diff --git a/packages/react-devtools-shared/src/backend/StyleX/utils.js b/packages/react-devtools-shared/src/backend/StyleX/utils.js new file mode 100644 index 00000000000..d5d2ff8ab91 --- /dev/null +++ b/packages/react-devtools-shared/src/backend/StyleX/utils.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +const cachedStyleNameToValueMap: Map = new Map(); + +export function getStyleXValues(data: any, mappedStyles: Object = {}) { + if (Array.isArray(data)) { + data.forEach(entry => { + if (Array.isArray(entry)) { + getStyleXValues(entry, mappedStyles); + } else { + crawlObjectProperties(entry, mappedStyles); + } + }); + } else { + crawlObjectProperties(data, mappedStyles); + } + + return Object.fromEntries(Object.entries(mappedStyles).sort()); +} + +function crawlObjectProperties(entry: Object, mappedStyles: Object) { + const keys = Object.keys(entry); + keys.forEach(key => { + const value = entry[key]; + if (typeof value === 'string') { + mappedStyles[key] = getPropertyValueForStyleName(value); + } else { + const nestedStyle = {}; + mappedStyles[key] = nestedStyle; + getStyleXValues([value], nestedStyle); + } + }); +} + +function getPropertyValueForStyleName(styleName: string): string | null { + if (cachedStyleNameToValueMap.has(styleName)) { + return ((cachedStyleNameToValueMap.get(styleName): any): string); + } + + for ( + let styleSheetIndex = 0; + styleSheetIndex < document.styleSheets.length; + styleSheetIndex++ + ) { + const styleSheet = ((document.styleSheets[ + styleSheetIndex + ]: any): CSSStyleSheet); + // $FlowFixMe Flow doesn't konw about these properties + const rules = styleSheet.rules || styleSheet.cssRules; + for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) { + const rule = rules[ruleIndex]; + // $FlowFixMe Flow doesn't konw about these properties + const {cssText, selectorText, style} = rule; + + if (selectorText != null) { + if (selectorText.startsWith(`.${styleName}`)) { + const match = cssText.match(/{ *([a-z\-]+):/); + if (match !== null) { + const property = match[1]; + const value = style.getPropertyValue(property); + + cachedStyleNameToValueMap.set(styleName, value); + + return value; + } else { + return null; + } + } + } + } + } + + return null; +} diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 8a89a5406c6..2f9b807d03b 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -86,6 +86,7 @@ import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags'; import is from 'shared/objectIs'; import isArray from 'shared/isArray'; import hasOwnProperty from 'shared/hasOwnProperty'; +import {getStyleXValues} from './StyleX/utils'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { @@ -3234,6 +3235,13 @@ export function attach( targetErrorBoundaryID = getNearestErrorBoundaryID(fiber); } + const modifiedProps = { + ...memoizedProps, + }; + if (modifiedProps.hasOwnProperty('xstyle')) { + modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle); + } + return { id, @@ -3279,7 +3287,7 @@ export function attach( // TODO Review sanitization approach for the below inspectable values. context, hooks, - props: memoizedProps, + props: modifiedProps, state: showState ? memoizedState : null, errors: Array.from(errors.entries()), warnings: Array.from(warnings.entries()), From 1645a2d998328cd631b988fc0e4e1f396ccefc48 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 7 Dec 2021 09:56:22 -0500 Subject: [PATCH 2/4] Wrap StyleX features behind new enableStyleXFeatures flag Only enabled for FB builds (for now) --- .../react-devtools-shared/src/backend/renderer.js | 11 ++++++++--- .../src/config/DevToolsFeatureFlags.core-fb.js | 7 ++++--- .../src/config/DevToolsFeatureFlags.core-oss.js | 7 ++++--- .../src/config/DevToolsFeatureFlags.default.js | 7 ++++--- .../src/config/DevToolsFeatureFlags.extension-fb.js | 7 ++++--- .../src/config/DevToolsFeatureFlags.extension-oss.js | 7 ++++--- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 2f9b807d03b..74a1d580e5a 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -82,7 +82,10 @@ import { MEMO_SYMBOL_STRING, } from './ReactSymbols'; import {format} from './utils'; -import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags'; +import { + enableProfilerChangedHookIndices, + enableStyleXFeatures, +} from 'react-devtools-feature-flags'; import is from 'shared/objectIs'; import isArray from 'shared/isArray'; import hasOwnProperty from 'shared/hasOwnProperty'; @@ -3238,8 +3241,10 @@ export function attach( const modifiedProps = { ...memoizedProps, }; - if (modifiedProps.hasOwnProperty('xstyle')) { - modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle); + if (enableStyleXFeatures) { + if (modifiedProps.hasOwnProperty('xstyle')) { + modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle); + } } return { diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js index 50eb47967ba..f382a2b4b2e 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-fb.js @@ -13,11 +13,12 @@ * It should always be imported from "react-devtools-feature-flags". ************************************************************************/ +export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableLogger = true; +export const enableNamedHooksFeature = true; export const enableProfilerChangedHookIndices = true; +export const enableStyleXFeatures = true; export const isInternalFacebookBuild = true; -export const enableNamedHooksFeature = true; -export const enableLogger = true; -export const consoleManagedByDevToolsDuringStrictMode = false; /************************************************************************ * Do not edit the code below. diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js index fd569a7c550..579efaedd61 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.core-oss.js @@ -13,11 +13,12 @@ * It should always be imported from "react-devtools-feature-flags". ************************************************************************/ +export const consoleManagedByDevToolsDuringStrictMode = false; +export const enableLogger = false; +export const enableNamedHooksFeature = true; export const enableProfilerChangedHookIndices = true; +export const enableStyleXFeatures = false; export const isInternalFacebookBuild = false; -export const enableNamedHooksFeature = true; -export const enableLogger = false; -export const consoleManagedByDevToolsDuringStrictMode = false; /************************************************************************ * Do not edit the code below. diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js index 9552dd0bd6e..3bd4efb101c 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.default.js @@ -13,8 +13,9 @@ * It should always be imported from "react-devtools-feature-flags". ************************************************************************/ +export const consoleManagedByDevToolsDuringStrictMode = true; +export const enableLogger = false; +export const enableNamedHooksFeature = true; export const enableProfilerChangedHookIndices = true; +export const enableStyleXFeatures = false; export const isInternalFacebookBuild = false; -export const enableNamedHooksFeature = true; -export const enableLogger = false; -export const consoleManagedByDevToolsDuringStrictMode = true; diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js index b3bd5a7a92d..d86b0ddf73a 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-fb.js @@ -13,11 +13,12 @@ * It should always be imported from "react-devtools-feature-flags". ************************************************************************/ +export const consoleManagedByDevToolsDuringStrictMode = true; +export const enableLogger = true; +export const enableNamedHooksFeature = true; export const enableProfilerChangedHookIndices = true; +export const enableStyleXFeatures = true; export const isInternalFacebookBuild = true; -export const enableNamedHooksFeature = true; -export const enableLogger = true; -export const consoleManagedByDevToolsDuringStrictMode = true; /************************************************************************ * Do not edit the code below. diff --git a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js index 53466bf84bb..f1a30730624 100644 --- a/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js +++ b/packages/react-devtools-shared/src/config/DevToolsFeatureFlags.extension-oss.js @@ -13,11 +13,12 @@ * It should always be imported from "react-devtools-feature-flags". ************************************************************************/ +export const consoleManagedByDevToolsDuringStrictMode = true; +export const enableLogger = false; +export const enableNamedHooksFeature = true; export const enableProfilerChangedHookIndices = true; +export const enableStyleXFeatures = false; export const isInternalFacebookBuild = false; -export const enableNamedHooksFeature = true; -export const enableLogger = false; -export const consoleManagedByDevToolsDuringStrictMode = true; /************************************************************************ * Do not edit the code below. From 54253644d28b5925e78124e94be3ac0dbf13b603 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 7 Dec 2021 13:43:50 -0500 Subject: [PATCH 3/4] Add stylex metadata to new plugins field And display it separately in DevTools, as read-only (for now) --- .../backend/StyleX/__tests__/utils-test.js | 107 +++++++++++++----- .../src/backend/StyleX/utils.js | 50 ++++++-- .../src/backend/legacy/renderer.js | 2 + .../src/backend/renderer.js | 19 ++-- .../src/backend/types.js | 11 ++ .../react-devtools-shared/src/backendAPI.js | 2 + .../InspectedElementStyleXPlugin.css | 6 + .../InspectedElementStyleXPlugin.js | 73 ++++++++++++ .../views/Components/InspectedElementView.js | 11 ++ .../src/devtools/views/Components/types.js | 11 ++ 10 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.css create mode 100644 packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js diff --git a/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js b/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js index 201a31c0de5..6783fea27f7 100644 --- a/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js +++ b/packages/react-devtools-shared/src/backend/StyleX/__tests__/utils-test.js @@ -8,7 +8,7 @@ */ describe('Stylex plugin utils', () => { - let getStyleXValues; + let getStyleXData; let styleElements; function defineStyles(style) { @@ -22,7 +22,7 @@ describe('Stylex plugin utils', () => { } beforeEach(() => { - getStyleXValues = require('../utils').getStyleXValues; + getStyleXData = require('../utils').getStyleXData; styleElements = []; }); @@ -47,16 +47,25 @@ describe('Stylex plugin utils', () => { `); expect( - getStyleXValues({ + getStyleXData({ + // The source/module styles are defined in + Example__style: 'Example__style', + + // Map of CSS style to StyleX class name, booleans, or nested structures display: 'foo', flexDirection: 'baz', alignItems: 'bar', }), ).toMatchInlineSnapshot(` Object { - "alignItems": "center", - "display": "flex", - "flexDirection": "center", + "resolvedStyles": Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + }, + "sources": Array [ + "Example__style", + ], } `); }); @@ -75,15 +84,25 @@ describe('Stylex plugin utils', () => { `); expect( - getStyleXValues([ - {display: 'foo'}, - {flexDirection: 'baz', alignItems: 'bar'}, + getStyleXData([ + {Example1__style: 'Example1__style', display: 'foo'}, + { + Example2__style: 'Example2__style', + flexDirection: 'baz', + alignItems: 'bar', + }, ]), ).toMatchInlineSnapshot(` Object { - "alignItems": "center", - "display": "flex", - "flexDirection": "center", + "resolvedStyles": Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + }, + "sources": Array [ + "Example1__style", + "Example2__style", + ], } `); }); @@ -102,19 +121,29 @@ describe('Stylex plugin utils', () => { `); expect( - getStyleXValues([ + getStyleXData([ false, - {display: 'foo'}, + {Example1__style: 'Example1__style', display: 'foo'}, false, false, - {flexDirection: 'baz', alignItems: 'bar'}, + { + Example2__style: 'Example2__style', + flexDirection: 'baz', + alignItems: 'bar', + }, false, ]), ).toMatchInlineSnapshot(` Object { - "alignItems": "center", - "display": "flex", - "flexDirection": "center", + "resolvedStyles": Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + }, + "sources": Array [ + "Example1__style", + "Example2__style", + ], } `); }); @@ -133,7 +162,11 @@ describe('Stylex plugin utils', () => { `); expect( - getStyleXValues({ + getStyleXData({ + // The source/module styles are defined in + Example__style: 'Example__style', + + // Map of CSS style to StyleX class name, booleans, or nested structures color: 'foo', ':hover': { color: 'bar', @@ -142,11 +175,16 @@ describe('Stylex plugin utils', () => { }), ).toMatchInlineSnapshot(` Object { - ":hover": Object { - "color": "blue", - "textDecoration": "none", + "resolvedStyles": Object { + ":hover": Object { + "color": "blue", + "textDecoration": "none", + }, + "color": "black", }, - "color": "black", + "sources": Array [ + "Example__style", + ], } `); }); @@ -165,17 +203,28 @@ describe('Stylex plugin utils', () => { `); expect( - getStyleXValues([ - {display: 'foo'}, + getStyleXData([ + {Example1__style: 'Example1__style', display: 'foo'}, false, - [false, {flexDirection: 'baz'}, {alignItems: 'bar'}], + [ + false, + {Example2__style: 'Example2__style', flexDirection: 'baz'}, + {Example3__style: 'Example3__style', alignItems: 'bar'}, + ], false, ]), ).toMatchInlineSnapshot(` Object { - "alignItems": "center", - "display": "flex", - "flexDirection": "center", + "resolvedStyles": Object { + "alignItems": "center", + "display": "flex", + "flexDirection": "center", + }, + "sources": Array [ + "Example1__style", + "Example2__style", + "Example3__style", + ], } `); }); diff --git a/packages/react-devtools-shared/src/backend/StyleX/utils.js b/packages/react-devtools-shared/src/backend/StyleX/utils.js index d5d2ff8ab91..fb77342241b 100644 --- a/packages/react-devtools-shared/src/backend/StyleX/utils.js +++ b/packages/react-devtools-shared/src/backend/StyleX/utils.js @@ -7,34 +7,66 @@ * @flow */ +export type StyleXData = {| + sources: Array, + resolvedStyles: Object, +|}; + const cachedStyleNameToValueMap: Map = new Map(); -export function getStyleXValues(data: any, mappedStyles: Object = {}) { +export function getStyleXData(data: any): StyleXData { + const sources = new Set(); + const resolvedStyles = {}; + + crawlData(data, sources, resolvedStyles); + + return { + sources: Array.from(sources).sort(), + resolvedStyles, + }; +} + +export function crawlData( + data: any, + sources: Set, + resolvedStyles: Object, +): void { if (Array.isArray(data)) { data.forEach(entry => { if (Array.isArray(entry)) { - getStyleXValues(entry, mappedStyles); + crawlData(entry, sources, resolvedStyles); } else { - crawlObjectProperties(entry, mappedStyles); + crawlObjectProperties(entry, sources, resolvedStyles); } }); } else { - crawlObjectProperties(data, mappedStyles); + crawlObjectProperties(data, sources, resolvedStyles); } - return Object.fromEntries(Object.entries(mappedStyles).sort()); + resolvedStyles = Object.fromEntries( + Object.entries(resolvedStyles).sort(), + ); } -function crawlObjectProperties(entry: Object, mappedStyles: Object) { +function crawlObjectProperties( + entry: Object, + sources: Set, + resolvedStyles: Object, +): void { const keys = Object.keys(entry); keys.forEach(key => { const value = entry[key]; if (typeof value === 'string') { - mappedStyles[key] = getPropertyValueForStyleName(value); + if (key === value) { + // Special case; this key is the name of the style's source/file/module. + sources.add(key); + } else { + resolvedStyles[key] = getPropertyValueForStyleName(value); + } } else { const nestedStyle = {}; - mappedStyles[key] = nestedStyle; - getStyleXValues([value], nestedStyle); + resolvedStyles[key] = nestedStyle; + crawlData([value], sources, nestedStyle); } }); } diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index c5684f222fa..d82fe6d3a9f 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -838,6 +838,8 @@ export function attach( rootType: null, rendererPackageName: null, rendererVersion: null, + + plugins: [], }; } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 74a1d580e5a..efe1cc567bb 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -89,7 +89,7 @@ import { import is from 'shared/objectIs'; import isArray from 'shared/isArray'; import hasOwnProperty from 'shared/hasOwnProperty'; -import {getStyleXValues} from './StyleX/utils'; +import {getStyleXData} from './StyleX/utils'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { @@ -102,6 +102,7 @@ import type { NativeType, PathFrame, PathMatch, + Plugin, ProfilingDataBackend, ProfilingDataForRootBackend, ReactRenderer, @@ -3238,12 +3239,14 @@ export function attach( targetErrorBoundaryID = getNearestErrorBoundaryID(fiber); } - const modifiedProps = { - ...memoizedProps, - }; + const plugins: Array = []; + if (enableStyleXFeatures) { - if (modifiedProps.hasOwnProperty('xstyle')) { - modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle); + if (memoizedProps.hasOwnProperty('xstyle')) { + plugins.push({ + type: 'stylex', + data: getStyleXData(memoizedProps.xstyle), + }); } } @@ -3292,7 +3295,7 @@ export function attach( // TODO Review sanitization approach for the below inspectable values. context, hooks, - props: modifiedProps, + props: memoizedProps, state: showState ? memoizedState : null, errors: Array.from(errors.entries()), warnings: Array.from(warnings.entries()), @@ -3306,6 +3309,8 @@ export function attach( rootType, rendererPackageName: renderer.rendererPackageName, rendererVersion: renderer.version, + + plugins, }; } diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 333ee30914c..b8eb5bc22b6 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -213,6 +213,14 @@ export type OwnersList = {| owners: Array | null, |}; +// For now, let's only support a hard-coded set of plugins. +type PluginType = 'stylex'; + +export type Plugin = { + type: PluginType, + data: any, +}; + export type InspectedElement = {| id: number, @@ -265,6 +273,9 @@ export type InspectedElement = {| // Meta information about the renderer that created this element. rendererPackageName: string | null, rendererVersion: string | null, + + // Array of UI plugins/visualizations for the inspected element. + plugins: Array, |}; export const InspectElementErrorType = 'error'; diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index 23b975e38d6..3849899b7df 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -208,6 +208,7 @@ export function convertInspectedElementBackendToFrontend( owners, context, hooks, + plugins, props, rendererPackageName, rendererVersion, @@ -233,6 +234,7 @@ export function convertInspectedElementBackendToFrontend( hasLegacyContext, id, key, + plugins, rendererPackageName, rendererVersion, rootType, diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.css new file mode 100644 index 00000000000..0aa7f62e279 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.css @@ -0,0 +1,6 @@ +.Source { + color: var(--color-dim); + margin-left: 1rem; + overflow: auto; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js new file mode 100644 index 00000000000..b834ac5407b --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 KeyValue from './KeyValue'; +import Store from '../../store'; +import sharedStyles from './InspectedElementSharedStyles.css'; +import styles from './InspectedElementStyleXPlugin.css'; + +import type {InspectedElement} from './types'; +import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; +import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types'; + +type Props = {| + bridge: FrontendBridge, + element: Element, + inspectedElement: InspectedElement, + store: Store, +|}; + +export default function InspectedElementStyleXPlugin({ + bridge, + element, + inspectedElement, + store, +}: Props) { + const styleXPlugin = inspectedElement.plugins.find( + ({type}) => type === 'stylex', + ); + if (styleXPlugin == null || styleXPlugin.data == null) { + return null; + } + + const {resolvedStyles, sources} = styleXPlugin.data; + + return ( +
+
+
stylex
+
+ {sources.map(source => ( +
+ {source} +
+ ))} + {Object.entries(resolvedStyles).map(([name, value]) => ( +
+ ); +} 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 8424621fcf1..ad1272b722c 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js @@ -23,6 +23,7 @@ import InspectedElementErrorsAndWarningsTree from './InspectedElementErrorsAndWa import InspectedElementHooksTree from './InspectedElementHooksTree'; import InspectedElementPropsTree from './InspectedElementPropsTree'; import InspectedElementStateTree from './InspectedElementStateTree'; +import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin'; import InspectedElementSuspenseToggle from './InspectedElementSuspenseToggle'; import NativeStyleEditor from './NativeStyleEditor'; import Badge from './Badge'; @@ -31,6 +32,7 @@ import { copyInspectedElementPath as copyInspectedElementPathAPI, storeAsGlobal as storeAsGlobalAPI, } from 'react-devtools-shared/src/backendAPI'; +import {enableStyleXFeatures} from 'react-devtools-feature-flags'; import styles from './InspectedElementView.css'; @@ -124,6 +126,15 @@ export default function InspectedElementView({ store={store} /> + {enableStyleXFeatures && ( + + )} + , |}; // TODO: Add profiling type From 6b9eb2b65887878bd50f42c72dc5282360974f11 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 7 Dec 2021 19:36:49 -0500 Subject: [PATCH 4/4] Refactored inspected element plugins type/format Moved StyleX plugin type to be shared. Replaced Array-of-plugins type with object type, since the frontend already needs to know about which types it supports anyway (so no need to do unnecessary filtering). --- .../src/backend/StyleX/utils.js | 7 ++----- .../src/backend/legacy/renderer.js | 4 +++- .../react-devtools-shared/src/backend/renderer.js | 11 +++++------ .../react-devtools-shared/src/backend/types.js | 13 +++---------- .../Components/InspectedElementStyleXPlugin.js | 13 ++++++++----- .../src/devtools/views/Components/types.js | 14 +++----------- packages/react-devtools-shared/src/types.js | 9 +++++++++ 7 files changed, 33 insertions(+), 38 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/StyleX/utils.js b/packages/react-devtools-shared/src/backend/StyleX/utils.js index fb77342241b..6bf5a297892 100644 --- a/packages/react-devtools-shared/src/backend/StyleX/utils.js +++ b/packages/react-devtools-shared/src/backend/StyleX/utils.js @@ -7,14 +7,11 @@ * @flow */ -export type StyleXData = {| - sources: Array, - resolvedStyles: Object, -|}; +import type {StyleXPlugin} from 'react-devtools-shared/src/types'; const cachedStyleNameToValueMap: Map = new Map(); -export function getStyleXData(data: any): StyleXData { +export function getStyleXData(data: any): StyleXPlugin { const sources = new Set(); const resolvedStyles = {}; diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index d82fe6d3a9f..b757d008ac2 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -839,7 +839,9 @@ export function attach( rendererPackageName: null, rendererVersion: null, - plugins: [], + plugins: { + stylex: null, + }, }; } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index efe1cc567bb..7aebe6e507a 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -102,7 +102,6 @@ import type { NativeType, PathFrame, PathMatch, - Plugin, ProfilingDataBackend, ProfilingDataForRootBackend, ReactRenderer, @@ -113,6 +112,7 @@ import type { import type { ComponentFilter, ElementType, + Plugins, } from 'react-devtools-shared/src/types'; type getDisplayNameForFiberType = (fiber: Fiber) => string | null; @@ -3239,14 +3239,13 @@ export function attach( targetErrorBoundaryID = getNearestErrorBoundaryID(fiber); } - const plugins: Array = []; + const plugins: Plugins = { + stylex: null, + }; if (enableStyleXFeatures) { if (memoizedProps.hasOwnProperty('xstyle')) { - plugins.push({ - type: 'stylex', - data: getStyleXData(memoizedProps.xstyle), - }); + plugins.stylex = getStyleXData(memoizedProps.xstyle); } } diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index b8eb5bc22b6..3318c8b6a96 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -13,6 +13,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type { ComponentFilter, ElementType, + Plugins, } from 'react-devtools-shared/src/types'; import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; @@ -213,14 +214,6 @@ export type OwnersList = {| owners: Array | null, |}; -// For now, let's only support a hard-coded set of plugins. -type PluginType = 'stylex'; - -export type Plugin = { - type: PluginType, - data: any, -}; - export type InspectedElement = {| id: number, @@ -274,8 +267,8 @@ export type InspectedElement = {| rendererPackageName: string | null, rendererVersion: string | null, - // Array of UI plugins/visualizations for the inspected element. - plugins: Array, + // UI plugins/visualizations for the inspected element. + plugins: Plugins, |}; export const InspectElementErrorType = 'error'; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js index b834ac5407b..8045803fd01 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementStyleXPlugin.js @@ -12,6 +12,7 @@ import KeyValue from './KeyValue'; import Store from '../../store'; import sharedStyles from './InspectedElementSharedStyles.css'; import styles from './InspectedElementStyleXPlugin.css'; +import {enableStyleXFeatures} from 'react-devtools-feature-flags'; import type {InspectedElement} from './types'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; @@ -30,14 +31,16 @@ export default function InspectedElementStyleXPlugin({ inspectedElement, store, }: Props) { - const styleXPlugin = inspectedElement.plugins.find( - ({type}) => type === 'stylex', - ); - if (styleXPlugin == null || styleXPlugin.data == null) { + if (!enableStyleXFeatures) { + return null; + } + + const styleXPlugin = inspectedElement.plugins.stylex; + if (styleXPlugin == null) { return null; } - const {resolvedStyles, sources} = styleXPlugin.data; + const {resolvedStyles, sources} = styleXPlugin; return (
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/types.js b/packages/react-devtools-shared/src/devtools/views/Components/types.js index 1abcc1b8d18..372cdd5b908 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/types.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/types.js @@ -12,7 +12,7 @@ import type { Dehydrated, Unserializable, } from 'react-devtools-shared/src/hydration'; -import type {ElementType} from 'react-devtools-shared/src/types'; +import type {ElementType, Plugins} from 'react-devtools-shared/src/types'; // Each element on the frontend corresponds to a Fiber on the backend. // Some of its information (e.g. id, type, displayName) come from the backend. @@ -64,14 +64,6 @@ export type InspectedElementResponseType = | 'no-change' | 'not-found'; -// For now, let's only support a hard-coded set of plugins. -type PluginType = 'stylex'; - -export type Plugin = { - type: PluginType, - data: any, -}; - export type InspectedElement = {| id: number, @@ -123,8 +115,8 @@ export type InspectedElement = {| rendererPackageName: string | null, rendererVersion: string | null, - // Array of UI plugins/visualizations for the inspected element. - plugins: Array, + // UI plugins/visualizations for the inspected element. + plugins: Plugins, |}; // TODO: Add profiling type diff --git a/packages/react-devtools-shared/src/types.js b/packages/react-devtools-shared/src/types.js index f8f334124e1..5a54979b63b 100644 --- a/packages/react-devtools-shared/src/types.js +++ b/packages/react-devtools-shared/src/types.js @@ -91,3 +91,12 @@ export type LRUCache = {| reset: () => void, set: (key: K, value: V) => void, |}; + +export type StyleXPlugin = {| + sources: Array, + resolvedStyles: Object, +|}; + +export type Plugins = {| + stylex: StyleXPlugin | null, +|};