Skip to content

Commit 167b67e

Browse files
author
Brian Vaughn
committed
StyleX plug-in for resolving atomic styles to values for props.xstyle
1 parent 149b420 commit 167b67e

File tree

3 files changed

+272
-1
lines changed

3 files changed

+272
-1
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
describe('Stylex plugin utils', () => {
11+
let getStyleXValues;
12+
let styleElements;
13+
14+
function defineStyles(style) {
15+
const styleElement = document.createElement('style');
16+
styleElement.type = 'text/css';
17+
styleElement.appendChild(document.createTextNode(style));
18+
19+
styleElements.push(styleElement);
20+
21+
document.head.appendChild(styleElement);
22+
}
23+
24+
beforeEach(() => {
25+
getStyleXValues = require('../utils').getStyleXValues;
26+
27+
styleElements = [];
28+
});
29+
30+
afterEach(() => {
31+
styleElements.forEach(styleElement => {
32+
document.head.removeChild(styleElement);
33+
});
34+
});
35+
36+
it('should support simple style objects', () => {
37+
defineStyles(`
38+
.foo {
39+
display: flex;
40+
}
41+
.bar: {
42+
align-items: center;
43+
}
44+
.baz {
45+
flex-direction: center;
46+
}
47+
`);
48+
49+
expect(
50+
getStyleXValues({
51+
display: 'foo',
52+
flexDirection: 'baz',
53+
alignItems: 'bar',
54+
}),
55+
).toMatchInlineSnapshot(`
56+
Object {
57+
"alignItems": "center",
58+
"display": "flex",
59+
"flexDirection": "center",
60+
}
61+
`);
62+
});
63+
64+
it('should support multiple style objects', () => {
65+
defineStyles(`
66+
.foo {
67+
display: flex;
68+
}
69+
.bar: {
70+
align-items: center;
71+
}
72+
.baz {
73+
flex-direction: center;
74+
}
75+
`);
76+
77+
expect(
78+
getStyleXValues([
79+
{display: 'foo'},
80+
{flexDirection: 'baz', alignItems: 'bar'},
81+
]),
82+
).toMatchInlineSnapshot(`
83+
Object {
84+
"alignItems": "center",
85+
"display": "flex",
86+
"flexDirection": "center",
87+
}
88+
`);
89+
});
90+
91+
it('should filter empty rules', () => {
92+
defineStyles(`
93+
.foo {
94+
display: flex;
95+
}
96+
.bar: {
97+
align-items: center;
98+
}
99+
.baz {
100+
flex-direction: center;
101+
}
102+
`);
103+
104+
expect(
105+
getStyleXValues([
106+
false,
107+
{display: 'foo'},
108+
false,
109+
false,
110+
{flexDirection: 'baz', alignItems: 'bar'},
111+
false,
112+
]),
113+
).toMatchInlineSnapshot(`
114+
Object {
115+
"alignItems": "center",
116+
"display": "flex",
117+
"flexDirection": "center",
118+
}
119+
`);
120+
});
121+
122+
it('should support pseudo-classes', () => {
123+
defineStyles(`
124+
.foo {
125+
color: black;
126+
}
127+
.bar: {
128+
color: blue;
129+
}
130+
.baz {
131+
text-decoration: none;
132+
}
133+
`);
134+
135+
expect(
136+
getStyleXValues({
137+
color: 'foo',
138+
':hover': {
139+
color: 'bar',
140+
textDecoration: 'baz',
141+
},
142+
}),
143+
).toMatchInlineSnapshot(`
144+
Object {
145+
":hover": Object {
146+
"color": "blue",
147+
"textDecoration": "none",
148+
},
149+
"color": "black",
150+
}
151+
`);
152+
});
153+
154+
it('should support nested selectors', () => {
155+
defineStyles(`
156+
.foo {
157+
display: flex;
158+
}
159+
.bar: {
160+
align-items: center;
161+
}
162+
.baz {
163+
flex-direction: center;
164+
}
165+
`);
166+
167+
expect(
168+
getStyleXValues([
169+
{display: 'foo'},
170+
false,
171+
[false, {flexDirection: 'baz'}, {alignItems: 'bar'}],
172+
false,
173+
]),
174+
).toMatchInlineSnapshot(`
175+
Object {
176+
"alignItems": "center",
177+
"display": "flex",
178+
"flexDirection": "center",
179+
}
180+
`);
181+
});
182+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
const cachedStyleNameToValueMap: Map<string, string> = new Map();
11+
12+
export function getStyleXValues(data: any, mappedStyles: Object = {}) {
13+
if (Array.isArray(data)) {
14+
data.forEach(entry => {
15+
if (Array.isArray(entry)) {
16+
getStyleXValues(entry, mappedStyles);
17+
} else {
18+
crawlObjectProperties(entry, mappedStyles);
19+
}
20+
});
21+
} else {
22+
crawlObjectProperties(data, mappedStyles);
23+
}
24+
25+
return Object.fromEntries<string, any>(Object.entries(mappedStyles).sort());
26+
}
27+
28+
function crawlObjectProperties(entry: Object, mappedStyles: Object) {
29+
const keys = Object.keys(entry);
30+
keys.forEach(key => {
31+
const value = entry[key];
32+
if (typeof value === 'string') {
33+
mappedStyles[key] = getPropertyValueForStyleName(value);
34+
} else {
35+
const nestedStyle = {};
36+
mappedStyles[key] = nestedStyle;
37+
getStyleXValues([value], nestedStyle);
38+
}
39+
});
40+
}
41+
42+
function getPropertyValueForStyleName(styleName: string): string | null {
43+
if (cachedStyleNameToValueMap.has(styleName)) {
44+
return ((cachedStyleNameToValueMap.get(styleName): any): string);
45+
}
46+
47+
for (
48+
let styleSheetIndex = 0;
49+
styleSheetIndex < document.styleSheets.length;
50+
styleSheetIndex++
51+
) {
52+
const styleSheet = ((document.styleSheets[
53+
styleSheetIndex
54+
]: any): CSSStyleSheet);
55+
// $FlowFixMe Flow doesn't konw about these properties
56+
const rules = styleSheet.rules || styleSheet.cssRules;
57+
for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
58+
const rule = rules[ruleIndex];
59+
// $FlowFixMe Flow doesn't konw about these properties
60+
const {cssText, selectorText, style} = rule;
61+
62+
if (selectorText != null) {
63+
if (selectorText.startsWith(`.${styleName}`)) {
64+
const match = cssText.match(/{ *([a-z\-]+):/);
65+
if (match !== null) {
66+
const property = match[1];
67+
const value = style.getPropertyValue(property);
68+
69+
cachedStyleNameToValueMap.set(styleName, value);
70+
71+
return value;
72+
} else {
73+
return null;
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
return null;
81+
}

packages/react-devtools-shared/src/backend/renderer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
8686
import is from 'shared/objectIs';
8787
import isArray from 'shared/isArray';
8888
import hasOwnProperty from 'shared/hasOwnProperty';
89+
import {getStyleXValues} from './StyleX/utils';
8990

9091
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
9192
import type {
@@ -3234,6 +3235,13 @@ export function attach(
32343235
targetErrorBoundaryID = getNearestErrorBoundaryID(fiber);
32353236
}
32363237

3238+
const modifiedProps = {
3239+
...memoizedProps,
3240+
};
3241+
if (modifiedProps.hasOwnProperty('xstyle')) {
3242+
modifiedProps.xstyle = getStyleXValues(modifiedProps.xstyle);
3243+
}
3244+
32373245
return {
32383246
id,
32393247

@@ -3279,7 +3287,7 @@ export function attach(
32793287
// TODO Review sanitization approach for the below inspectable values.
32803288
context,
32813289
hooks,
3282-
props: memoizedProps,
3290+
props: modifiedProps,
32833291
state: showState ? memoizedState : null,
32843292
errors: Array.from(errors.entries()),
32853293
warnings: Array.from(warnings.entries()),

0 commit comments

Comments
 (0)