diff --git a/.changeset/itchy-dolphins-talk.md b/.changeset/itchy-dolphins-talk.md new file mode 100644 index 00000000..5e988d4e --- /dev/null +++ b/.changeset/itchy-dolphins-talk.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/react": patch +--- + +Support optional args in css diff --git a/.changeset/soft-islands-compete.md b/.changeset/soft-islands-compete.md new file mode 100644 index 00000000..6d11336c --- /dev/null +++ b/.changeset/soft-islands-compete.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Support destructing, Support to direct selection of object with theme, Support for template literal on as prop diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 9a65e5bb..8d35044b 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -181,6 +181,92 @@ mod tests { ) .unwrap()); } + + #[test] + #[serial] + fn convert_tag() { + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + // assert_debug_snapshot!(extract( + // "test.tsx", + // r#"import {Box} from '@devup-ui/core' + // + // "#, + // ExtractOption { + // package: "@devup-ui/core".to_string(), + // css_file: None + // } + // ) + // .unwrap()); + // assert_debug_snapshot!(extract( + // "test.tsx", + // r#"import {Box} from '@devup-ui/core' + // + // "#, + // ExtractOption { + // package: "@devup-ui/core".to_string(), + // css_file: None + // } + // ) + // .unwrap()); + // + // assert_debug_snapshot!(extract( + // "test.tsx", + // r#"import {Box} from '@devup-ui/core' + // + // "#, + // ExtractOption { + // package: "@devup-ui/core".to_string(), + // css_file: None + // } + // ) + // .unwrap()); + // assert_debug_snapshot!(extract( + // "test.tsx", + // r#"import {Box} from '@devup-ui/core' + // + // "#, + // ExtractOption { + // package: "@devup-ui/core".to_string(), + // css_file: None + // } + // ) + // .unwrap()); + } #[test] #[serial] fn extract_style_props() { @@ -837,6 +923,19 @@ mod tests { } ) .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import { css } from "@devup-ui/core"; +
; + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); } #[test] @@ -854,6 +953,32 @@ mod tests { } ) .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); } #[test] @@ -1260,6 +1385,33 @@ export { } ) .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Center} from '@devup-ui/core' +
+
+ "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {Flex} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); } #[test] @@ -1347,7 +1499,7 @@ PROCESS_DATA.map(({ id, title, content }, idx) => ( ) .unwrap()); } - + #[test] #[serial] fn avoid_same_name_component() { @@ -1366,4 +1518,24 @@ import {Button} from '@devup/ui' ) .unwrap()); } + + #[test] + #[serial] + fn css_props_destructuring_assignment() { + reset_class_map(); + assert_debug_snapshot!(extract( + "test.js", + r#"import {css} from '@devup-ui/core' +
+ "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap()); + } } diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index dcdf2a5f..fbe8fdde 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -14,7 +14,7 @@ use oxc_span::SPAN; pub fn modify_prop_object<'a>( ast_builder: &AstBuilder<'a>, props: &mut oxc_allocator::Vec>, - styles: Vec>, + styles: &[ExtractStyleProp<'a>], ) { let mut class_name_prop = None; let mut style_prop = None; @@ -38,7 +38,7 @@ pub fn modify_prop_object<'a>( } // should modify class name prop - if let Some(ex) = gen_class_names(ast_builder, &styles) { + if let Some(ex) = gen_class_names(ast_builder, styles) { if let Some(pr) = if let Some(class_name_prop) = class_name_prop { merge_expression_for_class_name( ast_builder, @@ -76,7 +76,7 @@ pub fn modify_prop_object<'a>( } // should modify style prop - if let Some(mut ex) = gen_styles(ast_builder, &styles) { + if let Some(mut ex) = gen_styles(ast_builder, styles) { props.push(if let Some(style_prop) = style_prop { ObjectPropertyKind::ObjectProperty(ast_builder.alloc_object_property( SPAN, @@ -117,7 +117,7 @@ pub fn modify_prop_object<'a>( pub fn modify_props<'a>( ast_builder: &AstBuilder<'a>, props: &mut oxc_allocator::Vec>, - styles: Vec>, + styles: &[ExtractStyleProp<'a>], ) { let mut class_name_prop = None; let mut style_prop = None; @@ -141,7 +141,7 @@ pub fn modify_props<'a>( } // should modify class name prop - if let Some(ex) = gen_class_names(ast_builder, &styles) { + if let Some(ex) = gen_class_names(ast_builder, styles) { let mut attr = if let Some(class_name_prop) = class_name_prop { class_name_prop } else { @@ -160,7 +160,7 @@ pub fn modify_props<'a>( } // should modify style prop - if let Some(ex) = gen_styles(ast_builder, &styles) { + if let Some(ex) = gen_styles(ast_builder, styles) { let mut attr = if let Some(style_prop) = style_prop { style_prop } else { diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap new file mode 100644 index 00000000..6169903f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [], + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag-3.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag-3.snap new file mode 100644 index 00000000..f9d38b29 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-3.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [], + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag.snap new file mode 100644 index 00000000..397a26ab --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [], + code: ";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__css_props_destructuring_assignment.snap b/libs/extractor/src/snapshots/extractor__tests__css_props_destructuring_assignment.snap new file mode 100644 index 00000000..6bd09285 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__css_props_destructuring_assignment.snap @@ -0,0 +1,36 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {css} from '@devup-ui/core'\n
\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "padding", + value: "4px", + level: 0, + selector: None, + basic: false, + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: None, + basic: false, + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: None, + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\nimport { css } from \"@devup-ui/core\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-5.snap index 5b0cd8b0..9269ed82 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-5.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-5.snap @@ -6,8 +6,8 @@ ExtractOutput { styles: [ Static( ExtractStaticStyle { - property: "color", - value: "blue", + property: "background", + value: "red", level: 0, selector: Some( Postfix( @@ -19,8 +19,8 @@ ExtractOutput { ), Static( ExtractStaticStyle { - property: "background", - value: "red", + property: "color", + value: "blue", level: 0, selector: Some( Postfix( diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-6.snap new file mode 100644 index 00000000..38d9f610 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_class_name_props-6.snap @@ -0,0 +1,27 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import { css } from \"@devup-ui/core\";\n
;\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: None, + basic: false, + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: None, + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\nimport { css } from \"@devup-ui/core\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-2.snap new file mode 100644 index 00000000..8187be9c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-2.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "color", + value: "$nice", + level: 0, + selector: None, + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-3.snap new file mode 100644 index 00000000..ea9512f0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_static_css_with_theme-3.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "color", + value: "$nice", + level: 0, + selector: None, + basic: false, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_select-12.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-12.snap new file mode 100644 index 00000000..278f0345 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-12.snap @@ -0,0 +1,44 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Center} from '@devup-ui/core'\n
\n
\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + basic: true, + }, + ), + Static( + ExtractStaticStyle { + property: "justifyContent", + value: "center", + level: 0, + selector: None, + basic: true, + }, + ), + Static( + ExtractStaticStyle { + property: "alignItems", + value: "center", + level: 0, + selector: None, + basic: true, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "background", + level: 0, + identifier: "SOME_VAR[idx]", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_select-13.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-13.snap new file mode 100644 index 00000000..15c6367a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_select-13.snap @@ -0,0 +1,26 @@ +--- +source: libs/extractor/src/lib.rs +expression: "extract(\"test.js\",\nr#\"import {Flex} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap()" +--- +ExtractOutput { + styles: [ + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + basic: true, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "background", + level: 0, + identifier: "({\n\ta: \"var(--red)\",\n\tb: \"var(--blue)\"\n})[idx]", + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/style_extractor.rs b/libs/extractor/src/style_extractor.rs index acf37f9a..9cbfeb9f 100644 --- a/libs/extractor/src/style_extractor.rs +++ b/libs/extractor/src/style_extractor.rs @@ -5,6 +5,7 @@ use oxc_ast::ast::{Expression, JSXAttributeValue, ObjectPropertyKind, PropertyKe use crate::extract_style::ExtractStyleValue::{Dynamic, Static, Typography}; use crate::extract_style::{ExtractDynamicStyle, ExtractStaticStyle}; +use crate::style_extractor::ExtractResult::ChangeTag; use oxc_ast::AstBuilder; use oxc_span::SPAN; use oxc_syntax::operator::{BinaryOperator, LogicalOperator}; @@ -27,13 +28,9 @@ pub enum ExtractResult<'a> { // attribute will be removed and the value will be extracted ExtractStyle(Vec>), // attribute will be removed and the tag will be changed - ChangeTag(String), -} + ChangeTag(Expression<'a>), -impl<'a> From> for Vec> { - fn from(v: ExtractResult<'a>) -> Self { - vec![v] - } + ExtractStyleWithChangeTag(Vec>, Expression<'a>), } pub fn extract_style_from_jsx_attr<'a>( @@ -75,16 +72,170 @@ pub fn extract_style_from_expression<'a>( selector: Option<&str>, ) -> ExtractResult<'a> { let mut typo = false; + + if name.is_none() && selector.is_none() { + return match expression { + Expression::ObjectExpression(ref mut obj) => { + let mut props_styles = vec![]; + let mut tag = None; + for idx in (0..obj.properties.len()).rev() { + let mut prop = obj.properties.remove(idx); + let mut rm = false; + match &mut prop { + ObjectPropertyKind::ObjectProperty(prop) => { + if let PropertyKey::StaticIdentifier(ident) = &prop.key { + let name = ident.name.to_string(); + rm = match extract_style_from_expression( + ast_builder, + Some(&name), + &mut prop.value, + 0, + None, + ) { + ExtractResult::Maintain => false, + ExtractResult::Remove => true, + ExtractResult::ExtractStyle(mut styles) => { + props_styles.append(&mut styles); + true + } + ExtractResult::ChangeTag(t) => { + tag = Some(t); + true + } + ExtractResult::ExtractStyleWithChangeTag(mut styles, t) => { + props_styles.append(&mut styles); + tag = Some(t); + true + } + } + } + } + ObjectPropertyKind::SpreadProperty(prop) => { + rm = match extract_style_from_expression( + ast_builder, + None, + &mut prop.argument, + 0, + None, + ) { + ExtractResult::Maintain => false, + ExtractResult::Remove => true, + ExtractResult::ExtractStyle(mut styles) => { + props_styles.append(&mut styles); + true + } + ExtractResult::ChangeTag(t) => { + tag = Some(t); + true + } + ExtractResult::ExtractStyleWithChangeTag(mut styles, t) => { + props_styles.append(&mut styles); + tag = Some(t); + true + } + }; + } + } + if !rm { + obj.properties.insert(idx, prop); + } + } + if props_styles.is_empty() { + ExtractResult::Maintain + } else { + let ret = if let Some(tag) = tag { + ExtractResult::ExtractStyleWithChangeTag(props_styles, tag) + } else { + ExtractResult::ExtractStyle(props_styles) + }; + ret + } + } + Expression::ConditionalExpression(ref mut conditional) => { + let mut consequent = None; + let mut alternate = None; + if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( + ast_builder, + None, + &mut conditional.consequent, + level, + None, + ) { + consequent = Some(Box::new(styles.remove(0))); + } + if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( + ast_builder, + None, + &mut conditional.alternate, + level, + selector, + ) { + alternate = Some(Box::new(styles.remove(0))); + } + ExtractResult::ExtractStyle(vec![ExtractStyleProp::Conditional { + condition: conditional.test.clone_in(ast_builder.allocator), + consequent, + alternate, + }]) + } + Expression::ParenthesizedExpression(parenthesized) => extract_style_from_expression( + ast_builder, + None, + &mut parenthesized.expression, + level, + None, + ), + _ => ExtractResult::Maintain, + }; + } + if let Some(name) = name { if is_special_property(name) { return ExtractResult::Maintain; } if name == "as" { - if let Expression::StringLiteral(ident) = &expression { - return ExtractResult::ChangeTag(ident.value.to_string()); - } - return ExtractResult::Remove; + return ChangeTag(expression.clone_in(ast_builder.allocator)); + + // return match expression { + // Expression::StringLiteral(ident) => ExtractResult::ChangeTag( + // Expression::StringLiteral(ident.clone_in(ast_builder.allocator)), + // ), + // Expression::TemplateLiteral(tmp) => { + // if tmp.quasis.len() == 1 { + // ExtractResult::ChangeTag(Expression::TemplateLiteral( + // tmp.clone_in(ast_builder.allocator), + // )) + // } else { + // ExtractResult::Remove + // } + // } + // Expression::ConditionalExpression(ref mut conditional) => { + // let mut consequent = None; + // let mut alternate = None; + // if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( + // ast_builder, + // None, + // &mut conditional.consequent, + // level, + // None, + // ) { + // consequent = Some(Box::new(styles.remove(0))); + // } + // if let ExtractResult::ExtractStyle(mut styles) = extract_style_from_expression( + // ast_builder, + // None, + // &mut conditional.alternate, + // level, + // selector, + // ) { + // alternate = Some(Box::new(styles.remove(0))); + // } + // ExtractResult::ChangeTag( + // ) + // } + // _ => ExtractResult::Remove, + // }; } if let Some(selector) = name.strip_prefix("_") { @@ -161,57 +312,92 @@ pub fn extract_style_from_expression<'a>( } } } - Expression::ObjectExpression(obj) => match mem_expression { - Expression::StringLiteral(str) => { - let key = str.value.as_str(); - for p in obj.properties.iter() { - match p { - ObjectPropertyKind::ObjectProperty(o) => { - if let PropertyKey::StaticIdentifier(ident) = &o.key { - if ident.name == key { - return extract_style_from_expression( - ast_builder, - name, - &mut o.value.clone_in(ast_builder.allocator), - level, - selector, - ); + Expression::ObjectExpression(obj) => { + for p in obj.properties.iter_mut() { + if let ObjectPropertyKind::ObjectProperty(ref mut o) = p { + if let PropertyKey::StaticIdentifier(_) = o.key { + if let Expression::StringLiteral(str) = &mut o.value { + if let Some(rest) = str.value.strip_prefix("$") { + str.value = ast_builder.atom(&format!("var(--{})", rest)); + } + } else if let Expression::TemplateLiteral(tmp) = &mut o.value { + if tmp.quasis.len() == 1 { + if let Some(rest) = + tmp.quasis[0].value.raw.strip_prefix("$") + { + tmp.quasis[0].value.raw = + ast_builder.atom(&format!("var(--{})", rest)); } } } - ObjectPropertyKind::SpreadProperty(_) => { - if let Some(name) = name { - return ExtractResult::ExtractStyle(vec![ - ExtractStyleProp::Static(Dynamic( - ExtractDynamicStyle::new( + } + } + } + match mem_expression { + Expression::StringLiteral(str) => { + let key = str.value.as_str(); + for p in obj.properties.iter() { + match p { + ObjectPropertyKind::ObjectProperty(o) => { + if let PropertyKey::StaticIdentifier(ident) = &o.key { + if ident.name == key { + return extract_style_from_expression( + ast_builder, name, + &mut o.value.clone_in(ast_builder.allocator), level, - expression_to_code(expression).as_str(), - selector.map(|s| s.into()), - ), - )), - ]); + selector, + ); + } + } + } + ObjectPropertyKind::SpreadProperty(_) => { + if let Some(name) = name { + return ExtractResult::ExtractStyle(vec![ + ExtractStyleProp::Static(Dynamic( + ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector.map(|s| s.into()), + ), + )), + ]); + } } } } + ExtractResult::Remove } - ExtractResult::Remove - } - Expression::Identifier(_) => { - if let Some(name) = name { - return ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static( - Dynamic(ExtractDynamicStyle::new( - name, - level, - expression_to_code(expression).as_str(), - selector.map(|s| s.into()), - )), - )]); + Expression::Identifier(_) => { + if let Some(name) = name { + return ExtractResult::ExtractStyle(vec![ + ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector.map(|s| s.into()), + ))), + ]); + } + ExtractResult::Maintain } - ExtractResult::Maintain + _ => ExtractResult::Maintain, } - _ => ExtractResult::Maintain, - }, + } + Expression::Identifier(_) => { + if let Some(name) = name { + return ExtractResult::ExtractStyle(vec![ExtractStyleProp::Static( + Dynamic(ExtractDynamicStyle::new( + name, + level, + expression_to_code(expression).as_str(), + selector.map(|s| s.into()), + )), + )]); + } + ExtractResult::Maintain + } _ => ExtractResult::Maintain, } } diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 925957bb..3a4f6735 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -1,5 +1,6 @@ use crate::component::ExportVariableKind; -use crate::extract_style::{ExtractCss, ExtractStyleProperty}; +use crate::extract_style::ExtractCss; +use crate::gen_class_name::gen_class_names; use crate::prop_modify_utils::{modify_prop_object, modify_props}; use crate::style_extractor::{ extract_style_from_expression, extract_style_from_jsx_attr, ExtractResult, @@ -12,8 +13,8 @@ use oxc_ast::ast::JSXAttributeItem::Attribute; use oxc_ast::ast::JSXAttributeName::Identifier; use oxc_ast::ast::{ Argument, BindingPatternKind, CallExpression, Expression, ImportDeclaration, - ImportOrExportKind, JSXElement, JSXElementName, ObjectPropertyKind, Program, PropertyKey, - Statement, TaggedTemplateExpression, TemplateElementValue, VariableDeclarator, WithClause, + ImportOrExportKind, JSXElement, JSXElementName, Program, PropertyKey, Statement, + TaggedTemplateExpression, TemplateElementValue, VariableDeclarator, WithClause, }; use oxc_ast::visit::walk_mut::{ walk_call_expression, walk_import_declaration, walk_jsx_element, walk_program, @@ -118,125 +119,76 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { }; if let Some(kind) = element_kind { if it.arguments.len() > 1 { - if let Expression::ObjectExpression(obj) = - it.arguments[1].to_expression_mut() - { - let mut duplicate_set = HashSet::new(); - let mut tag = kind.to_tag().unwrap_or("div").to_string(); - let mut props_styles = vec![]; - for idx in (0..obj.properties.len()).rev() { - let mut prop = obj.properties.remove(idx); - let mut rm = false; - if let ObjectPropertyKind::ObjectProperty(prop) = &mut prop { - if let PropertyKey::StaticIdentifier(ident) = &prop.key { - let name = sort_to_long(&ident.name); - if duplicate_set.contains(name.as_str()) { - continue; - } - duplicate_set.insert(name.clone()); - rm = match extract_style_from_expression( - &self.ast, - Some(&name), - &mut prop.value, - 0, - None, - ) { - ExtractResult::Maintain => false, - ExtractResult::Remove => true, - ExtractResult::ExtractStyle(mut styles) => { - styles.reverse(); - props_styles.append(&mut styles); - true - } - ExtractResult::ChangeTag(t) => { - tag = t; - true - } - } - } - } - if !rm { - obj.properties.insert(idx, prop); - } + let mut tag = Expression::StringLiteral(self.ast.alloc_string_literal( + SPAN, + kind.to_tag().unwrap_or("div"), + None, + )); + let mut props_styles = vec![]; + match extract_style_from_expression( + &self.ast, + None, + it.arguments[1].to_expression_mut(), + 0, + None, + ) { + ExtractResult::ExtractStyle(mut styles) => { + props_styles.append(&mut styles); } - - for ex in kind.extract().into_iter().rev() { - props_styles.push(ExtractStyleProp::Static(ex)); + ExtractResult::ExtractStyleWithChangeTag(mut styles, t) => { + tag = t; + props_styles.append(&mut styles); } - - for style in props_styles.iter().rev() { - self.styles.append(&mut style.extract()); + ExtractResult::Maintain => {} + ExtractResult::Remove => {} + ExtractResult::ChangeTag(t) => { + tag = t; } + } - modify_prop_object(&self.ast, &mut obj.properties, props_styles); - it.arguments[0] = - Argument::StringLiteral(self.ast.alloc_string_literal( - SPAN, - self.ast.atom(tag.as_str()), - None, - )); + for ex in kind.extract().into_iter().rev() { + props_styles.push(ExtractStyleProp::Static(ex)); + } + + for style in props_styles.iter().rev() { + self.styles.append(&mut style.extract()); + } + if let Expression::ObjectExpression(obj) = + it.arguments[1].to_expression_mut() + { + modify_prop_object(&self.ast, &mut obj.properties, &props_styles); } + + it.arguments[0] = Argument::from(tag); } } } } if let Expression::Identifier(ident) = &it.callee { if self.css_imports.contains_key(ident.name.as_str()) && it.arguments.len() == 1 { - if let Argument::ObjectExpression(ref mut obj) = it.arguments[0] { - let mut props_styles = vec![]; - for idx in (0..obj.properties.len()).rev() { - let mut prop = obj.properties.remove(idx); - let mut rm = false; - if let ObjectPropertyKind::ObjectProperty(prop) = &mut prop { - if let PropertyKey::StaticIdentifier(ident) = &prop.key { - let name = ident.name.to_string(); - rm = match extract_style_from_expression( - &self.ast, - Some(&name), - &mut prop.value, - 0, - None, - ) { - ExtractResult::Maintain => false, - ExtractResult::Remove => true, - ExtractResult::ExtractStyle(mut styles) => { - styles.reverse(); - props_styles.append(&mut styles); - true - } - ExtractResult::ChangeTag(_) => true, - } - } - } - if !rm { - obj.properties.insert(idx, prop); + match extract_style_from_expression( + &self.ast, + None, + it.arguments[0].to_expression_mut(), + 0, + None, + ) { + ExtractResult::ExtractStyle(styles) + | ExtractResult::ExtractStyleWithChangeTag(styles, _) => { + let class_name = gen_class_names(&self.ast, &styles); + let mut styles = styles + .into_iter() + .flat_map(|ex| ex.extract()) + .collect::>(); + + self.styles.append(&mut styles); + if let Some(cls) = class_name { + it.arguments[0] = Argument::from(cls); + } else { + it.arguments.pop(); } } - let mut styles = props_styles - .into_iter() - .flat_map(|ex| ex.extract()) - .collect::>(); - let class_name = styles - .iter() - .filter_map(|ex| match ex { - ExtractStyleValue::Static(css) => { - if let StyleProperty::ClassName(cls) = css.extract() { - Some(cls.to_string()) - } else { - None - } - } - _ => None, - }) - .collect::>() - .join(" "); - - self.styles.append(&mut styles); - it.arguments[0] = Argument::StringLiteral(self.ast.alloc_string_literal( - SPAN, - self.ast.atom(&class_name), - None, - )); + _ => {} } } } @@ -283,10 +235,13 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { let opening_element = &mut elem.opening_element; let component_name = &opening_element.name.to_string(); - println!("component_name: {} {:?}", component_name, self.imports); if let Some(kind) = self.imports.get(component_name) { let attrs = &mut opening_element.attributes; - let mut tag_name = kind.to_tag().unwrap_or("div").to_string(); + let mut tag_name = Expression::StringLiteral(self.ast.alloc_string_literal( + SPAN, + kind.to_tag().unwrap_or("div"), + None, + )); let mut props_styles = vec![]; // extract ExtractStyleProp and remain style and class name, just extract @@ -314,6 +269,12 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { tag_name = tag; true } + ExtractResult::ExtractStyleWithChangeTag(mut styles, tag) => { + styles.reverse(); + props_styles.append(&mut styles); + tag_name = tag; + true + } } } } @@ -330,13 +291,31 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { self.styles.append(&mut style.extract()); } // modify!! - modify_props(&self.ast, attrs, props_styles); + modify_props(&self.ast, attrs, &props_styles); + + match tag_name { + Expression::StringLiteral(str) => { + // change tag name + let ident = JSXElementName::Identifier( + self.ast.alloc_jsx_identifier(SPAN, str.value.as_str()), + ); + opening_element.name = ident.clone_in(self.ast.allocator); + if let Some(el) = &mut elem.closing_element { + el.name = ident; + } + } + Expression::TemplateLiteral(literal) => { + let ident = JSXElementName::Identifier( + self.ast + .alloc_jsx_identifier(SPAN, literal.quasis[0].value.raw.as_str()), + ); + opening_element.name = ident.clone_in(self.ast.allocator); + if let Some(el) = &mut elem.closing_element { + el.name = ident; + } + } - // change tag name - let ident = JSXElementName::Identifier(self.ast.alloc_jsx_identifier(SPAN, tag_name)); - opening_element.name = ident.clone_in(self.ast.allocator); - if let Some(el) = &mut elem.closing_element { - el.name = ident; + _ => {} } } } diff --git a/packages/react/src/utils/__tests__/index.test.ts b/packages/react/src/utils/__tests__/index.test.ts index 4bb18d54..5aa0c9bf 100644 --- a/packages/react/src/utils/__tests__/index.test.ts +++ b/packages/react/src/utils/__tests__/index.test.ts @@ -4,5 +4,6 @@ describe('css', () => { it('should return className', async () => { expect(css`virtual-css`).toEqual('virtual-css') expect(css('class name' as any)).toEqual('class name') + expect(css()).toEqual('') }) }) diff --git a/packages/react/src/utils/css.ts b/packages/react/src/utils/css.ts index d34cc92d..3a819358 100644 --- a/packages/react/src/utils/css.ts +++ b/packages/react/src/utils/css.ts @@ -3,10 +3,12 @@ import type { DevupSelectorProps } from '../types/props/selector' export function css(props: DevupCommonProps & DevupSelectorProps): string export function css(strings: TemplateStringsArray): string +export function css(): string export function css( - strings: TemplateStringsArray | (DevupCommonProps & DevupSelectorProps), + strings?: TemplateStringsArray | (DevupCommonProps & DevupSelectorProps), ): string { + if (typeof strings === 'undefined') return '' if (Array.isArray(strings)) { return strings.join('') } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d6cc14b..87970174 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,13 +139,13 @@ importers: version: 19.0.3(@types/react@19.0.7) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.3.4(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) typescript: specifier: ^5 version: 5.7.3 vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) apps/vite-lib: dependencies: @@ -157,7 +157,7 @@ importers: version: 19.0.0 vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) devDependencies: '@devup-ui/vite-plugin': specifier: workspace:* @@ -170,13 +170,13 @@ importers: version: 19.0.7 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.3.4(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) typescript: specifier: ^5 version: 5.7.3 vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) benchmark/next-chakra-ui: dependencies: @@ -293,10 +293,10 @@ importers: version: 5.7.3 vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) vitest: specifier: ^3.0.3 version: 3.0.3(@types/node@22.10.7)(terser@5.37.0) @@ -318,10 +318,10 @@ importers: version: 5.7.3 vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) vitest: specifier: ^3.0.3 version: 3.0.3(@types/node@22.10.7)(terser@5.37.0) @@ -333,14 +333,14 @@ importers: version: link:../../bindings/devup-ui-wasm vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) devDependencies: typescript: specifier: ^5.7.3 version: 5.7.3 vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) packages/webpack-plugin: dependencies: @@ -356,10 +356,10 @@ importers: version: 5.7.3 vite: specifier: ^6 - version: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + version: 6.0.11(@types/node@22.10.7)(terser@5.37.0) vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + version: 4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) vitest: specifier: ^3.0.3 version: 3.0.3(@types/node@22.10.7)(terser@5.37.0) @@ -4450,8 +4450,8 @@ packages: vite: optional: true - vite@6.0.10: - resolution: {integrity: sha512-MEszunEcMo6pFsfXN1GhCFQqnE25tWRH0MA4f0Q7uanACi4y1Us+ZGpTMnITwCTnYzB2b9cpmnelTlxgTBmaBA==} + vite@6.0.11: + resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -6469,14 +6469,14 @@ snapshots: '@ungap/structured-clone@1.2.1': {} - '@vitejs/plugin-react@4.3.4(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0))': + '@vitejs/plugin-react@4.3.4(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + vite: 6.0.11(@types/node@22.10.7)(terser@5.37.0) transitivePeerDependencies: - supports-color @@ -6505,13 +6505,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.3(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0))': + '@vitest/mocker@3.0.3(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0))': dependencies: '@vitest/spy': 3.0.3 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + vite: 6.0.11(@types/node@22.10.7)(terser@5.37.0) '@vitest/pretty-format@3.0.3': dependencies: @@ -9807,7 +9807,7 @@ snapshots: debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.2 - vite: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + vite: 6.0.11(@types/node@22.10.7)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - jiti @@ -9822,7 +9822,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)): + vite-plugin-dts@4.5.0(@types/node@22.10.7)(rollup@4.31.0)(typescript@5.7.3)(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)): dependencies: '@microsoft/api-extractor': 7.49.1(@types/node@22.10.7) '@rollup/pluginutils': 5.1.4(rollup@4.31.0) @@ -9835,13 +9835,13 @@ snapshots: magic-string: 0.30.17 typescript: 5.7.3 optionalDependencies: - vite: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + vite: 6.0.11(@types/node@22.10.7)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite@6.0.10(@types/node@22.10.7)(terser@5.37.0): + vite@6.0.11(@types/node@22.10.7)(terser@5.37.0): dependencies: esbuild: 0.24.2 postcss: 8.5.1 @@ -9854,7 +9854,7 @@ snapshots: vitest@3.0.3(@types/node@22.10.7)(terser@5.37.0): dependencies: '@vitest/expect': 3.0.3 - '@vitest/mocker': 3.0.3(vite@6.0.10(@types/node@22.10.7)(terser@5.37.0)) + '@vitest/mocker': 3.0.3(vite@6.0.11(@types/node@22.10.7)(terser@5.37.0)) '@vitest/pretty-format': 3.0.3 '@vitest/runner': 3.0.3 '@vitest/snapshot': 3.0.3 @@ -9870,7 +9870,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.0.10(@types/node@22.10.7)(terser@5.37.0) + vite: 6.0.11(@types/node@22.10.7)(terser@5.37.0) vite-node: 3.0.3(@types/node@22.10.7)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: