Skip to content

Commit 5776b5c

Browse files
committed
Support stylex
1 parent a6debc2 commit 5776b5c

23 files changed

Lines changed: 1553 additions & 2 deletions
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use crate::ExtractStyleProp;
2+
use crate::extract_style::extract_static_style::ExtractStaticStyle;
3+
use crate::extract_style::extract_style_value::ExtractStyleValue;
4+
use crate::stylex::{
5+
SelectorPart, decompose_value_conditions, format_number, is_unitless_property,
6+
normalize_stylex_property,
7+
};
8+
use crate::utils::{get_number_by_literal_expression, get_string_by_literal_expression};
9+
use css::optimize_value::optimize_value;
10+
use oxc_ast::AstBuilder;
11+
use oxc_ast::ast::{Expression, ObjectPropertyKind};
12+
13+
use crate::utils::get_string_by_property_key;
14+
15+
/// Extract styles from a `stylex.create()` call's argument (ObjectExpression).
16+
///
17+
/// Handles static string/number values (Phase 1) and value-level conditions (Phase 2).
18+
///
19+
/// Returns a Vec of `(namespace_name, style_props)` pairs. Each namespace
20+
/// corresponds to a top-level key in the `stylex.create({...})` argument.
21+
pub fn extract_stylex_namespace_styles<'a>(
22+
_ast_builder: &AstBuilder<'a>,
23+
expression: &mut Expression<'a>,
24+
) -> Vec<(String, Vec<ExtractStyleProp<'a>>)> {
25+
let Expression::ObjectExpression(obj) = expression else {
26+
return vec![];
27+
};
28+
29+
let mut result = vec![];
30+
31+
for prop in obj.properties.iter() {
32+
let ObjectPropertyKind::ObjectProperty(prop) = prop else {
33+
continue;
34+
};
35+
36+
let Some(ns_name) = get_string_by_property_key(&prop.key) else {
37+
continue;
38+
};
39+
40+
let Expression::ObjectExpression(ns_obj) = &prop.value else {
41+
// Non-object namespace value (e.g., null): push empty styles
42+
result.push((ns_name, vec![]));
43+
continue;
44+
};
45+
46+
let mut styles = vec![];
47+
48+
for style_prop in ns_obj.properties.iter() {
49+
let ObjectPropertyKind::ObjectProperty(style_prop) = style_prop else {
50+
continue;
51+
};
52+
53+
let Some(prop_name) = get_string_by_property_key(&style_prop.key) else {
54+
continue;
55+
};
56+
57+
// Phase 2: pseudo-element / pseudo-class top-level keys
58+
if prop_name.starts_with("::") || prop_name.starts_with(':') {
59+
let Expression::ObjectExpression(inner_obj) = &style_prop.value else {
60+
continue;
61+
};
62+
for inner_prop in inner_obj.properties.iter() {
63+
let ObjectPropertyKind::ObjectProperty(inner_prop) = inner_prop else {
64+
continue;
65+
};
66+
let Some(inner_name) = get_string_by_property_key(&inner_prop.key) else {
67+
continue;
68+
};
69+
let inner_css_property = normalize_stylex_property(&inner_name);
70+
let parent_selectors = vec![SelectorPart::Pseudo(prop_name.clone())];
71+
for decomposed in decompose_value_conditions(
72+
&inner_css_property,
73+
&inner_prop.value,
74+
&parent_selectors,
75+
) {
76+
if let Some(css_value) = decomposed.value {
77+
styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static(
78+
ExtractStaticStyle {
79+
property: decomposed.property,
80+
value: optimize_value(&css_value),
81+
level: 0,
82+
selector: decomposed.selector,
83+
style_order: None,
84+
layer: None,
85+
},
86+
)));
87+
}
88+
}
89+
}
90+
continue;
91+
}
92+
93+
let css_property = normalize_stylex_property(&prop_name);
94+
95+
// Phase 1: static string/number values
96+
let css_value = if let Some(s) = get_string_by_literal_expression(&style_prop.value) {
97+
s
98+
} else if let Some(n) = get_number_by_literal_expression(&style_prop.value) {
99+
if is_unitless_property(&css_property) || n == 0.0 {
100+
format_number(n)
101+
} else {
102+
format!("{}px", format_number(n))
103+
}
104+
} else if matches!(&style_prop.value, Expression::ObjectExpression(_)) {
105+
// Phase 2: value-level conditions
106+
for decomposed in decompose_value_conditions(&css_property, &style_prop.value, &[])
107+
{
108+
if let Some(css_value) = decomposed.value {
109+
styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static(
110+
ExtractStaticStyle {
111+
property: decomposed.property,
112+
value: optimize_value(&css_value),
113+
level: 0,
114+
selector: decomposed.selector,
115+
style_order: None,
116+
layer: None,
117+
},
118+
)));
119+
}
120+
}
121+
continue;
122+
} else {
123+
// Skip NullLiteral, dynamic values, etc.
124+
continue;
125+
};
126+
127+
// Construct directly — bypass convert_value() to avoid devup-ui
128+
// spacing transformations. StyleX values are raw CSS, only optimize_value().
129+
styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static(
130+
ExtractStaticStyle {
131+
property: css_property,
132+
value: optimize_value(&css_value),
133+
level: 0,
134+
selector: None,
135+
style_order: None,
136+
layer: None,
137+
},
138+
)));
139+
}
140+
141+
result.push((ns_name, styles));
142+
}
143+
144+
result
145+
}

libs/extractor/src/extractor/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub(super) mod extract_style_from_expression;
88
pub(super) mod extract_style_from_jsx;
99
pub(super) mod extract_style_from_member_expression;
1010
pub(super) mod extract_style_from_styled;
11+
pub(super) mod extract_style_from_stylex;
1112

1213
/**
1314
* type

0 commit comments

Comments
 (0)