diff --git a/.changeset/long-beds-suffer.md b/.changeset/long-beds-suffer.md new file mode 100644 index 00000000..966102b1 --- /dev/null +++ b/.changeset/long-beds-suffer.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Add maintain properies diff --git a/Cargo.lock b/Cargo.lock index 40d551ec..6a406c04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,7 @@ version = "0.1.0" dependencies = [ "css", "insta", + "once_cell", "oxc_allocator", "oxc_ast", "oxc_codegen", diff --git a/bindings/devup-ui-wasm/src/lib.rs b/bindings/devup-ui-wasm/src/lib.rs index 185c584d..fa11103e 100644 --- a/bindings/devup-ui-wasm/src/lib.rs +++ b/bindings/devup-ui-wasm/src/lib.rs @@ -1,4 +1,5 @@ -use extractor::{extract, ExtractOption, ExtractStyleValue, StyleProperty}; +use extractor::extract_style::ExtractStyleValue; +use extractor::{extract, ExtractOption, StyleProperty}; use js_sys::{Object, Reflect}; use once_cell::sync::Lazy; use sheet::theme::{ColorTheme, Theme, Typography}; @@ -45,22 +46,22 @@ impl Output { match style { ExtractStyleValue::Static(st) => { if sheet.add_property( - cls, - st.property.clone(), - st.level, - st.value.clone(), - st.selector.clone(), + &cls, + st.property(), + st.level(), + st.value(), + st.selector(), ) { collected = true; } } ExtractStyleValue::Dynamic(dy) => { if sheet.add_property( - cls, - dy.property.clone(), - dy.level, - format!("var({})", variable.unwrap()), - dy.selector.clone(), + &cls, + dy.property(), + dy.level(), + &format!("var({})", variable.unwrap()), + dy.selector(), ) { collected = true; } diff --git a/libs/extractor/Cargo.toml b/libs/extractor/Cargo.toml index 4686f228..debfd490 100644 --- a/libs/extractor/Cargo.toml +++ b/libs/extractor/Cargo.toml @@ -11,6 +11,7 @@ oxc_allocator = "0.46.0" oxc_ast = "0.46.0" oxc_codegen = "0.46.0" css = { path = "../css" } +once_cell = "1.20.2" [dev-dependencies] insta = "1.42.0" diff --git a/libs/extractor/src/component.rs b/libs/extractor/src/component.rs index 1b5fc11d..b5619bf8 100644 --- a/libs/extractor/src/component.rs +++ b/libs/extractor/src/component.rs @@ -1,5 +1,6 @@ -use crate::ExtractStyleValue::Static; -use crate::{ExtractStaticStyle, ExtractStyleValue}; +use crate::extract_style::ExtractStaticStyle; +use crate::extract_style::ExtractStyleValue::Static; +use crate::ExtractStyleValue; /// devup-ui export variable kind #[derive(Debug, PartialEq, Clone)] @@ -40,56 +41,56 @@ impl ExportVariableKind { | ExportVariableKind::Text | ExportVariableKind::Image | ExportVariableKind::Box => vec![], - ExportVariableKind::Flex => vec![Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - })], + ExportVariableKind::Flex => vec![Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + ))], ExportVariableKind::VStack => { vec![ - Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "column".to_string(), - property: "flexDirection".to_string(), - level: 0, - selector: None, - }), + Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "flexDirection".to_string(), + "column".to_string(), + 0, + None, + )), ] } ExportVariableKind::Center => { vec![ - Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "center".to_string(), - property: "justifyContent".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "center".to_string(), - property: "alignItems".to_string(), - level: 0, - selector: None, - }), + Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "justifyContent".to_string(), + "center".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "alignItems".to_string(), + "center".to_string(), + 0, + None, + )), ] } - ExportVariableKind::Grid => vec![Static(ExtractStaticStyle { - value: "grid".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - })], + ExportVariableKind::Grid => vec![Static(ExtractStaticStyle::new( + "display".to_string(), + "grid".to_string(), + 0, + None, + ))], } } } @@ -181,61 +182,61 @@ mod tests { assert_eq!(ExportVariableKind::Input.extract(), vec![]); assert_eq!( ExportVariableKind::Flex.extract(), - vec![Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - })] + vec![Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + ))] ); assert_eq!( ExportVariableKind::VStack.extract(), vec![ - Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "column".to_string(), - property: "flexDirection".to_string(), - level: 0, - selector: None, - }) + Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "flexDirection".to_string(), + "column".to_string(), + 0, + None, + )) ] ); assert_eq!( ExportVariableKind::Center.extract(), vec![ - Static(ExtractStaticStyle { - value: "flex".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "center".to_string(), - property: "justifyContent".to_string(), - level: 0, - selector: None, - }), - Static(ExtractStaticStyle { - value: "center".to_string(), - property: "alignItems".to_string(), - level: 0, - selector: None, - }) + Static(ExtractStaticStyle::new( + "display".to_string(), + "flex".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "justifyContent".to_string(), + "center".to_string(), + 0, + None, + )), + Static(ExtractStaticStyle::new( + "alignItems".to_string(), + "center".to_string(), + 0, + None, + )) ] ); assert_eq!( ExportVariableKind::Grid.extract(), - vec![Static(ExtractStaticStyle { - value: "grid".to_string(), - property: "display".to_string(), - level: 0, - selector: None, - })] + vec![Static(ExtractStaticStyle::new( + "display".to_string(), + "grid".to_string(), + 0, + None, + ))] ); } } diff --git a/libs/extractor/src/extract_style/mod.rs b/libs/extractor/src/extract_style/mod.rs new file mode 100644 index 00000000..cbcf4248 --- /dev/null +++ b/libs/extractor/src/extract_style/mod.rs @@ -0,0 +1,177 @@ +use crate::utils::convert_value; +use crate::StyleProperty; +use css::{css_to_classname, sheet_to_classname, sheet_to_variable_name}; +use once_cell::sync::Lazy; +use std::collections::HashSet; + +#[derive(Debug, PartialEq, Clone)] +pub struct ExtractStaticStyle { + /// property + property: String, + /// fixed value + value: String, + /// responsive level + level: u8, + /// selector + selector: Option, +} + +static MAINTAIN_VALUE_PROPERTIES: Lazy> = Lazy::new(|| { + let mut set = HashSet::::new(); + set.insert("opacity".to_string()); + set.insert("zIndex".to_string()); + set.insert("fontWeight".to_string()); + set.insert("scale".to_string()); + set +}); + +impl ExtractStaticStyle { + /// create a new ExtractStaticStyle + pub fn new(property: String, value: String, level: u8, selector: Option) -> Self { + Self { + value: if MAINTAIN_VALUE_PROPERTIES.contains(&property) { + value + } else { + convert_value(&value) + }, + property, + level, + selector, + } + } + + pub fn property(&self) -> &str { + self.property.as_str() + } + + pub fn value(&self) -> &str { + self.value.as_str() + } + + pub fn level(&self) -> u8 { + self.level + } + + pub fn selector(&self) -> Option<&str> { + self.selector.as_deref() + } +} + +pub trait ExtractStyleProperty { + /// extract style properties + fn extract(&self) -> StyleProperty; +} + +impl ExtractStyleProperty for ExtractStaticStyle { + fn extract(&self) -> StyleProperty { + StyleProperty::ClassName(sheet_to_classname( + self.property.as_str(), + self.level, + Some(convert_value(self.value.as_str()).as_str()), + if let Some(selector) = &self.selector { + Some(selector.as_str()) + } else { + None + }, + )) + } +} +#[derive(Debug, PartialEq, Clone)] +pub struct ExtractCss { + /// css code + pub css: String, +} + +impl ExtractStyleProperty for ExtractCss { + /// hashing css code to class name + fn extract(&self) -> StyleProperty { + StyleProperty::ClassName(css_to_classname(self.css.as_str())) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ExtractDynamicStyle { + /// property + property: String, + /// responsive + level: u8, + identifier: String, + + /// selector + selector: Option, +} + +impl ExtractDynamicStyle { + /// create a new ExtractDynamicStyle + pub fn new(property: String, level: u8, identifier: String, selector: Option) -> Self { + Self { + property, + level, + identifier, + selector, + } + } + + pub fn property(&self) -> &str { + self.property.as_str() + } + + pub fn level(&self) -> u8 { + self.level + } + + pub fn selector(&self) -> Option<&str> { + self.selector.as_deref() + } + + pub fn identifier(&self) -> &str { + self.identifier.as_str() + } +} + +impl ExtractStyleProperty for ExtractDynamicStyle { + fn extract(&self) -> StyleProperty { + StyleProperty::Variable { + class_name: sheet_to_classname( + self.property.as_str(), + self.level, + None, + if let Some(selector) = &self.selector { + Some(selector.as_str()) + } else { + None + }, + ), + variable_name: sheet_to_variable_name( + self.property.as_str(), + self.level, + if let Some(selector) = &self.selector { + Some(selector.as_str()) + } else { + None + }, + ), + identifier: self.identifier.clone(), + } + } +} +#[derive(Debug, PartialEq, Clone)] +pub enum ExtractStyleValue { + Static(ExtractStaticStyle), + Typography(String), + Dynamic(ExtractDynamicStyle), + Css(ExtractCss), +} + +impl ExtractStyleValue { + pub fn extract(&self) -> StyleProperty { + match self { + ExtractStyleValue::Static(style) => style.extract(), + ExtractStyleValue::Dynamic(style) => style.extract(), + ExtractStyleValue::Css(css) => css.extract(), + ExtractStyleValue::Typography(typo) => { + StyleProperty::ClassName(format!("typo-{}", typo)) + } + } + } +} diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index bbc2a267..59a5ce42 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -1,4 +1,5 @@ mod component; +pub mod extract_style; mod gen_class_name; mod gen_style; mod object_prop_extract_utils; @@ -9,9 +10,8 @@ mod visit; use oxc_codegen::Codegen; -use crate::utils::convert_value; +use crate::extract_style::ExtractStyleValue; use crate::visit::DevupVisitor; -use css::{css_to_classname, sheet_to_classname, sheet_to_variable_name}; use oxc_allocator::Allocator; use oxc_ast::ast::Expression; use oxc_ast::VisitMut; @@ -67,110 +67,6 @@ pub enum StyleProperty { identifier: String, }, } -pub trait ExtractStyleProperty { - /// extract style properties - fn extract(&self) -> StyleProperty; -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ExtractStaticStyle { - /// property - pub property: String, - /// fixed value - pub value: String, - /// responsive level - pub level: u8, - /// selector - pub selector: Option, -} - -impl ExtractStyleProperty for ExtractStaticStyle { - fn extract(&self) -> StyleProperty { - StyleProperty::ClassName(sheet_to_classname( - self.property.as_str(), - self.level, - Some(convert_value(self.value.as_str()).as_str()), - if let Some(selector) = &self.selector { - Some(selector.as_str()) - } else { - None - }, - )) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ExtractCss { - /// css code - pub css: String, -} - -impl ExtractStyleProperty for ExtractCss { - /// hashing css code to class name - fn extract(&self) -> StyleProperty { - StyleProperty::ClassName(css_to_classname(self.css.as_str())) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ExtractDynamicStyle { - /// property - pub property: String, - /// responsive - pub level: u8, - identifier: String, - - /// selector - pub selector: Option, -} - -impl ExtractStyleProperty for ExtractDynamicStyle { - fn extract(&self) -> StyleProperty { - StyleProperty::Variable { - class_name: sheet_to_classname( - self.property.as_str(), - self.level, - None, - if let Some(selector) = &self.selector { - Some(selector.as_str()) - } else { - None - }, - ), - variable_name: sheet_to_variable_name( - self.property.as_str(), - self.level, - if let Some(selector) = &self.selector { - Some(selector.as_str()) - } else { - None - }, - ), - identifier: self.identifier.clone(), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ExtractStyleValue { - Static(ExtractStaticStyle), - Typography(String), - Dynamic(ExtractDynamicStyle), - Css(ExtractCss), -} - -impl ExtractStyleValue { - pub fn extract(&self) -> StyleProperty { - match self { - ExtractStyleValue::Static(style) => style.extract(), - ExtractStyleValue::Dynamic(style) => style.extract(), - ExtractStyleValue::Css(css) => css.extract(), - ExtractStyleValue::Typography(typo) => { - StyleProperty::ClassName(format!("typo-{}", typo)) - } - } - } -} #[derive(Debug, PartialEq)] pub struct ExtractOutput { @@ -1040,4 +936,21 @@ export { ) .unwrap()); } + + #[test] + #[serial] + fn maintain_value() { + 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()); + } } diff --git a/libs/extractor/src/prop_extract_utils.rs b/libs/extractor/src/prop_extract_utils.rs index f1d792d2..52522883 100644 --- a/libs/extractor/src/prop_extract_utils.rs +++ b/libs/extractor/src/prop_extract_utils.rs @@ -1,6 +1,6 @@ -use crate::utils::convert_value; +use crate::extract_style::{ExtractDynamicStyle, ExtractStaticStyle}; +use crate::ExtractStyleProp; use crate::ExtractStyleValue::{Dynamic, Static}; -use crate::{ExtractDynamicStyle, ExtractStaticStyle, ExtractStyleProp}; use oxc_allocator::CloneIn; use oxc_ast::ast::{ ArrayExpression, ConditionalExpression, Expression, JSXAttributeValue, JSXExpression, @@ -18,14 +18,9 @@ pub fn extract_style_prop<'a>( value: &JSXAttributeValue<'a>, ) -> Option> { match value { - JSXAttributeValue::StringLiteral(str) => { - Some(ExtractStyleProp::Static(Static(ExtractStaticStyle { - value: convert_value(str.value.as_str()), - level: 0, - property: name, - selector: None, - }))) - } + JSXAttributeValue::StringLiteral(str) => Some(ExtractStyleProp::Static(Static( + ExtractStaticStyle::new(name, str.value.to_string(), 0, None), + ))), JSXAttributeValue::ExpressionContainer(expression) => { if let JSXExpression::EmptyExpression(_) = expression.expression { None @@ -51,31 +46,31 @@ pub fn extract_style_prop_from_express<'a>( ) -> Option> { match &expression { Expression::NumericLiteral(num) => { - Some(ExtractStyleProp::Static(Static(ExtractStaticStyle { - value: convert_value(num.value.to_string().as_str()), + Some(ExtractStyleProp::Static(Static(ExtractStaticStyle::new( + name.to_string(), + num.value.to_string(), level, - property: name.to_string(), - selector: selector.map(|s| s.to_string()), - }))) + selector.map(|s| s.to_string()), + )))) } Expression::StringLiteral(str) => { - Some(ExtractStyleProp::Static(Static(ExtractStaticStyle { - value: convert_value(str.value.as_str()), + Some(ExtractStyleProp::Static(Static(ExtractStaticStyle::new( + name.to_string(), + str.value.to_string(), level, - property: name.to_string(), - selector: selector.map(|s| s.to_string()), - }))) + selector.map(|s| s.to_string()), + )))) } Expression::Identifier(identifier) => { if IGNORED_IDENTIFIERS.contains(&identifier.name.as_str()) { None } else { - Some(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle { + Some(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( + name.to_string(), level, - property: name.to_string(), - identifier: identifier.name.to_string(), - selector: selector.map(|s| s.to_string()), - }))) + identifier.name.to_string(), + selector.map(|s| s.to_string()), + )))) } } Expression::LogicalExpression(logical) => match logical.operator { diff --git a/libs/extractor/src/snapshots/extractor__tests__maintain_value.snap b/libs/extractor/src/snapshots/extractor__tests__maintain_value.snap new file mode 100644 index 00000000..f723f8bb --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__maintain_value.snap @@ -0,0 +1,49 @@ +--- +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, + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: None, + }, + ), + Static( + ExtractStaticStyle { + property: "zIndex", + value: "2", + level: 0, + selector: None, + }, + ), + Static( + ExtractStaticStyle { + property: "fontWeight", + value: "900", + level: 0, + selector: None, + }, + ), + Static( + ExtractStaticStyle { + property: "scale", + value: "2", + level: 0, + selector: None, + }, + ), + ], + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index f1ac64d9..13d57320 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -1,9 +1,10 @@ use crate::component::ExportVariableKind; +use crate::extract_style::ExtractCss; use crate::object_prop_extract_utils::{extract_from_style_value, extract_object_from_jsx_attr}; use crate::prop_extract_utils::{extract_style_prop, extract_style_prop_from_express}; use crate::prop_modify_utils::{modify_prop_object, modify_props}; use crate::utils::is_special_property; -use crate::{ExtractCss, ExtractStyleProp, ExtractStyleValue, StyleProperty}; +use crate::{ExtractStyleProp, ExtractStyleValue, StyleProperty}; use oxc_allocator::{Allocator, CloneIn}; use oxc_ast::ast::ImportDeclarationSpecifier::ImportSpecifier; use oxc_ast::ast::JSXAttributeItem::Attribute; diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index 852c4077..2ef6d57e 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -93,17 +93,17 @@ impl StyleSheet { pub fn add_property( &mut self, - class_name: String, - property: String, + class_name: &str, + property: &str, level: u8, - value: String, - selector: Option, + value: &str, + selector: Option<&str>, ) -> bool { let prop = StyleSheetProperty { - class_name, - property, - value, - selector, + class_name: class_name.to_string(), + property: property.to_string(), + value: value.to_string(), + selector: selector.map(|s| s.to_string()), }; self.properties.entry(level).or_default().insert(prop) }