Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_stylex_attrs_style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"packages/react/package.json": "Patch",
"bindings/devup-ui-wasm/package.json": "Patch"
},
"note": "stylex.attrs() gives the dynamic CSS variables it sets as `style` attribute text (`\"--a:\" + value`), as StyleX does for spreading onto non-React targets, instead of a style object; stylex.props() keeps the object React takes. The attrs() return type declares `style` as a string",
"date": "2026-10-01T00:00:00.000Z"
}
19 changes: 19 additions & 0 deletions libs/extractor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20814,6 +20814,25 @@ const e = <div {...attrs(s.a)} />;"
)));
}

// `attrs()` gives the `style` attribute as text, as StyleX does, while
// `props()` keeps the object React takes
#[test]
#[serial]
fn test_stylex_attrs_style_is_text() {
let code = extract_tsx(
r"import * as stylex from '@stylexjs/stylex';
const s = stylex.create({ box: (w, h) => ({ width: w, height: h }) });
export const a = stylex.attrs(s.box(w, h));
export const p = stylex.props(s.box(w, h));",
)
.code;
assert!(
code.contains("style: \"--a:\" + ((v) => typeof v === \"number\" ? v + \"px\" : v)(w) + \";--b:\" + ((v) => typeof v === \"number\" ? v + \"px\" : v)(h)"),
"{code}"
);
assert!(code.contains("style: {\n\t\t\"--a\":"), "{code}");
}

#[test]
#[serial]
fn test_emotion_global_with_spread_attribute() {
Expand Down
56 changes: 51 additions & 5 deletions libs/extractor/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,47 @@ impl<'a> DevupVisitor<'a> {
self.stylex_function(callee).as_ref() == Some(function)
}

/// `"--a:" + a + ";--b:" + b`: the variables `props` sets, as the text of a
/// `style` attribute
fn style_text(&self, props: Vec<ObjectPropertyKind<'a>>) -> Expression<'a> {
let mut text: Option<Expression<'a>> = None;
let props = props.into_iter().filter_map(|prop| {
if let ObjectPropertyKind::ObjectProperty(prop) = prop {
Some(prop.unbox())
} else {
None
}
});
for (index, prop) in props.enumerate() {
let name = get_string_by_property_key(&prop.key).unwrap_or_default();
let prefix = format!("{}{name}:", if index == 0 { "" } else { ";" });
let head = Expression::new_string_literal(
SPAN,
Str::from_in(prefix.as_str(), self.ast.allocator()),
None,
&self.ast,
);
let head = match text {
Some(text) => Expression::new_binary_expression(
SPAN,
text,
BinaryOperator::Addition,
head,
&self.ast,
),
None => head,
};
text = Some(Expression::new_binary_expression(
SPAN,
head,
BinaryOperator::Addition,
Expression::new_parenthesized_expression(SPAN, prop.value, &self.ast),
&self.ast,
));
}
text.unwrap_or_else(|| Expression::new_string_literal(SPAN, "", None, &self.ast))
}

/// Resolve `stylex.props()` arguments to className expressions and style properties.
/// Returns (`class_exprs`, `style_props`) where `style_props` are CSS variable assignments
/// from dynamic namespace calls like `styles.bar(h)`.
Expand Down Expand Up @@ -1565,11 +1606,16 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> {

// Add style property for dynamic CSS variables
if !style_props.is_empty() {
let style_obj = Expression::new_object_expression(
SPAN,
oxc_allocator::Vec::from_iter_in(style_props, &self.ast),
&self.ast,
);
// `attrs()` gives the `style` attribute as text, `props()` an object
let style_obj = if class_attribute == "class" {
self.style_text(style_props)
} else {
Expression::new_object_expression(
SPAN,
oxc_allocator::Vec::from_iter_in(style_props, &self.ast),
&self.ast,
)
};
props.push(ObjectPropertyKind::new_object_property(
SPAN,
PropertyKind::Init,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/utils/stylex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function props(

export function attrs(
..._styles: ReadonlyArray<StyleProperties | false | null | undefined>
): { class?: string; style?: Record<string, string> } {
): { class?: string; style?: string } {
throw new Error('Cannot run on the runtime')
}

Expand Down
Loading