Skip to content

Commit 3431c9f

Browse files
committed
Babel preset with integrated StyleX
This patch changes RSD to export a babel preset (not plugin), which integrates StyleX to simplify developer setup and ensure that the correct configuration is used. StyleX itself is also patched to fix theming on web and remove deprecated or unrelated APIs for cross-platform React.
1 parent e598e12 commit 3431c9f

File tree

13 files changed

+566
-70
lines changed

13 files changed

+566
-70
lines changed

apps/examples/babel.config.js

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
const stylexPlugin = require('@stylexjs/babel-plugin');
9-
const reactStrictPlugin = require('react-strict-dom/babel');
8+
const reactStrictPreset = require('react-strict-dom/babel-preset');
109

1110
function getPlatform(caller) {
1211
return caller && caller.platform;
@@ -27,30 +26,18 @@ module.exports = function (api) {
2726
const platform = api.caller(getPlatform);
2827
const isDev = api.caller(getIsDev);
2928

29+
const presets = ['babel-preset-expo'];
3030
const plugins = [];
3131

3232
if (platform === 'web') {
33-
plugins.push([reactStrictPlugin, { debug: true }]);
34-
plugins.push([
35-
stylexPlugin,
36-
{
37-
dev: isDev,
38-
importSources: [
39-
'@stylexjs/stylex',
40-
{ from: 'react-strict-dom', as: 'css' }
41-
],
42-
runtimeInjection: isDev,
43-
styleResolution: 'property-specificity',
44-
unstable_moduleResolution: {
45-
rootDir: __dirname,
46-
type: 'commonJS'
47-
}
48-
}
33+
presets.push([
34+
reactStrictPreset,
35+
{ debug: true, dev: isDev, rootDir: __dirname }
4936
]);
5037
}
5138

5239
return {
5340
plugins,
54-
presets: ['babel-preset-expo']
41+
presets
5542
};
5643
};

apps/examples/src/App.js

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import * as React from 'react';
1111
import { ScrollView } from 'react-native';
1212
import { css, html } from 'react-strict-dom';
13-
import { tokens } from './tokens.stylex';
13+
import { tokens, themeColors, systemColors } from './tokens.stylex';
1414

1515
type ExampleBlockProps = $ReadOnly<{
1616
title: string,
@@ -38,6 +38,63 @@ function ExampleBlock(props: ExampleBlockProps) {
3838
);
3939
}
4040

41+
const redBlueTheme = css.createTheme(themeColors, {
42+
primary100: 'red',
43+
primary200: 'blue'
44+
});
45+
const purpleYellowTheme = css.createTheme(themeColors, {
46+
primary100: 'purple',
47+
primary200: 'yellow'
48+
});
49+
const greenPinkTheme = css.createTheme(themeColors, {
50+
primary100: 'green',
51+
primary200: 'pink'
52+
});
53+
54+
const themedStyles = css.create({
55+
container: {
56+
flex: 1,
57+
justifyContent: 'center',
58+
backgroundColor: '#bbb',
59+
padding: 8
60+
},
61+
square: {
62+
width: 100,
63+
height: 100,
64+
backgroundColor: systemColors.squareColor,
65+
borderColor: systemColors.outlineColor,
66+
borderStyle: 'solid',
67+
borderWidth: 5
68+
}
69+
});
70+
71+
function ThemeExample() {
72+
return (
73+
<html.div style={themedStyles.container}>
74+
{/* default theme */}
75+
<html.div style={themedStyles.square} />
76+
{/* redblue theme */}
77+
<html.div style={redBlueTheme}>
78+
<html.div style={themedStyles.square} />
79+
</html.div>
80+
{/* purpleyellow theme */}
81+
<html.div style={purpleYellowTheme}>
82+
<html.div style={themedStyles.square} />
83+
</html.div>
84+
{/* greenpink theme */}
85+
<html.div style={greenPinkTheme}>
86+
<html.div style={themedStyles.square} />
87+
</html.div>
88+
{/* nested theme */}
89+
<html.div style={redBlueTheme}>
90+
<html.div style={greenPinkTheme}>
91+
<html.div style={themedStyles.square} />
92+
</html.div>
93+
</html.div>
94+
</html.div>
95+
);
96+
}
97+
4198
const BGCOLOR_INACTIVE = 'red';
4299
const BGCOLOR_ACTIVE = 'blue';
43100

@@ -457,6 +514,8 @@ function Shell(): React.MixedElement {
457514
/>
458515
</html.div>
459516
</html.div>
517+
518+
<ThemeExample />
460519
</ExampleBlock>
461520

462521
{/* hover */}

apps/examples/src/tokens.stylex.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
* @flow strict-local
88
*/
99

10-
import type { VarGroup } from '@stylexjs/stylex';
10+
import type { StyleVars } from 'react-strict-dom';
1111

1212
import { css } from 'react-strict-dom';
1313

14-
export const tokens: VarGroup<
14+
export const tokens: StyleVars<
1515
$ReadOnly<{
1616
squareColor: string,
1717
textColor: string,
@@ -27,3 +27,23 @@ export const tokens: VarGroup<
2727
inputColor: 'red',
2828
inputPlaceholderColor: 'pink'
2929
});
30+
31+
export const themeColors: StyleVars<
32+
$ReadOnly<{
33+
primary100: string,
34+
primary200: string
35+
}>
36+
> = css.defineVars({
37+
primary100: 'black',
38+
primary200: 'white'
39+
});
40+
41+
export const systemColors: StyleVars<
42+
$ReadOnly<{
43+
squareColor: string,
44+
outlineColor: string
45+
}>
46+
> = css.defineVars({
47+
squareColor: themeColors.primary100,
48+
outlineColor: themeColors.primary200
49+
});

packages/react-strict-dom/babel/index.js

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
*/
77

88
const path = require('path');
9-
const { addNamed, addNamespace } = require('@babel/helper-module-imports');
9+
const { addNamed } = require('@babel/helper-module-imports');
10+
const styleXPlugin = require('@stylexjs/babel-plugin');
1011

1112
function createShortFilename(absolutePath, baseDir = process.cwd()) {
1213
if (!path.isAbsolute(baseDir)) {
@@ -18,7 +19,7 @@ function createShortFilename(absolutePath, baseDir = process.cwd()) {
1819
return shortPath;
1920
}
2021

21-
module.exports = function ({ types: t }, options = {}) {
22+
function reactStrictPlugin({ types: t }, options = {}) {
2223
const packageName = 'react-strict-dom';
2324
const packageRuntime = 'react-strict-dom/runtime';
2425
const findImportDeclaration = (body, sourceValue) =>
@@ -31,12 +32,13 @@ module.exports = function ({ types: t }, options = {}) {
3132
? specifiers.filter((specifier) => specifier.imported.name === 'html')
3233
: [];
3334
let defaultStylesImportIdentifier;
34-
let stylexImportIdentifier;
35+
let styleResolverImportIdentifier;
3536

3637
return {
3738
visitor: {
3839
Program: {
3940
enter(path) {
41+
// Add runtime imports
4042
const importDeclarations = findImportDeclaration(
4143
path.node.body,
4244
packageName
@@ -47,18 +49,24 @@ module.exports = function ({ types: t }, options = {}) {
4749
'defaultStyles',
4850
packageRuntime
4951
);
50-
stylexImportIdentifier = addNamespace(path, '@stylexjs/stylex', {
51-
nameHint: 'stylex'
52-
});
52+
styleResolverImportIdentifier = addNamed(
53+
path,
54+
'resolveStyle',
55+
packageRuntime
56+
);
5357
path.scope.rename(
5458
'defaultStyles',
5559
defaultStylesImportIdentifier.name
5660
);
57-
path.scope.rename('stylex', stylexImportIdentifier.name);
61+
path.scope.rename(
62+
'resolveStyle',
63+
styleResolverImportIdentifier.name
64+
);
5865
}
5966
}
6067
},
6168
JSXMemberExpression(path, state) {
69+
//
6270
const importDeclarations = findImportDeclaration(
6371
state.file.ast.program.body,
6472
packageName
@@ -96,16 +104,19 @@ module.exports = function ({ types: t }, options = {}) {
96104
let dirAttributeExists = false;
97105

98106
path.node.attributes.forEach((attribute, index) => {
107+
// React DOM compat: 'for' replaced by 'htmlFor'
99108
if (t.isJSXAttribute(attribute) && attribute.name.name === 'for') {
100109
attribute.name.name = 'htmlFor';
101110
}
111+
// Browser compat: 'role=none' replaced by 'role=presentation'
102112
if (
103113
t.isJSXAttribute(attribute) &&
104114
attribute.name.name === 'role' &&
105115
attribute.value.value === 'none'
106116
) {
107117
attribute.value.value = 'presentation';
108118
}
119+
// React DOM compat: 'style' replaced by resolver that produces React DOM props
109120
if (
110121
t.isJSXAttribute(attribute) &&
111122
attribute.name.name === 'style'
@@ -119,10 +130,7 @@ module.exports = function ({ types: t }, options = {}) {
119130
);
120131
path.node.attributes[index] = t.jsxSpreadAttribute(
121132
t.callExpression(
122-
t.memberExpression(
123-
t.identifier(stylexImportIdentifier.name),
124-
t.identifier('props')
125-
),
133+
t.identifier(styleResolverImportIdentifier.name),
126134
[defaultStyles].concat(
127135
Array.isArray(styleValue.elements)
128136
? styleValue.elements
@@ -139,13 +147,15 @@ module.exports = function ({ types: t }, options = {}) {
139147
}
140148
});
141149

150+
// Set type=button on <button> by default
142151
const elementName = path.node.name.property.name;
143152
if (elementName === 'button' && !typeAttributeExists) {
144153
path.node.attributes.push(
145154
t.jsxAttribute(t.jsxIdentifier('type'), t.stringLiteral('button'))
146155
);
147156
}
148157

158+
// Set dir=auto by default on text inputs
149159
if (
150160
(elementName === 'input' || elementName === 'textarea') &&
151161
!dirAttributeExists
@@ -155,6 +165,7 @@ module.exports = function ({ types: t }, options = {}) {
155165
);
156166
}
157167

168+
// Inline the style resolving logic
158169
if (!styleAttributeExists) {
159170
const elementName = path.node.name.property.name;
160171
const defaultStyles = t.memberExpression(
@@ -164,10 +175,7 @@ module.exports = function ({ types: t }, options = {}) {
164175
path.node.attributes.push(
165176
t.jsxSpreadAttribute(
166177
t.callExpression(
167-
t.memberExpression(
168-
t.identifier(stylexImportIdentifier.name),
169-
t.identifier('props')
170-
),
178+
t.identifier(styleResolverImportIdentifier.name),
171179
[defaultStyles]
172180
)
173181
)
@@ -195,7 +203,7 @@ module.exports = function ({ types: t }, options = {}) {
195203
// displays filename and line number of the source element
196204
path.node.attributes.unshift(
197205
t.jsxAttribute(
198-
t.jsxIdentifier('data-react-src'),
206+
t.jsxIdentifier('data-element-src'),
199207
t.stringLiteral(`${shortFilename}:${originalLineNumber}`)
200208
)
201209
);
@@ -204,4 +212,44 @@ module.exports = function ({ types: t }, options = {}) {
204212
}
205213
}
206214
};
215+
}
216+
217+
const defaultOptions = {
218+
dev: true,
219+
debug: true,
220+
rootDir: process.cwd()
207221
};
222+
223+
function reactStrictPreset(_, options = {}) {
224+
const opts = { ...defaultOptions, ...options };
225+
226+
return {
227+
plugins: [
228+
[
229+
reactStrictPlugin,
230+
{
231+
debug: opts.debug
232+
}
233+
],
234+
[
235+
styleXPlugin,
236+
{
237+
dev: opts.dev,
238+
importSources: [{ from: 'react-strict-dom', as: 'css' }],
239+
runtimeInjection: opts.dev, // temporary until Expo/Metro can support built-time
240+
styleResolution: 'property-specificity',
241+
unstable_moduleResolution: {
242+
type: 'commonJS',
243+
rootDir: opts.rootDir
244+
//themeFileExtension: '.cssvars.js',
245+
},
246+
useRemForFontSize: false
247+
}
248+
]
249+
]
250+
};
251+
}
252+
253+
reactStrictPreset.generateStyles = styleXPlugin.processStylexRules;
254+
255+
module.exports = reactStrictPreset;

packages/react-strict-dom/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"react-native": "./dist/native/index.js",
88
"default": "./dist/dom/index.js"
99
},
10-
"./babel": "./babel/index.js",
10+
"./babel-preset": "./babel/index.js",
1111
"./runtime": "./dist/dom/runtime.js",
1212
"./package.json": "./package.json"
1313
},

packages/react-strict-dom/src/dom/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,22 @@
77
* @flow strict
88
*/
99

10+
import type {
11+
StyleXStyles,
12+
StyleXStylesWithout,
13+
StaticStyles,
14+
Theme,
15+
VarGroup
16+
} from '@stylexjs/stylex';
17+
1018
import * as html from './html';
1119
import * as css from '@stylexjs/stylex';
1220

21+
type StyleTheme<V, T> = Theme<V, T>;
22+
type StyleVars<T> = VarGroup<T>;
23+
type Styles<T> = StyleXStyles<T>;
24+
type StylesWithout<T> = StyleXStylesWithout<T>;
25+
26+
export type { StaticStyles, StyleTheme, StyleVars, Styles, StylesWithout };
27+
1328
export { css, html };

packages/react-strict-dom/src/dom/runtime.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,5 @@ export const defaultStyles = {
176176
u: u as typeof u,
177177
ul: ul as typeof ul
178178
};
179+
180+
export const resolveStyle = stylex.props;

0 commit comments

Comments
 (0)