Skip to content

Commit aab63b2

Browse files
nmnmellyeliu
andauthored
[feat] Simple sx={} syntax as an alternative to stylex.props (facebook#1218)
* [feat] Simple sx={} syntax as an alternative to stylex.props * feat(babel-plugin): make sx prop name configurable via sxPropName option Adds a new `sxPropName` option (default: 'sx') to allow users to configure the JSX prop name used for the sx shorthand. Setting it to `false` disables the transformation entirely. Also removes stray console.log statements from the JSXOpeningElement visitor. * fix(babel-plugin): fix flow errors in JSXOpeningElement visitor Rewrites the sx prop visitor to use a for-loop with explicit type guards (isJSXAttribute, isJSXExpressionContainer, isJSXEmptyExpression) so Flow can properly narrow the types at each step. --------- Co-authored-by: mellyeliu <mellye.liu@gmail.com>
1 parent 872d606 commit aab63b2

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

packages/@stylexjs/babel-plugin/__tests__/transform-stylex-props-test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,76 @@ describe('@stylexjs/babel-plugin', () => {
185185
`);
186186
});
187187

188+
test('Test that sx attribute can be used instead of ...stylex.props', () => {
189+
expect(
190+
transform(
191+
`
192+
import stylex from 'stylex';
193+
const styles = stylex.create({
194+
red: {
195+
color: 'red',
196+
}
197+
});
198+
function Foo() {
199+
return (
200+
<>
201+
<div id="test" sx={styles.red}>Hello World</div>
202+
<div className="test" sx={styles.red} id="test">Hello World</div>
203+
<div id="test" sx={styles.red} className="test">Hello World</div>
204+
</>
205+
);
206+
}
207+
`,
208+
options,
209+
),
210+
).toMatchInlineSnapshot(`
211+
"import _inject from "@stylexjs/stylex/lib/stylex-inject";
212+
var _inject2 = _inject;
213+
import stylex from 'stylex';
214+
_inject2({
215+
ltr: ".color-x1e2nbdu{color:red}",
216+
priority: 3000
217+
});
218+
function Foo() {
219+
return <>
220+
<div id="test" className="color-x1e2nbdu" data-style-src="npm-package:js/node_modules/npm-package/dist/components/Foo.react.js:4">Hello World</div>
221+
<div className="test" className="color-x1e2nbdu" data-style-src="npm-package:js/node_modules/npm-package/dist/components/Foo.react.js:4" id="test">Hello World</div>
222+
<div id="test" className="color-x1e2nbdu" data-style-src="npm-package:js/node_modules/npm-package/dist/components/Foo.react.js:4" className="test">Hello World</div>
223+
</>;
224+
}"
225+
`);
226+
});
227+
228+
test('sx prop with custom sxPropName', () => {
229+
expect(
230+
transform(
231+
`
232+
import stylex from 'stylex';
233+
const styles = stylex.create({
234+
red: {
235+
color: 'red',
236+
}
237+
});
238+
function Foo() {
239+
return <div css={styles.red}>Hello World</div>;
240+
}
241+
`,
242+
{ ...options, sxPropName: 'css' },
243+
),
244+
).toMatchInlineSnapshot(`
245+
"import _inject from "@stylexjs/stylex/lib/stylex-inject";
246+
var _inject2 = _inject;
247+
import stylex from 'stylex';
248+
_inject2({
249+
ltr: ".color-x1e2nbdu{color:red}",
250+
priority: 3000
251+
});
252+
function Foo() {
253+
return <div className="color-x1e2nbdu" data-style-src="npm-package:js/node_modules/npm-package/dist/components/Foo.react.js:4">Hello World</div>;
254+
}"
255+
`);
256+
});
257+
188258
test('local dynamic styles', () => {
189259
expect(
190260
transform(

packages/@stylexjs/babel-plugin/src/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,50 @@ function styleXTransform(): PluginObj<> {
284284
},
285285
},
286286

287+
JSXOpeningElement(path: NodePath<t.JSXOpeningElement>) {
288+
const sxPropName = state.options.sxPropName;
289+
if (sxPropName === false) {
290+
return;
291+
}
292+
const node = path.node;
293+
if (
294+
node.name.type !== 'JSXIdentifier' ||
295+
typeof node.name.name !== 'string' ||
296+
node.name.name[0] !== node.name.name[0].toLowerCase()
297+
) {
298+
return;
299+
}
300+
for (const attr of path.get('attributes')) {
301+
if (
302+
!attr.isJSXAttribute() ||
303+
!attr.get('name').isJSXIdentifier() ||
304+
attr.get('name').node.name !== sxPropName
305+
) {
306+
continue;
307+
}
308+
const valuePath = attr.get('value');
309+
if (!valuePath.isJSXExpressionContainer()) {
310+
continue;
311+
}
312+
const value = valuePath.get('expression');
313+
if (value.isJSXEmptyExpression()) {
314+
continue;
315+
}
316+
attr.replaceWith(
317+
t.jsxSpreadAttribute(
318+
t.callExpression(
319+
t.memberExpression(
320+
t.identifier('stylex'),
321+
t.identifier('props'),
322+
),
323+
[value.node],
324+
),
325+
),
326+
);
327+
break;
328+
}
329+
},
330+
287331
CallExpression(path: NodePath<t.CallExpression>) {
288332
const parentPath = path.parentPath;
289333
if (parentPath.isVariableDeclarator()) {

packages/@stylexjs/babel-plugin/src/utils/state-manager.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export type StyleXOptions = $ReadOnly<{
104104
enableLogicalStylesPolyfill?: boolean,
105105
enableLTRRTLComments?: boolean,
106106
enableMinifiedKeys?: boolean,
107+
sxPropName?: string | false,
107108
importSources: $ReadOnlyArray<
108109
string | $ReadOnly<{ from: string, as: string }>,
109110
>,
@@ -302,6 +303,13 @@ export default class StateManager {
302303
'options.enableLTRRTLComments',
303304
);
304305

306+
const sxPropName: StyleXStateOptions['sxPropName'] = z.logAndDefault(
307+
z.unionOf(z.string(), z.literal(false)),
308+
options.sxPropName ?? 'sx',
309+
'sx',
310+
'options.sxPropName',
311+
);
312+
305313
const test: StyleXStateOptions['test'] = z.logAndDefault(
306314
z.boolean(),
307315
options.test ?? defaultOptions.test,
@@ -439,6 +447,7 @@ export default class StateManager {
439447
enableLegacyValueFlipping,
440448
enableLogicalStylesPolyfill,
441449
enableLTRRTLComments,
450+
sxPropName,
442451
importSources,
443452
rewriteAliases:
444453
typeof options.rewriteAliases === 'boolean'

0 commit comments

Comments
 (0)