Skip to content

Commit 2d1f8db

Browse files
committed
Implement styled
1 parent 265c083 commit 2d1f8db

File tree

4 files changed

+601
-0
lines changed

4 files changed

+601
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use crate::{
2+
ExtractStyleProp, css_utils::css_to_style,
3+
extract_style::extract_style_value::ExtractStyleValue, extractor::ExtractResult,
4+
extractor::extract_style_from_expression::extract_style_from_expression,
5+
gen_class_name::gen_class_names,
6+
};
7+
use oxc_allocator::CloneIn;
8+
use oxc_ast::{
9+
AstBuilder,
10+
ast::{Argument, Expression},
11+
};
12+
use oxc_span::SPAN;
13+
14+
/// Extract styles from styled function calls
15+
/// Handles patterns like:
16+
/// - styled.div`css`
17+
/// - styled("div")`css`
18+
/// - styled("div")({ bg: "red" })
19+
/// - styled.div({ bg: "red" })
20+
/// - styled(Component)({ bg: "red" })
21+
pub fn extract_style_from_styled<'a>(
22+
ast_builder: &AstBuilder<'a>,
23+
expression: &mut Expression<'a>,
24+
styled_name: &str,
25+
split_filename: Option<&str>,
26+
) -> (ExtractResult<'a>, Expression<'a>) {
27+
match expression {
28+
// Case 1: styled.div`css` or styled("div")`css`
29+
Expression::TaggedTemplateExpression(tag) => {
30+
// Check if tag is styled.div or styled(...)
31+
let (tag_name, is_member) = match &tag.tag {
32+
Expression::StaticMemberExpression(member) => {
33+
if let Expression::Identifier(ident) = &member.object {
34+
if ident.name.as_str() == styled_name {
35+
(Some(member.property.name.to_string()), true)
36+
} else {
37+
(None, false)
38+
}
39+
} else {
40+
(None, false)
41+
}
42+
}
43+
Expression::CallExpression(call) => {
44+
if let Expression::Identifier(ident) = &call.callee {
45+
if ident.name.as_str() == styled_name && call.arguments.len() == 1 {
46+
// styled("div") or styled(Component)
47+
if let Argument::StringLiteral(lit) = &call.arguments[0] {
48+
(Some(lit.value.to_string()), false)
49+
} else {
50+
// Component reference - we'll handle this later
51+
(None, false)
52+
}
53+
} else {
54+
(None, false)
55+
}
56+
} else {
57+
(None, false)
58+
}
59+
}
60+
_ => (None, false),
61+
};
62+
63+
if tag_name.is_some() || is_member {
64+
// Extract CSS from template literal
65+
let css_str = tag
66+
.quasi
67+
.quasis
68+
.iter()
69+
.map(|quasi| quasi.value.raw.to_string())
70+
.collect::<String>();
71+
72+
let styles = css_to_style(&css_str, 0, &None);
73+
let mut props_styles: Vec<ExtractStyleProp<'_>> = styles
74+
.iter()
75+
.map(|ex| ExtractStyleProp::Static(ExtractStyleValue::Static(ex.clone())))
76+
.collect();
77+
78+
let class_name =
79+
gen_class_names(ast_builder, &mut props_styles, None, split_filename);
80+
81+
let result = ExtractResult {
82+
styles: props_styles,
83+
tag: tag_name.map(|name| {
84+
ast_builder.expression_string_literal(SPAN, ast_builder.atom(&name), None)
85+
}),
86+
style_order: None,
87+
style_vars: None,
88+
props: None,
89+
};
90+
91+
let new_expr = if let Some(cls) = class_name {
92+
cls
93+
} else {
94+
ast_builder.expression_string_literal(SPAN, ast_builder.atom(""), None)
95+
};
96+
97+
return (result, new_expr);
98+
}
99+
}
100+
// Case 2: styled.div({ bg: "red" }) or styled("div")({ bg: "red" })
101+
Expression::CallExpression(call) => {
102+
// Check if this is a call to styled.div or styled("div")
103+
let (tag_name, is_member) = match &call.callee {
104+
Expression::StaticMemberExpression(member) => {
105+
if let Expression::Identifier(ident) = &member.object {
106+
if ident.name.as_str() == styled_name {
107+
(Some(member.property.name.to_string()), true)
108+
} else {
109+
(None, false)
110+
}
111+
} else {
112+
(None, false)
113+
}
114+
}
115+
Expression::CallExpression(inner_call) => {
116+
if let Expression::Identifier(ident) = &inner_call.callee {
117+
if ident.name.as_str() == styled_name && inner_call.arguments.len() == 1 {
118+
// styled("div") or styled(Component)
119+
if let Argument::StringLiteral(lit) = &inner_call.arguments[0] {
120+
(Some(lit.value.to_string()), false)
121+
} else {
122+
// Component reference
123+
(None, false)
124+
}
125+
} else {
126+
(None, false)
127+
}
128+
} else {
129+
(None, false)
130+
}
131+
}
132+
_ => (None, false),
133+
};
134+
135+
if (tag_name.is_some() || is_member) && call.arguments.len() == 1 {
136+
// Extract styles from object expression
137+
let ExtractResult {
138+
mut styles,
139+
style_order,
140+
style_vars,
141+
props,
142+
..
143+
} = extract_style_from_expression(
144+
ast_builder,
145+
None,
146+
if let Argument::SpreadElement(spread) = &mut call.arguments[0] {
147+
&mut spread.argument
148+
} else {
149+
call.arguments[0].to_expression_mut()
150+
},
151+
0,
152+
&None,
153+
);
154+
155+
let class_name =
156+
gen_class_names(ast_builder, &mut styles, style_order, split_filename);
157+
158+
let result = ExtractResult {
159+
styles,
160+
tag: tag_name.map(|name| {
161+
ast_builder.expression_string_literal(SPAN, ast_builder.atom(&name), None)
162+
}),
163+
style_order,
164+
style_vars,
165+
props,
166+
};
167+
168+
let new_expr = if let Some(cls) = class_name {
169+
cls
170+
} else {
171+
ast_builder.expression_string_literal(SPAN, ast_builder.atom(""), None)
172+
};
173+
174+
return (result, new_expr);
175+
}
176+
}
177+
_ => {}
178+
}
179+
180+
// Default: no extraction
181+
(
182+
ExtractResult::default(),
183+
expression.clone_in(ast_builder.allocator),
184+
)
185+
}

libs/extractor/src/extractor/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(super) mod extract_keyframes_from_expression;
77
pub(super) mod extract_style_from_expression;
88
pub(super) mod extract_style_from_jsx;
99
pub(super) mod extract_style_from_member_expression;
10+
pub(super) mod extract_style_from_styled;
1011

1112
/**
1213
* type

0 commit comments

Comments
 (0)