From 2e1c60d814b9b385c61075878466fe05b0c425b3 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 8 Jul 2025 10:03:09 +0900 Subject: [PATCH 1/3] Fix className logic --- .changeset/chilly-mice-leave.md | 5 + libs/extractor/src/lib.rs | 34 ++ libs/extractor/src/prop_modify_utils.rs | 81 ++- ...tor__tests__ignore_special_props2.snap.new | 575 ------------------ .../extractor__tests__rest_props-2.snap | 57 ++ ...r__tests__support_transpile_cjs_2.snap.new | 9 - 6 files changed, 132 insertions(+), 629 deletions(-) create mode 100644 .changeset/chilly-mice-leave.md delete mode 100644 libs/extractor/src/snapshots/extractor__tests__ignore_special_props2.snap.new create mode 100644 libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs_2.snap.new diff --git a/.changeset/chilly-mice-leave.md b/.changeset/chilly-mice-leave.md new file mode 100644 index 00000000..56a833f0 --- /dev/null +++ b/.changeset/chilly-mice-leave.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Refactor className logic diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 02764c56..99971d7b 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -2285,6 +2285,40 @@ e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) ) .unwrap() )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.jsx", + r#"import { VStack } from '@devup-ui/core' + +export default function Card({ + children, + className, + ...props +}) { + return ( + + {children} + + ) +} + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); } #[test] diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index 16290ea8..f1189a94 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -245,76 +245,67 @@ fn merge_string_expressions<'a>( let mut string_literals: std::vec::Vec = vec![]; let mut other_expressions = vec![]; let mut prev_str = String::new(); - for (idx, ex) in expressions.iter().enumerate() { + for ex in expressions.iter() { match ex { Expression::StringLiteral(literal) => { - prev_str.push_str( - format!( - "{}{}", - if prev_str.trim().is_empty() && other_expressions.is_empty() { - "" - } else { - " " - }, - literal.value.trim() - ) - .as_str(), + let target_prev = prev_str.trim(); + let target = literal.value.trim(); + prev_str = format!( + "{}{}{}", + target_prev, + if target_prev.is_empty() { "" } else { " " }, + target ); } Expression::TemplateLiteral(template) => { for (idx, q) in template.quasis.iter().enumerate() { - if !prev_str.is_empty() { + let target_prev = prev_str.trim(); + let target = q.value.raw.trim(); + if idx < template.quasis.len() - 1 { string_literals.push(format!( - "{}{}{}{}", - prev_str.trim(), - if !prev_str.trim().is_empty() { " " } else { "" }, - q.value.raw.trim(), - if idx == template.quasis.len() - 1 { - "" - } else { + "{}{}{}{}{}", + if !other_expressions.is_empty() || idx > 0 { " " - } - )); - prev_str = String::new(); - } else if q.tail { - prev_str = q.value.raw.trim().to_string(); - } else { - string_literals.push(format!( - "{}{}{}", - if idx == 0 - && other_expressions.is_empty() - && string_literals.is_empty() - { - "" } else { - " " - }, - q.value.raw.trim(), - if q.value.raw.trim().is_empty() || !q.value.raw.ends_with(' ') { "" - } else { + }, + target_prev, + if !target_prev.is_empty() { " " } else { "" }, + target, + if !target.is_empty() && !target.ends_with("typo-") { " " + } else { + "" } )); - prev_str = String::new(); + } else { + prev_str = q.value.raw.trim().to_string(); } } other_expressions.extend(template.expressions.clone_in(ast_builder.allocator)); } ex => { + let target_prev = prev_str.trim(); string_literals.push(format!( - "{}{}", - prev_str.trim(), - if idx > 0 { " " } else { "" } + "{}{}{}", + if !other_expressions.is_empty() { + " " + } else { + "" + }, + target_prev, + if !target_prev.is_empty() { " " } else { "" } )); other_expressions.push(ex.clone_in(ast_builder.allocator)); prev_str = String::new(); } } } - if !prev_str.is_empty() { - string_literals.push(prev_str.trim_end().to_string()); - } + string_literals.push(format!( + "{}{}", + if !prev_str.trim().is_empty() { " " } else { "" }, + prev_str.trim(), + )); if other_expressions.is_empty() { return Some(ast_builder.expression_string_literal( SPAN, diff --git a/libs/extractor/src/snapshots/extractor__tests__ignore_special_props2.snap.new b/libs/extractor/src/snapshots/extractor__tests__ignore_special_props2.snap.new deleted file mode 100644 index ce1976b1..00000000 --- a/libs/extractor/src/snapshots/extractor__tests__ignore_special_props2.snap.new +++ /dev/null @@ -1,575 +0,0 @@ ---- -source: libs/extractor/src/lib.rs -assertion_line: 236 -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box as DevupButton} from '@devup-ui/core'\n \n \n {icon && (\n svg': {\n color: 'inherit',\n },\n }}\n top=\"50%\"\n transform=\"translate(-100%, -50%)\"\n >\n {icon}\n \n )}\n \n {children}\n \n \n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" ---- -ToBTreeSet { - styles: { - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#674DC7) 10%,var(--inputBackground,#2E2E2E) 90%)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#674DC7) 100%,#000 15%)", - level: 0, - selector: Some( - Selector( - "&:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#674DC7) 100%,#000 30%)", - level: 0, - selector: Some( - Selector( - "&:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#8163E1) 10%,#FFF 90%)", - level: 0, - selector: Some( - Selector( - "&:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#8163E1) 100%,#FFF 15%)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#8163E1) 100%,#FFF 30%)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "color-mix(in srgb,var(--primary,#8163E1) 20%,#FFF 80%)", - level: 0, - selector: Some( - Selector( - "&:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "var(--inputBackground,#FFF)", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "var(--primary,#8163E1)", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "background", - value: "var(--primary,#8163E1)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "backgroundColor", - value: "#47474A", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "backgroundColor", - value: "#F0F0F3", - level: 0, - selector: Some( - Selector( - "&:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "border", - value: "1px solid var(--border,#E4E4E4)", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "border", - value: "1px solid var(--primary,#8163E1)", - level: 0, - selector: Some( - Selector( - "&:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "border", - value: "none", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "borderColor", - value: "transparent", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "borderColor", - value: "var(--border,#E4E4E4)", - level: 0, - selector: Some( - Selector( - "&:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "borderColor", - value: "var(--primary,#8163E1)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "borderRadius", - value: "10px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "borderRadius", - value: "8px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "boxSizing", - value: "border-box", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "#000", - level: 0, - selector: Some( - Selector( - "&:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "#373737", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "#D6D7DE", - level: 0, - selector: Some( - Selector( - "&:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "#FFF", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "var(--text,#272727)", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "color", - value: "var(--text,#F6F6F6)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:active", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "cursor", - value: "not-allowed", - level: 0, - selector: Some( - Selector( - "&:disabled", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "cursor", - value: "pointer", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "fontSize", - value: "14px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "fontSize", - value: "15px", - level: 4, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "fontWeight", - value: "700", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "letterSpacing", - value: "-0.02em", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "letterSpacing", - value: "-0.03em", - level: 4, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "outline", - value: "2px solid", - level: 0, - selector: Some( - Selector( - "&:focus-visible", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "outlineColor", - value: "var(--primary,#674DC7)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:hover", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "outlineColor", - value: "var(--primaryFocus,#927CE4)", - level: 0, - selector: Some( - Selector( - ":root[data-theme=dark] &:focus-visible", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "outlineColor", - value: "var(--primaryFocus,#9385D3)", - level: 0, - selector: Some( - Selector( - "&:focus-visible", - ), - ), - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "outlineOffset", - value: "2px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "position", - value: "relative", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "px", - value: "40px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "py", - value: "12px", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - Static( - ExtractStaticStyle { - property: "transition", - value: ".25s", - level: 0, - selector: None, - style_order: Some( - 1, - ), - }, - ), - }, - code: "import \"@devup-ui/core/devup-ui.css\";\n
\n \n {icon &&
svg\": { color: \"inherit\" } }} top=\"50%\" transform=\"translate(-100%, -50%)\">\n {icon}\n
}\n \n {children}\n \n
\n
;\n", -} diff --git a/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap new file mode 100644 index 00000000..cdc817ed --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__rest_props-2.snap @@ -0,0 +1,57 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import { VStack } from '@devup-ui/core'\n\nexport default function Card({\n children,\n className,\n ...props\n}) {\n return (\n \n {children}\n \n )\n}\n\n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "boxShadow", + value: "none", + level: 0, + selector: Some( + Selector( + "&:active", + ), + ), + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "flexDirection", + value: "column", + level: 0, + selector: None, + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "transform", + value: "scale(0.95)", + level: 0, + selector: Some( + Selector( + "&:active", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\nexport default function Card({ children, className,...props }) {\n\treturn
\n {children}\n
;\n}\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs_2.snap.new b/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs_2.snap.new deleted file mode 100644 index f18461f6..00000000 --- a/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs_2.snap.new +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: libs/extractor/src/lib.rs -assertion_line: 2140 -expression: "ToBTreeSet::from(extract(\"test.cjs\",\nr#\"'use client'\n\nimport type { Conditional } from 'src/types/utils'\n\nimport { DevupTheme } from '../types/theme'\n\n/**\n * Initialize the theme, if you can't use the `ThemeScript` component\n * e.g. in vite\n * @param auto - Whether to use the system theme\n * @param theme - The theme to use\n */\nexport function initTheme(\n auto?: boolean,\n theme?: Conditional,\n): void {\n if (theme) {\n document.documentElement.setAttribute('data-theme', theme)\n } else {\n console.log(\n 'hello',\n typeof process.env.DEVUP_UI_DEFAULT_THEME,\n process.env.DEVUP_UI_DEFAULT_THEME,\n localStorage.getItem('__DF_THEME_SELECTED__') ||\n (auto && window.matchMedia('(prefers-color-scheme:dark)').matches\n ? 'dark'\n : (process.env.DEVUP_UI_DEFAULT_THEME ?? 'default')),\n )\n document.documentElement.setAttribute(\n 'data-theme',\n localStorage.getItem('__DF_THEME_SELECTED__') ||\n (auto && window.matchMedia('(prefers-color-scheme:dark)').matches\n ? 'dark'\n : (process.env.DEVUP_UI_DEFAULT_THEME ?? 'default')),\n )\n }\n}\n\"#,\nExtractOption\n{ package: \"@devup-ui/react\".to_string(), css_file: None }).unwrap())" ---- -ToBTreeSet { - styles: {}, - code: "'use client'\n\nimport type { Conditional } from 'src/types/utils'\n\nimport { DevupTheme } from '../types/theme'\n\n/**\n * Initialize the theme, if you can't use the `ThemeScript` component\n * e.g. in vite\n * @param auto - Whether to use the system theme\n * @param theme - The theme to use\n */\nexport function initTheme(\n auto?: boolean,\n theme?: Conditional,\n): void {\n if (theme) {\n document.documentElement.setAttribute('data-theme', theme)\n } else {\n console.log(\n 'hello',\n typeof process.env.DEVUP_UI_DEFAULT_THEME,\n process.env.DEVUP_UI_DEFAULT_THEME,\n localStorage.getItem('__DF_THEME_SELECTED__') ||\n (auto && window.matchMedia('(prefers-color-scheme:dark)').matches\n ? 'dark'\n : (process.env.DEVUP_UI_DEFAULT_THEME ?? 'default')),\n )\n document.documentElement.setAttribute(\n 'data-theme',\n localStorage.getItem('__DF_THEME_SELECTED__') ||\n (auto && window.matchMedia('(prefers-color-scheme:dark)').matches\n ? 'dark'\n : (process.env.DEVUP_UI_DEFAULT_THEME ?? 'default')),\n )\n }\n}\n", -} From 868652ba1d1c731d8c7605eda2c3282f50979536 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 8 Jul 2025 10:13:37 +0900 Subject: [PATCH 2/3] Fix className logic --- libs/extractor/src/prop_modify_utils.rs | 33 ++++++++++++------- ...extract_style_props_with_class_name-9.snap | 2 +- .../extractor__tests__rest_props-2.snap | 2 +- ...actor__tests__support_transpile_mjs-5.snap | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index f1189a94..3660b6ad 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -133,6 +133,8 @@ pub fn modify_props<'a>( } } } + println!("class_name_prop: {:?}", class_name_prop); + println!("style_prop: {:?}", style_prop); if let Some(ex) = get_class_name_expression( ast_builder, &class_name_prop, @@ -179,17 +181,26 @@ pub fn get_class_name_expression<'a>( ] .into_iter() .flatten() - .chain(spread_props.iter().map(|ex| { - convert_class_name( - ast_builder, - &Expression::StaticMemberExpression(ast_builder.alloc_static_member_expression( - SPAN, - ex.clone_in(ast_builder.allocator), - ast_builder.identifier_name(SPAN, ast_builder.atom("className")), - true, - )), - ) - })) + .chain(if class_name_prop.is_some() { + vec![] + } else { + spread_props + .iter() + .map(|ex| { + convert_class_name( + ast_builder, + &Expression::StaticMemberExpression( + ast_builder.alloc_static_member_expression( + SPAN, + ex.clone_in(ast_builder.allocator), + ast_builder.identifier_name(SPAN, ast_builder.atom("className")), + true, + ), + ), + ) + }) + .collect::>() + }) .collect::>() .as_slice(), ) diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-9.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-9.snap index 8c827d5d..35ac0d70 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-9.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-9.snap @@ -20,5 +20,5 @@ ToBTreeSet { "buttonS", ), }, - code: "import \"@devup-ui/core/devup-ui.css\";\nimport clsx from \"clsx\";\n