Skip to content

Commit

Permalink
StyleX plug-in for resolving atomic styles to values for props.xstyle
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Vaughn committed Nov 22, 2021
1 parent 149b420 commit 167b67e
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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",
}
`);
});
});
81 changes: 81 additions & 0 deletions packages/react-devtools-shared/src/backend/StyleX/utils.js
Original file line number Diff line number Diff line change
@@ -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<string, string> = 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<string, any>(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;
}
10 changes: 9 additions & 1 deletion packages/react-devtools-shared/src/backend/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -3234,6 +3235,13 @@ export function attach(
targetErrorBoundaryID = getNearestErrorBoundaryID(fiber);
}

const modifiedProps = {
...memoizedProps,
};
if (modifiedProps.hasOwnProperty('xstyle')) {
modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle);
}

return {
id,

Expand Down Expand Up @@ -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()),
Expand Down

0 comments on commit 167b67e

Please sign in to comment.