diff --git a/.changeset/fine-rabbits-think.md b/.changeset/fine-rabbits-think.md new file mode 100644 index 00000000..933f967f --- /dev/null +++ b/.changeset/fine-rabbits-think.md @@ -0,0 +1,6 @@ +--- +"@devup-ui/webpack-plugin": patch +"@devup-ui/next-plugin": patch +--- + +Support next build cache diff --git a/.changeset/proud-paws-carry.md b/.changeset/proud-paws-carry.md new file mode 100644 index 00000000..66e290cd --- /dev/null +++ b/.changeset/proud-paws-carry.md @@ -0,0 +1,5 @@ +--- +"@devup-ui/wasm": patch +--- + +Fix styleOrder issue diff --git a/libs/extractor/src/gen_style.rs b/libs/extractor/src/gen_style.rs index f86e7bde..61c29889 100644 --- a/libs/extractor/src/gen_style.rs +++ b/libs/extractor/src/gen_style.rs @@ -220,32 +220,32 @@ fn gen_style<'a>( }, ExtractStyleProp::Expression { styles, .. } => { for style in styles { - match style.extract() { - StyleProperty::ClassName(_) => {} - StyleProperty::Variable { - variable_name, - identifier, - .. - } => { - properties.push(ObjectPropertyKind::ObjectProperty( - ast_builder.alloc_object_property( + if let StyleProperty::Variable { + variable_name, + identifier, + .. + } = style.extract() + { + properties.push(ObjectPropertyKind::ObjectProperty( + ast_builder.alloc_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StringLiteral(ast_builder.alloc_string_literal( SPAN, - PropertyKind::Init, - PropertyKey::StringLiteral(ast_builder.alloc_string_literal( - SPAN, - ast_builder.atom(&variable_name), - None, - )), - Expression::Identifier(ast_builder.alloc_identifier_reference( + ast_builder.atom(&variable_name), + None, + )), + Expression::Identifier( + ast_builder.alloc_identifier_reference( SPAN, ast_builder.atom(&identifier), - )), - false, - false, - false, + ), ), - )); - } + false, + false, + false, + ), + )); } } } @@ -253,18 +253,16 @@ fn gen_style<'a>( let mut tmp_map = BTreeMap::>::new(); for (key, value) in map.iter() { for style in value.extract() { - match style.extract() { - StyleProperty::ClassName(_) => {} - StyleProperty::Variable { - variable_name, - identifier, - .. - } => { - tmp_map - .entry(variable_name) - .or_default() - .push((key.to_string(), identifier)); - } + if let StyleProperty::Variable { + variable_name, + identifier, + .. + } = style.extract() + { + tmp_map + .entry(variable_name) + .or_default() + .push((key.to_string(), identifier)); } } } @@ -359,29 +357,24 @@ pub fn apply_style_attribute<'a>( ) { if let Some(ref mut value) = style_prop.value { if let JSXAttributeValue::ExpressionContainer(container) = value { - match container.expression { - JSXExpression::ObjectExpression(ref mut obj) => { - expression.properties.insert( - 0, - ObjectPropertyKind::SpreadProperty(ast_builder.alloc_spread_element( - SPAN, - Expression::ObjectExpression(obj.clone_in(ast_builder.allocator)), - )), - ); - } - JSXExpression::Identifier(ref ident) => { - expression.properties.insert( - 0, - ObjectPropertyKind::SpreadProperty(ast_builder.alloc_spread_element( - SPAN, - Expression::Identifier(ident.clone_in(ast_builder.allocator)), - )), - ); - } - _ => {} - }; + if let Some(spread_property) = match &container.expression { + JSXExpression::ObjectExpression(obj) => Some(Expression::ObjectExpression( + obj.clone_in(ast_builder.allocator), + )), + JSXExpression::Identifier(ident) => Some(Expression::Identifier( + ident.clone_in(ast_builder.allocator), + )), + _ => None, + } { + expression.properties.insert( + 0, + ObjectPropertyKind::SpreadProperty( + ast_builder.alloc_spread_element(SPAN, spread_property), + ), + ); + } container.expression = JSXExpression::ObjectExpression(ast_builder.alloc(expression)); - }; + } } else { style_prop.value = Some(JSXAttributeValue::ExpressionContainer( ast_builder.alloc_jsx_expression_container( diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index a8e90cd0..bd55d594 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -228,7 +228,7 @@ mod tests { extract( "test.tsx", r#"import {Box} from '@devup-ui/core' - + "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -243,7 +243,7 @@ mod tests { extract( "test.tsx", r#"import {Box} from '@devup-ui/core' - + "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -256,7 +256,35 @@ mod tests { 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!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + "#, ExtractOption { package: "@devup-ui/core".to_string(), @@ -268,7 +296,7 @@ mod tests { // assert_debug_snapshot!(extract( // "test.tsx", // r#"import {Box} from '@devup-ui/core' - // + // // "#, // ExtractOption { // package: "@devup-ui/core".to_string(), @@ -279,7 +307,7 @@ mod tests { // assert_debug_snapshot!(extract( // "test.tsx", // r#"import {Box} from '@devup-ui/core' - // + // // "#, // ExtractOption { // package: "@devup-ui/core".to_string(), @@ -291,7 +319,7 @@ mod tests { // assert_debug_snapshot!(extract( // "test.tsx", // r#"import {Box} from '@devup-ui/core' - // + // // "#, // ExtractOption { // package: "@devup-ui/core".to_string(), @@ -2850,4 +2878,74 @@ export { .unwrap() )); } + + #[test] + #[serial] + fn style_order2() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.jsx", + r#"import {Box, css} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.jsx", + r#"import {Box, css} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.jsx", + r#"import {Box, css} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } } diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap index 4d1c4c6c..ee0b2598 100644 --- a/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-2.snap @@ -1,8 +1,8 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" --- ToBTreeSet { styles: {}, - code: ";\n", + 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 index 8e1bbb49..caf63bc9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__convert_tag-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-3.snap @@ -1,8 +1,8 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" --- ToBTreeSet { styles: {}, - code: ";\n", + code: "
;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag-4.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag-4.snap new file mode 100644 index 00000000..7db7e72b --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-4.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__convert_tag-5.snap b/libs/extractor/src/snapshots/extractor__tests__convert_tag-5.snap new file mode 100644 index 00000000..06f47bc1 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag-5.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + 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 index cc1945bc..12e14960 100644 --- a/libs/extractor/src/snapshots/extractor__tests__convert_tag.snap +++ b/libs/extractor/src/snapshots/extractor__tests__convert_tag.snap @@ -1,8 +1,8 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" --- ToBTreeSet { styles: {}, - code: ";\n", + code: "
;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap new file mode 100644 index 00000000..9ad954bc --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2-2.snap @@ -0,0 +1,76 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box, css} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "white", + level: 0, + selector: None, + style_order: Some( + 100, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Selector( + "*[aria-diabled='true'] &", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "16px", + level: 0, + selector: None, + style_order: Some( + 20, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap new file mode 100644 index 00000000..986fcf89 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2-3.snap @@ -0,0 +1,76 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box, css} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "white", + level: 0, + selector: None, + style_order: Some( + 100, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Selector( + "*[aria-diabled='true'] &", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "16px", + level: 0, + selector: None, + style_order: Some( + 20, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__style_order2.snap b/libs/extractor/src/snapshots/extractor__tests__style_order2.snap new file mode 100644 index 00000000..832dfdd0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__style_order2.snap @@ -0,0 +1,76 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box, css} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 1, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "white", + level: 0, + selector: None, + style_order: Some( + 100, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: "0.5", + level: 0, + selector: Some( + Selector( + "*[aria-diabled='true'] &", + ), + ), + style_order: Some( + 20, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "16px", + level: 0, + selector: None, + style_order: Some( + 20, + ), + }, + ), + }, + 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 e18cac3c..de5122a2 100644 --- a/libs/extractor/src/style_extractor.rs +++ b/libs/extractor/src/style_extractor.rs @@ -85,14 +85,11 @@ pub fn extract_style_from_expression<'a>( if !match &mut prop { ObjectPropertyKind::ObjectProperty(prop) => { if let PropertyKey::StaticIdentifier(ident) = &prop.key { - let name = ident.name.to_string(); + let name = ident.name.as_str(); if name == "styleOrder" { - if let Some(value) = - get_number_by_literal_expression(&prop.value) - { - style_order = Some(value as u8); - } + style_order = get_number_by_literal_expression(&prop.value) + .map(|v| v as u8); continue; } @@ -110,9 +107,7 @@ pub fn extract_style_from_expression<'a>( styles.into_iter().for_each(|mut styles| { props_styles.append(&mut styles) }); - if let Some(t) = _tag { - tag = Some(t); - } + tag = _tag.or(tag); true } } @@ -135,9 +130,7 @@ pub fn extract_style_from_expression<'a>( styles .into_iter() .for_each(|mut styles| props_styles.append(&mut styles)); - if let Some(t) = _tag { - tag = Some(t); - } + tag = _tag.or(tag); true } } @@ -273,7 +266,7 @@ pub fn extract_style_from_expression<'a>( format!( "{}{}", selector.to_string().split("&").collect::>()[0], - name.as_str() + name ) } else { name @@ -314,11 +307,11 @@ pub fn extract_style_from_expression<'a>( style_order: None, tag: None, styles: Some(vec![ExtractStyleProp::Static(if typo { - Typography(value.as_str().to_string()) + Typography(value.to_string()) } else { Static(ExtractStaticStyle::new( name, - value.as_str(), + &value, level, selector.cloned(), )) @@ -335,7 +328,7 @@ pub fn extract_style_from_expression<'a>( ExtractDynamicStyle::new( name.unwrap(), level, - expression_to_code(expression).as_str(), + &expression_to_code(expression), selector.cloned(), ), ))]), @@ -357,11 +350,11 @@ pub fn extract_style_from_expression<'a>( if tmp.quasis.len() == 1 { ExtractResult::Extract { styles: Some(vec![ExtractStyleProp::Static(if typo { - Typography(tmp.quasis[0].value.raw.as_str().to_string()) + Typography(tmp.quasis[0].value.raw.to_string()) } else { Static(ExtractStaticStyle::new( name, - tmp.quasis[0].value.raw.as_str(), + &tmp.quasis[0].value.raw, level, selector.cloned(), )) @@ -407,7 +400,7 @@ pub fn extract_style_from_expression<'a>( ExtractDynamicStyle::new( name, level, - expression_to_code(expression).as_str(), + &expression_to_code(expression), selector.cloned(), ), ))]), @@ -461,7 +454,7 @@ pub fn extract_style_from_expression<'a>( ExtractDynamicStyle::new( name, level, - identifier.name.as_str(), + &identifier.name, selector.cloned(), ), ))]), @@ -701,15 +694,14 @@ fn extract_style_from_member_expression<'a>( vec![ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( name.unwrap(), level, - expression_to_code(&Expression::ComputedMemberExpression( + &expression_to_code(&Expression::ComputedMemberExpression( ast_builder.alloc_computed_member_expression( SPAN, etc, mem_expression.clone_in(ast_builder.allocator), false, ), - )) - .as_str(), + )), selector.cloned(), )))] }), @@ -726,15 +718,14 @@ fn extract_style_from_member_expression<'a>( Box::new(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( name.unwrap(), level, - expression_to_code(&Expression::ComputedMemberExpression( + &expression_to_code(&Expression::ComputedMemberExpression( ast_builder.alloc_computed_member_expression( SPAN, sp.argument.clone_in(ast_builder.allocator), mem_expression.clone_in(ast_builder.allocator), false, ), - )) - .as_str(), + )), selector.cloned(), )))), ); @@ -811,15 +802,14 @@ fn extract_style_from_member_expression<'a>( ret.push(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( name.unwrap(), level, - expression_to_code(&Expression::ComputedMemberExpression( + &expression_to_code(&Expression::ComputedMemberExpression( ast_builder.alloc_computed_member_expression( SPAN, etc, mem_expression.clone_in(ast_builder.allocator), false, ), - )) - .as_str(), + )), selector.cloned(), )))) } @@ -860,15 +850,14 @@ fn extract_style_from_member_expression<'a>( ret.push(ExtractStyleProp::Static(Dynamic(ExtractDynamicStyle::new( name, level, - expression_to_code(&Expression::ComputedMemberExpression( + &expression_to_code(&Expression::ComputedMemberExpression( ast_builder.alloc_computed_member_expression( SPAN, mem.object.clone_in(ast_builder.allocator), mem_expression.clone_in(ast_builder.allocator), false, ), - )) - .as_str(), + )), selector.cloned(), )))) } diff --git a/libs/extractor/src/utils.rs b/libs/extractor/src/utils.rs index 405337ea..6fc42dcb 100644 --- a/libs/extractor/src/utils.rs +++ b/libs/extractor/src/utils.rs @@ -165,7 +165,7 @@ pub fn is_special_property(name: &str) -> bool { || SPECIAL_PROPERTIES.contains(name) } -pub fn get_number_by_jsx_expression(expr: &JSXAttributeValue) -> Option { +pub fn jsx_expression_to_number(expr: &JSXAttributeValue) -> Option { match expr { JSXAttributeValue::StringLiteral(sl) => get_number_by_literal_expression( &Expression::StringLiteral(sl.clone_in(&Allocator::default())), @@ -182,6 +182,15 @@ pub fn get_number_by_literal_expression(expr: &Expression) -> Option { Expression::ParenthesizedExpression(parenthesized) => { get_number_by_literal_expression(&parenthesized.expression) } + Expression::StringLiteral(sl) => sl.value.parse::().ok(), + Expression::TemplateLiteral(tmp) => tmp + .quasis + .iter() + .map(|q| q.value.raw.to_string()) + .collect::>() + .join("") + .parse::() + .ok(), Expression::NumericLiteral(num) => Some(num.value), Expression::UnaryExpression(unary) => get_number_by_literal_expression(&unary.argument) .and_then(|num| match unary.operator { @@ -200,7 +209,7 @@ pub fn get_string_by_literal_expression(expr: &Expression) -> Option { Expression::ParenthesizedExpression(parenthesized) => { get_string_by_literal_expression(&parenthesized.expression) } - Expression::StringLiteral(str) => Some(str.value.as_str().to_string()), + Expression::StringLiteral(str) => Some(str.value.into()), Expression::TemplateLiteral(tmp) => { let mut collect = vec![]; for (idx, q) in tmp.quasis.iter().enumerate() { @@ -222,6 +231,9 @@ pub fn get_string_by_literal_expression(expr: &Expression) -> Option { #[cfg(test)] mod tests { + use oxc_allocator::Vec; + use oxc_ast::ast::{JSXAttributeName, JSXElementName}; + use super::*; #[test] @@ -231,4 +243,123 @@ mod tests { assert_eq!(convert_value("foo"), "foo"); assert_eq!(convert_value("4"), "16px"); } + + #[test] + fn test_get_number_by_literal_expression() { + let allocator = Allocator::default(); + { + let parsed = Parser::new(&allocator, "1", SourceType::d_ts()).parse(); + assert_eq!(parsed.program.body.len(), 1); + assert!(matches!( + parsed.program.body[0], + Statement::ExpressionStatement(_) + )); + if let Statement::ExpressionStatement(expr) = &parsed.program.body[0] { + assert_eq!( + get_number_by_literal_expression(&expr.expression), + Some(1.0) + ); + } + } + { + let parsed = Parser::new(&allocator, "-1", SourceType::d_ts()).parse(); + assert_eq!(parsed.program.body.len(), 1); + assert!(matches!( + parsed.program.body[0], + Statement::ExpressionStatement(_) + )); + if let Statement::ExpressionStatement(expr) = &parsed.program.body[0] { + assert_eq!( + get_number_by_literal_expression(&expr.expression), + Some(-1.0) + ); + } + } + { + let parsed = Parser::new(&allocator, "1.5", SourceType::d_ts()).parse(); + assert_eq!(parsed.program.body.len(), 1); + assert!(matches!( + parsed.program.body[0], + Statement::ExpressionStatement(_) + )); + if let Statement::ExpressionStatement(expr) = &parsed.program.body[0] { + assert_eq!( + get_number_by_literal_expression(&expr.expression), + Some(1.5) + ); + } + } + { + let parsed = Parser::new(&allocator, "delete 1", SourceType::d_ts()).parse(); + assert_eq!(parsed.program.body.len(), 1); + assert!(matches!( + parsed.program.body[0], + Statement::ExpressionStatement(_) + )); + if let Statement::ExpressionStatement(expr) = &parsed.program.body[0] { + assert_eq!(get_number_by_literal_expression(&expr.expression), None); + } + } + } + + #[test] + fn test_jsx_expression_to_number() { + let allocator = Allocator::default(); + let builder = oxc_ast::AstBuilder::new(&allocator); + assert_eq!( + jsx_expression_to_number( + &builder + .alloc_jsx_attribute( + SPAN, + JSXAttributeName::Identifier( + builder.alloc_jsx_identifier(SPAN, "styleOrder") + ), + Some(JSXAttributeValue::StringLiteral( + builder.alloc_string_literal(SPAN, "1", None), + )), + ) + .value + .as_ref() + .unwrap() + ), + Some(1.0) + ); + + assert_eq!( + jsx_expression_to_number( + &builder + .alloc_jsx_attribute( + SPAN, + JSXAttributeName::Identifier( + builder.alloc_jsx_identifier(SPAN, "styleOrder") + ), + Some(JSXAttributeValue::Element(builder.alloc_jsx_element( + SPAN, + builder.alloc_jsx_opening_element( + SPAN, + JSXElementName::Identifier( + builder.alloc_jsx_identifier(SPAN, "div") + ), + Some(builder.ts_type_parameter_instantiation( + SPAN, + Vec::new_in(&allocator) + )), + Vec::new_in(&allocator).into(), + ), + Vec::new_in(&allocator).into(), + Some(builder.alloc_jsx_closing_element( + SPAN, + JSXElementName::Identifier( + builder.alloc_jsx_identifier(SPAN, "div") + ) + )), + ))) + ) + .value + .as_ref() + .unwrap() + ), + None + ); + } } diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 22476248..11ed8176 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -22,7 +22,7 @@ use oxc_ast_visit::walk_mut::{ walk_variable_declarator, }; -use crate::utils::get_number_by_jsx_expression; +use crate::utils::jsx_expression_to_number; use oxc_ast::AstBuilder; use oxc_span::SPAN; use std::collections::{HashMap, HashSet}; @@ -164,24 +164,29 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } fn visit_call_expression(&mut self, it: &mut CallExpression<'a>) { let jsx = if let Expression::Identifier(ident) = &it.callee { - self.jsx_imports - .get(&ident.name.to_string()) - .map(|s| s.to_string()) - } else if let Expression::StaticMemberExpression(member) = &it.callee { - if let Expression::Identifier(ident) = &member.object { - if self.jsx_object == Some(ident.name.to_string()) { - Some(member.property.to_string()) - } else { - None + self.jsx_imports.get(ident.name.as_str()).cloned() + } else { + match &self.jsx_object { + Some(name) => { + if let Expression::StaticMemberExpression(member) = &it.callee { + if let Expression::Identifier(ident) = &member.object { + if name == ident.name.as_str() { + Some(member.property.name.to_string()) + } else { + None + } + } else { + None + } + } else { + None + } } - } else { - None + None => None, } - } else { - None }; if let Some(j) = jsx { - if (j == "jsx" || j == "jsxs") && it.arguments.len() > 0 { + if (j == "jsx" || j == "jsxs") && !it.arguments.is_empty() { let expr = it.arguments[0].to_expression(); let element_kind = if let Expression::Identifier(ident) = expr { self.imports.get(ident.name.as_str()).cloned() @@ -259,42 +264,45 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } fn visit_variable_declarator(&mut self, it: &mut VariableDeclarator<'a>) { if let Some(Expression::CallExpression(call)) = &it.init { - if call.arguments.len() == 1 { - if let (Expression::Identifier(ident), Argument::StringLiteral(arg)) = - (&call.callee, &call.arguments[0]) - { - if ident.name == "require" { - if arg.value == "react/jsx-runtime" { - if let BindingPatternKind::BindingIdentifier(ident) = &it.id.kind { - self.jsx_object = Some(ident.name.to_string()); - } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { - for prop in &object.properties { - if let PropertyKey::StaticIdentifier(ident) = &prop.key { - if let Some(k) = prop - .value - .get_binding_identifier() - .map(|id| id.name.to_string()) - { - self.jsx_imports.insert(k, ident.name.to_string()); - } - } + if call.arguments.len() != 1 { + return; + } + if let (Expression::Identifier(ident), Argument::StringLiteral(arg)) = + (&call.callee, &call.arguments[0]) + { + if ident.name != "require" { + return; + } + + if arg.value == "react/jsx-runtime" { + if let BindingPatternKind::BindingIdentifier(ident) = &it.id.kind { + self.jsx_object = Some(ident.name.to_string()); + } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { + for prop in &object.properties { + if let PropertyKey::StaticIdentifier(ident) = &prop.key { + if let Some(k) = prop + .value + .get_binding_identifier() + .map(|id| id.name.to_string()) + { + self.jsx_imports.insert(k, ident.name.to_string()); } } - } else if arg.value == self.package { - if let BindingPatternKind::BindingIdentifier(ident) = &it.id.kind { - self.import_object = Some(ident.name.to_string()); - } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { - for prop in &object.properties { - if let PropertyKey::StaticIdentifier(ident) = &prop.key { - if let Ok(kind) = ExportVariableKind::try_from( - prop.value - .get_binding_identifier() - .map(|id| id.name.to_string()) - .unwrap_or("".to_string()), - ) { - self.imports.insert(ident.name.to_string(), kind); - } - } + } + } + } else if arg.value == self.package { + if let BindingPatternKind::BindingIdentifier(ident) = &it.id.kind { + self.import_object = Some(ident.name.to_string()); + } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { + for prop in &object.properties { + if let PropertyKey::StaticIdentifier(ident) = &prop.key { + if let Ok(kind) = ExportVariableKind::try_from( + prop.value + .get_binding_identifier() + .map(|id| id.name.to_string()) + .unwrap_or("".to_string()), + ) { + self.imports.insert(ident.name.to_string(), kind); } } } @@ -316,7 +324,6 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } } } - return; } @@ -362,15 +369,14 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { let mut attr = attrs.remove(i); if let Attribute(attr) = &mut attr { if let Identifier(name) = &attr.name { - let name = short_to_long(name.name.as_str()); + let name = short_to_long(&name.name); if duplicate_set.contains(&name) { continue; } duplicate_set.insert(name.clone()); if name == "styleOrder" { - style_order = - get_number_by_jsx_expression(attr.value.as_ref().unwrap()) - .map(|n| n as u8); + style_order = jsx_expression_to_number(attr.value.as_ref().unwrap()) + .map(|n| n as u8); continue; } @@ -393,41 +399,32 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { attrs.insert(i, attr); } - for ex in kind.extract().into_iter().rev() { - props_styles.push(ExtractStyleProp::Static(ex)); - } + kind.extract() + .into_iter() + .rev() + .for_each(|ex| props_styles.push(ExtractStyleProp::Static(ex))); modify_props(&self.ast, attrs, &mut props_styles, style_order); - for style in props_styles.iter().rev() { - self.styles.extend(style.extract()); - } + + props_styles + .iter() + .rev() + .for_each(|style| self.styles.extend(style.extract())); // modify!! - match tag_name { - Expression::StringLiteral(str) => { - // change tag name - let ident = JSXElementName::Identifier( - self.ast - .alloc_jsx_identifier(SPAN, self.ast.atom(&str.value)), - ); - 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, - self.ast.atom(&literal.quasis[0].value.raw), - )); - opening_element.name = ident.clone_in(self.ast.allocator); - if let Some(el) = &mut elem.closing_element { - el.name = ident; - } - } + if let Some(tag) = match tag_name { + Expression::StringLiteral(str) => Some(str.value.as_str()), + Expression::TemplateLiteral(literal) => Some(literal.quasis[0].value.raw.as_str()), + _ => None, + } { + let ident = JSXElementName::Identifier( + self.ast.alloc_jsx_identifier(SPAN, self.ast.atom(tag)), + ); - _ => {} + if let Some(el) = &mut elem.closing_element { + el.name = ident.clone_in(self.ast.allocator); + } + opening_element.name = ident; } } } diff --git a/packages/next-plugin/src/__tests__/plugin.test.ts b/packages/next-plugin/src/__tests__/plugin.test.ts index a5e90a46..b65ac017 100644 --- a/packages/next-plugin/src/__tests__/plugin.test.ts +++ b/packages/next-plugin/src/__tests__/plugin.test.ts @@ -13,9 +13,11 @@ describe('plugin', () => { it('should apply webpack plugin', async () => { const ret = DevupUI({}) - ret.webpack!({ plugins: [] }, {} as any) + ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any) - expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({}) + expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({ + cssFile: resolve('.next/cache', 'devup-ui_tmpBuildId.css'), + }) }) it('should apply webpack plugin with config', async () => { @@ -26,10 +28,11 @@ describe('plugin', () => { }, ) - ret.webpack!({ plugins: [] }, {} as any) + ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any) expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({ package: 'new-package', + cssFile: resolve('.next/cache', 'devup-ui_tmpBuildId.css'), }) }) @@ -44,10 +47,11 @@ describe('plugin', () => { }, ) - ret.webpack!({ plugins: [] }, {} as any) + ret.webpack!({ plugins: [] }, { buildId: 'tmpBuildId' } as any) expect(DevupUIWebpackPlugin).toHaveBeenCalledWith({ package: 'new-package', + cssFile: resolve('.next/cache', 'devup-ui_tmpBuildId.css'), }) expect(webpack).toHaveBeenCalled() }) diff --git a/packages/next-plugin/src/plugin.ts b/packages/next-plugin/src/plugin.ts index 52899147..88a0ed74 100644 --- a/packages/next-plugin/src/plugin.ts +++ b/packages/next-plugin/src/plugin.ts @@ -65,6 +65,10 @@ export function DevupUI( const { webpack } = config config.webpack = (config, _options) => { + options.cssFile ??= resolve( + options.interfacePath ?? '.next/cache', + `devup-ui_${_options.buildId}.css`, + ) config.plugins.push( new DevupUIWebpackPlugin({ ...options, diff --git a/packages/webpack-plugin/src/__tests__/plugin.test.ts b/packages/webpack-plugin/src/__tests__/plugin.test.ts index e790305e..3bf1aefb 100644 --- a/packages/webpack-plugin/src/__tests__/plugin.test.ts +++ b/packages/webpack-plugin/src/__tests__/plugin.test.ts @@ -103,6 +103,8 @@ describe('devupUIPlugin', () => { const plugin = new DevupUIWebpackPlugin({ devupPath: 'custom-devup.json', }) + + vi.mocked(getCss).mockReturnValue('css') const compiler = { options: { module: { @@ -114,6 +116,9 @@ describe('devupUIPlugin', () => { afterCompile: { tap: vi.fn(), }, + done: { + tap: vi.fn(), + }, watchRun: { tapAsync: vi.fn(), }, @@ -131,6 +136,23 @@ describe('devupUIPlugin', () => { }, }) expect(add).toHaveBeenCalledWith(resolve('custom-devup.json')) + expect(compiler.hooks.done.tap).toHaveBeenCalled() + + vi.mocked(compiler.hooks.done.tap).mock.calls[0][1]({ + hasErrors: () => true, + }) + expect(writeFileSync).not.toHaveBeenCalled() + + vi.mocked(compiler.hooks.done.tap).mock.calls[0][1]({ + hasErrors: () => false, + }) + expect(writeFileSync).toHaveBeenCalledWith( + resolve('.df', 'devup-ui.css'), + 'css', + { + encoding: 'utf-8', + }, + ) }) it('should skip writing css file', () => { @@ -155,6 +177,9 @@ describe('devupUIPlugin', () => { afterCompile: { tap: vi.fn(), }, + done: { + tap: vi.fn(), + }, watchRun: { tapAsync: vi.fn(), }, @@ -191,6 +216,9 @@ describe('devupUIPlugin', () => { afterCompile: { tap: vi.fn(), }, + done: { + tap: vi.fn(), + }, watchRun: { tapAsync: vi.fn(), }, @@ -208,6 +236,7 @@ describe('devupUIPlugin', () => { encoding: 'utf-8', }, ) + expect(compiler.hooks.done.tap).not.toHaveBeenCalled() }) it('should register devup watch', () => { const plugin = new DevupUIWebpackPlugin({ @@ -224,6 +253,9 @@ describe('devupUIPlugin', () => { afterCompile: { tap: vi.fn(), }, + done: { + tap: vi.fn(), + }, watchRun: { tapAsync: vi.fn(), }, @@ -310,6 +342,9 @@ describe('devupUIPlugin', () => { afterCompile: { tap: vi.fn(), }, + done: { + tap: vi.fn(), + }, watchRun: { tapAsync: vi.fn(), }, diff --git a/packages/webpack-plugin/src/plugin.ts b/packages/webpack-plugin/src/plugin.ts index 0eb7487a..12f7a534 100644 --- a/packages/webpack-plugin/src/plugin.ts +++ b/packages/webpack-plugin/src/plugin.ts @@ -9,6 +9,7 @@ import { createRequire } from 'node:module' import { join, resolve } from 'node:path' import { + getCss, getDefaultTheme, getThemeInterface, importClassMap, @@ -138,6 +139,14 @@ export class DevupUIWebpackPlugin { 'process.env.DEVUP_UI_DEFAULT_THEME': JSON.stringify(getDefaultTheme()), }), ) + if (!this.options.watch) { + compiler.hooks.done.tap('DevupUIWebpackPlugin', (stats) => { + if (!stats.hasErrors()) { + // write css file + writeFileSync(this.options.cssFile, getCss(), { encoding: 'utf-8' }) + } + }) + } compiler.options.module.rules.push( {