diff --git a/.changepacks/changepack_log_global_order.json b/.changepacks/changepack_log_global_order.json new file mode 100644 index 00000000..228e03af --- /dev/null +++ b/.changepacks/changepack_log_global_order.json @@ -0,0 +1,7 @@ +{ + "changes": { + "bindings/devup-ui-wasm/package.json": "Patch" + }, + "note": "Global CSS keeps the order it is written in: globalCss rules, @import and @font-face now appear in source order within each file (the later of two equal rules wins as written, repeated imports and font faces count where they are written last), object globalCss declarations on the same selector keep their written order, and custom cascade layers are declared in the order files first use them (nested layers after their parent) instead of alphabetical order", + "date": "2026-09-30T00:00:00.000Z" +} diff --git a/libs/extractor/src/extract_style/extract_css.rs b/libs/extractor/src/extract_style/extract_css.rs index 5913869c..9e910469 100644 --- a/libs/extractor/src/extract_style/extract_css.rs +++ b/libs/extractor/src/extract_style/extract_css.rs @@ -1,6 +1,8 @@ #[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] pub struct ExtractCss { + pub file: String, + /// Where the rules are written in the file; the sheet keeps them in this order + pub order: u32, /// css must be global css pub css: String, - pub file: String, } diff --git a/libs/extractor/src/extract_style/extract_font_face.rs b/libs/extractor/src/extract_style/extract_font_face.rs index 1a9b7538..80ebed69 100644 --- a/libs/extractor/src/extract_style/extract_font_face.rs +++ b/libs/extractor/src/extract_style/extract_font_face.rs @@ -3,5 +3,7 @@ use std::collections::BTreeMap; #[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] pub struct ExtractFontFace { pub file: String, + /// Where the rule is written in the file; the sheet keeps them in this order + pub order: u32, pub properties: BTreeMap, } diff --git a/libs/extractor/src/extract_style/extract_import.rs b/libs/extractor/src/extract_style/extract_import.rs index 5c52c82f..f44f5465 100644 --- a/libs/extractor/src/extract_style/extract_import.rs +++ b/libs/extractor/src/extract_style/extract_import.rs @@ -1,6 +1,8 @@ #[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] pub struct ExtractImport { + pub file: String, + /// Where the import is written in the file; the sheet keeps them in this order + pub order: u32, /// import must be global css pub url: String, - pub file: String, } diff --git a/libs/extractor/src/extract_style/extract_layer_order.rs b/libs/extractor/src/extract_style/extract_layer_order.rs new file mode 100644 index 00000000..1d2e0a00 --- /dev/null +++ b/libs/extractor/src/extract_style/extract_layer_order.rs @@ -0,0 +1,6 @@ +/// The cascade layers a file puts styles in, in the order the file first uses them +#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] +pub struct ExtractLayerOrder { + pub file: String, + pub layers: Vec, +} diff --git a/libs/extractor/src/extract_style/extract_static_style.rs b/libs/extractor/src/extract_style/extract_static_style.rs index 1adc78be..b2e73f85 100644 --- a/libs/extractor/src/extract_style/extract_static_style.rs +++ b/libs/extractor/src/extract_style/extract_static_style.rs @@ -40,6 +40,8 @@ pub struct ExtractStaticStyle { pub layer: Option, /// How theme tokens should be resolved when converting to CSS. pub theme_token_resolution: ThemeTokenResolution, + /// Where a global declaration is written in its file; `0` for the others + pub order: u32, } impl Debug for ExtractStaticStyle { @@ -105,6 +107,7 @@ impl ExtractStaticStyle { style_order: None, layer: None, theme_token_resolution: ThemeTokenResolution::CssVariable, + order: 0, } } @@ -137,6 +140,7 @@ impl ExtractStaticStyle { style_order: Some(0), layer: None, theme_token_resolution: ThemeTokenResolution::CssVariable, + order: 0, } } @@ -184,6 +188,11 @@ impl ExtractStaticStyle { self.style_order } + #[must_use] + pub const fn order(&self) -> u32 { + self.order + } + #[must_use] pub const fn theme_token_resolution(&self) -> ThemeTokenResolution { self.theme_token_resolution diff --git a/libs/extractor/src/extract_style/extract_style_value.rs b/libs/extractor/src/extract_style/extract_style_value.rs index 58805775..030a124a 100644 --- a/libs/extractor/src/extract_style/extract_style_value.rs +++ b/libs/extractor/src/extract_style/extract_style_value.rs @@ -1,8 +1,8 @@ use crate::extract_style::{ ExtractStyleProperty, extract_css::ExtractCss, extract_dynamic_style::ExtractDynamicStyle, extract_font_face::ExtractFontFace, extract_import::ExtractImport, - extract_keyframes::ExtractKeyframes, extract_static_style::ExtractStaticStyle, - style_property::StyleProperty, + extract_keyframes::ExtractKeyframes, extract_layer_order::ExtractLayerOrder, + extract_static_style::ExtractStaticStyle, style_property::StyleProperty, }; #[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)] @@ -14,6 +14,7 @@ pub enum ExtractStyleValue { Import(ExtractImport), FontFace(ExtractFontFace), Keyframes(ExtractKeyframes), + LayerOrder(ExtractLayerOrder), } impl ExtractStyleValue { @@ -31,7 +32,8 @@ impl ExtractStyleValue { } ExtractStyleValue::Css(_) | ExtractStyleValue::Import(_) - | ExtractStyleValue::FontFace(_) => None, + | ExtractStyleValue::FontFace(_) + | ExtractStyleValue::LayerOrder(_) => None, } } pub const fn set_style_order(&mut self, order: u8) { @@ -90,14 +92,22 @@ mod tests { assert!(matches!(extracted, Some(StyleProperty::ClassName(_)))); let value = ExtractStyleValue::Css(ExtractCss { - css: String::new(), file: String::new(), + order: 0, + css: String::new(), }); assert!(value.extract(None).is_none()); let value = ExtractStyleValue::Import(ExtractImport { + file: String::new(), + order: 0, url: String::new(), + }); + assert!(value.extract(None).is_none()); + + let value = ExtractStyleValue::LayerOrder(ExtractLayerOrder { file: String::new(), + layers: vec![], }); assert!(value.extract(None).is_none()); } diff --git a/libs/extractor/src/extract_style/mod.rs b/libs/extractor/src/extract_style/mod.rs index e101f8bb..aacf5ec4 100644 --- a/libs/extractor/src/extract_style/mod.rs +++ b/libs/extractor/src/extract_style/mod.rs @@ -4,6 +4,7 @@ pub(super) mod extract_dynamic_style; pub(super) mod extract_font_face; pub(super) mod extract_import; pub(super) mod extract_keyframes; +pub mod extract_layer_order; pub mod extract_static_style; pub mod extract_style_value; pub mod style_property; diff --git a/libs/extractor/src/extractor/extract_global_style_from_expression.rs b/libs/extractor/src/extractor/extract_global_style_from_expression.rs index a4484b90..58c75d59 100644 --- a/libs/extractor/src/extractor/extract_global_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_global_style_from_expression.rs @@ -160,8 +160,9 @@ fn collect_global_styles<'a>( } styles.push(ExtractStyleProp::Static( ExtractStyleValue::Import(ExtractImport { - url: import_url, file: file.to_string(), + order: 0, + url: import_url, }), )); } @@ -170,8 +171,9 @@ fn collect_global_styles<'a>( { styles.push(ExtractStyleProp::Static( ExtractStyleValue::Import(ExtractImport { - url: url.into_owned(), file: file.to_string(), + order: 0, + url: url.into_owned(), }), )); } @@ -181,28 +183,47 @@ fn collect_global_styles<'a>( if let Expression::ArrayExpression(arr) = &o.value { for p in &arr.elements { if let ArrayExpressionElement::ObjectExpression(o) = p { - styles.push(ExtractStyleProp::Static(ExtractStyleValue::FontFace(ExtractFontFace { - properties: o - .properties - .iter() - .filter_map(|p| { - if let ObjectPropertyKind::ObjectProperty(o) = p - && let Some(property_name) = get_str_by_property_key(&o.key) - && let Some(s) = get_string_by_literal_expression(&o.value) - { - let it = disassemble_property(&property_name).map(|p| { - let v = if check_multi_css_optimize(&p) { optimize_multi_css_value(&s) } else { Cow::Borrowed(&*s) }; - if p == "src" { (p.into_owned(), wrap_url(&v).into_owned()) } else { (p.into_owned(), v.into_owned()) } - }); - Some(it.collect::>()) - } else { - None - } - }) - .flatten() - .collect(), - file: file.to_string(), - }))); + let properties = o + .properties + .iter() + .filter_map(|p| { + if let ObjectPropertyKind::ObjectProperty(o) = p + && let Some(property_name) = + get_str_by_property_key(&o.key) + && let Some(s) = + get_string_by_literal_expression(&o.value) + { + let it = disassemble_property(&property_name) + .map(|p| { + let v = if check_multi_css_optimize(&p) + { + optimize_multi_css_value(&s) + } else { + Cow::Borrowed(&*s) + }; + if p == "src" { + ( + p.into_owned(), + wrap_url(&v).into_owned(), + ) + } else { + (p.into_owned(), v.into_owned()) + } + }); + Some(it.collect::>()) + } else { + None + } + }) + .flatten() + .collect(); + styles.push(ExtractStyleProp::Static( + ExtractStyleValue::FontFace(ExtractFontFace { + file: file.to_string(), + order: 0, + properties, + }), + )); } else if let ArrayExpressionElement::TemplateLiteral(t) = p { let css_styles = css_to_style_literal(t, 0, &None) .into_iter() @@ -216,6 +237,7 @@ fn collect_global_styles<'a>( .collect::>(); styles.push(ExtractStyleProp::Static( ExtractStyleValue::FontFace(ExtractFontFace { + order: 0, properties: css_styles .iter() .filter_map(|p| { diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 4b8f7f27..7d6e2ee8 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -1285,6 +1285,7 @@ fn typography_atom(name: &str, level: u8, selector: &Option) -> E style_order: None, layer: None, theme_token_resolution: ThemeTokenResolution::default(), + order: 0, } } diff --git a/libs/extractor/src/extractor/extract_style_from_stylex.rs b/libs/extractor/src/extractor/extract_style_from_stylex.rs index 69ff713a..423ca38e 100644 --- a/libs/extractor/src/extractor/extract_style_from_stylex.rs +++ b/libs/extractor/src/extractor/extract_style_from_stylex.rs @@ -36,6 +36,7 @@ fn raw_static_style<'a>( style_order: None, layer: None, theme_token_resolution: Default::default(), + order: 0, })) } diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 68981f36..0a0dbccd 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -12,6 +12,7 @@ mod module_loader; mod mutations; mod prop_modify_utils; mod source_map; +mod style_collector; mod style_values; mod stylex; mod tailwind; @@ -502,7 +503,7 @@ fn extract_source( }); Ok(ExtractOutput { - styles: visitor.styles, + styles: visitor.styles.into_styles(filename), code: result.code, map, css_file: Some(css_file), diff --git a/libs/extractor/src/snapshots/extractor__tests__create_global_style_collapses_to_a_null_component.snap b/libs/extractor/src/snapshots/extractor__tests__create_global_style_collapses_to_a_null_component.snap index 20f43858..4b196b8f 100644 --- a/libs/extractor/src/snapshots/extractor__tests__create_global_style_collapses_to_a_null_component.snap +++ b/libs/extractor/src/snapshots/extractor__tests__create_global_style_collapses_to_a_null_component.snap @@ -33,8 +33,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: "body{margin:0}", file: "test.tsx", + order: 1, + css: "body{margin:0}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-2.snap index 958a0d6d..4c9e4e2a 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-2.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: `url('/fonts/Roboto-Regular.ttf')`,\n fontWeight: `400`,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: `url('/fonts/Roboto-Regular.ttf')`,\n fontWeight: `400`,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "Roboto", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-3.snap index fe9a5c84..b749618b 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-3.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "Roboto", "src": "url('/fonts/Roboto-Regular.ttf')", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-4.snap index 52400442..5f329369 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-4.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-4.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [`\n font-family: \"Roboto\";\n src: \"url('/fonts/Roboto-Regular.ttf')\";\n font-weight: 400;\n `]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [`\n font-family: \"Roboto\";\n src: \"url('/fonts/Roboto-Regular.ttf')\";\n font-weight: 400;\n `]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "Roboto", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-5.snap index 099e88cc..c11909b7 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-5.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-5.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto Hello\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto Hello\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "\"Roboto Hello\"", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-6.snap index 9a802683..3f6a5242 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-6.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-6.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: undefined,\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: undefined,\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-weight": "400", "src": "url('/fonts/Roboto-Regular.ttf')", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-7.snap index acda00ec..9c2773cb 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-7.snap @@ -1,32 +1,35 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto Regular2\",\n src: \"//fonts/Roboto-Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular\",\n src: \"//fonts/Roboto Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular3\",\n src: \"fonts/Roboto Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular4\",\n src: \"local('fonts/Roboto Regular.ttf')\",\n fontWeight: 400,\n },\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto Regular2\",\n src: \"//fonts/Roboto-Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular\",\n src: \"//fonts/Roboto Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular3\",\n src: \"fonts/Roboto Regular.ttf\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto Regular4\",\n src: \"local('fonts/Roboto Regular.ttf')\",\n fontWeight: 400,\n },\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { - "font-family": "\"Roboto Regular\"", + "font-family": "\"Roboto Regular2\"", "font-weight": "400", - "src": "url(\"//fonts/Roboto Regular.ttf\")", + "src": "url(//fonts/Roboto-Regular.ttf)", }, }, ), FontFace( ExtractFontFace { file: "test.tsx", + order: 2, properties: { - "font-family": "\"Roboto Regular2\"", + "font-family": "\"Roboto Regular\"", "font-weight": "400", - "src": "url(//fonts/Roboto-Regular.ttf)", + "src": "url(\"//fonts/Roboto Regular.ttf\")", }, }, ), FontFace( ExtractFontFace { file: "test.tsx", + order: 3, properties: { "font-family": "\"Roboto Regular3\"", "font-weight": "400", @@ -37,6 +40,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "test.tsx", + order: 4, properties: { "font-family": "\"Roboto Regular4\"", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-8.snap index cd62e520..09f91c25 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-8.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-8.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [{\"url\": \"@devup-ui/core/css/global.css\"}]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [{\"url\": \"@devup-ui/core/css/global.css\"}]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "\"@devup-ui/core/css/global.css\"", file: "test.tsx", + order: 1, + url: "\"@devup-ui/core/css/global.css\"", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-9.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-9.snap index 1594e4a1..4d834269 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-9.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces-9.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [{\"url\": \"@devup-ui/core/css/global.css\", \"query\": \"layer\"}]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [{\"url\": \"@devup-ui/core/css/global.css\", \"query\": \"layer\"}]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "\"@devup-ui/core/css/global.css\" layer", file: "test.tsx", + order: 1, + url: "\"@devup-ui/core/css/global.css\" layer", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces.snap index 7d1e97d2..1cf567e4 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_font_faces.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto2\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"Roboto\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n },\n {\n fontFamily: \"Roboto2\",\n src: \"url('/fonts/Roboto-Regular.ttf')\",\n fontWeight: 400,\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "Roboto", "font-weight": "400", @@ -17,6 +18,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "test.tsx", + order: 2, properties: { "font-family": "Roboto2", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap index 4e66ff5b..0fa2068b 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-2.snap @@ -1,19 +1,21 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\", \"@devup-ui/core/css/global2.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\", \"@devup-ui/core/css/global2.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "@devup-ui/core/css/global.css", file: "test.tsx", + order: 1, + url: "@devup-ui/core/css/global.css", }, ), Import( ExtractImport { - url: "@devup-ui/core/css/global2.css", file: "test.tsx", + order: 2, + url: "@devup-ui/core/css/global2.css", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap index 518e9a7f..89f8c5d6 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports-3.snap @@ -1,19 +1,21 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [`@devup-ui/core/css/global3.css`, `@devup-ui/core/css/global4.css`]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [`@devup-ui/core/css/global3.css`, `@devup-ui/core/css/global4.css`]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "@devup-ui/core/css/global3.css", file: "test.tsx", + order: 1, + url: "@devup-ui/core/css/global3.css", }, ), Import( ExtractImport { - url: "@devup-ui/core/css/global4.css", file: "test.tsx", + order: 2, + url: "@devup-ui/core/css/global4.css", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap index 150dabb2..7feaee66 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_imports.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [\"@devup-ui/core/css/global.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "@devup-ui/core/css/global.css", file: "test.tsx", + order: 1, + url: "@devup-ui/core/css/global.css", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap index dcd24f04..c240f81d 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-5.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`\n div {\n background-color: red;\n color: blue;\n }\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`\n div {\n background-color: red;\n color: blue;\n }\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Css( ExtractCss { - css: "div{background-color:red;color:blue}", file: "test.tsx", + order: 1, + css: "div{background-color:red;color:blue}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-9.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-9.snap index 04921366..2b0b8be9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-9.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_template_literal-9.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`:root {color-scheme: light dark}`\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\n globalCss`:root {color-scheme: light dark}`\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Css( ExtractCss { - css: ":root{color-scheme:light dark}", file: "test.tsx", + order: 1, + css: ":root{color-scheme:light dark}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_wrong_imports.snap b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_wrong_imports.snap index f677156b..d6b7e822 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_wrong_imports.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_global_css_with_wrong_imports.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [1, 2, \"./test.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n imports: [1, 2, \"./test.css\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "./test.css", file: "test.tsx", + order: 1, + url: "./test.css", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization-2.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization-2.snap index c75dd447..8bed2966 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization-2.snap @@ -7,6 +7,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "IconFont", "src": "url(/fonts/icons.woff2)", diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization.snap index 243071df..b84fdc8f 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_multi_value_optimization.snap @@ -7,6 +7,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "\"Custom Font\",\"Fallback Font\",sans-serif", "font-style": "normal", diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax-2.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax-2.snap index ed8403d3..363c86a9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax-2.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"MultiFont\",\n src: \"url('/fonts/multi.woff2')\",\n fontWeight: \"bold\",\n fontDisplay: \"swap\"\n },\n {\n fontFamily: \"AnotherFont\",\n src: \"local('Arial')\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"MultiFont\",\n src: \"url('/fonts/multi.woff2')\",\n fontWeight: \"bold\",\n fontDisplay: \"swap\"\n },\n {\n fontFamily: \"AnotherFont\",\n src: \"local('Arial')\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-display": "swap", "font-family": "MultiFont", @@ -18,6 +19,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "test.tsx", + order: 2, properties: { "font-family": "AnotherFont", "src": "local('Arial')", diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax.snap index bfb9c42d..c0aadd18 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_fontfaces_object_syntax.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"CustomFont\",\n src: \"url('/fonts/custom.woff2')\",\n fontWeight: \"400\",\n fontStyle: \"normal\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"CustomFont\",\n src: \"url('/fonts/custom.woff2')\",\n fontWeight: \"400\",\n fontStyle: \"normal\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "CustomFont", "font-style": "normal", diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_layer_blocks_and_import_objects.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_layer_blocks_and_import_objects.snap index f039363f..6dcb1423 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_layer_blocks_and_import_objects.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_layer_blocks_and_import_objects.snap @@ -89,14 +89,26 @@ ToBTreeSet { ), Import( ExtractImport { - url: "\"a.css\" screen", file: "test.tsx", + order: 1, + url: "\"a.css\" screen", }, ), Import( ExtractImport { + file: "test.tsx", + order: 2, url: "\"b.css\"", + }, + ), + LayerOrder( + ExtractLayerOrder { file: "test.tsx", + layers: [ + "utilities", + "base", + "base.reset", + ], }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_layer_property_extraction.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_layer_property_extraction.snap index 968cb53e..00c16a38 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_layer_property_extraction.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_layer_property_extraction.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".reset-box\": {\n \"@layer\": \"reset\",\n margin: 0,\n padding: 0,\n boxSizing: \"border-box\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".reset-box\": {\n \"@layer\": \"reset\",\n margin: 0,\n padding: 0,\n boxSizing: \"border-box\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -61,6 +61,14 @@ ToBTreeSet { ), }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "reset", + ], + }, + ), }, code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_font_faces.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_font_faces.snap index 3b00b65c..d272e07f 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_font_faces.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_font_faces.snap @@ -1,12 +1,13 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"CustomFont\",\n src: \"url('/fonts/custom.woff2')\",\n fontWeight: \"400\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n fontFaces: [\n {\n fontFamily: \"CustomFont\",\n src: \"url('/fonts/custom.woff2')\",\n fontWeight: \"400\"\n }\n ]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { FontFace( ExtractFontFace { file: "test.tsx", + order: 1, properties: { "font-family": "CustomFont", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_imports.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_imports.snap index 75244afb..b12ed8a3 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_imports.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_imports.snap @@ -1,13 +1,14 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n imports: [\"https://fonts.googleapis.com/css?family=Roboto\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n imports: [\"https://fonts.googleapis.com/css?family=Roboto\"]\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { Import( ExtractImport { - url: "https://fonts.googleapis.com/css?family=Roboto", file: "test.tsx", + order: 1, + url: "https://fonts.googleapis.com/css?family=Roboto", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer-2.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer-2.snap index ff8f0255..3912eb27 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer-2.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n \"*\": {\n \"@layer\": \"reset\",\n boxSizing: \"border-box\"\n },\n body: {\n \"@layer\": \"base\",\n fontFamily: \"sans-serif\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n \"*\": {\n \"@layer\": \"reset\",\n boxSizing: \"border-box\"\n },\n body: {\n \"@layer\": \"base\",\n fontFamily: \"sans-serif\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -42,6 +42,15 @@ ToBTreeSet { ), }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "reset", + "base", + ], + }, + ), }, code: "import \"@devup-ui/core/devup-ui-0.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer.snap index 250a4095..b542c61a 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n \"*\": {\n \"@layer\": \"reset\",\n margin: 0,\n padding: 0\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\nglobalCss({\n \"*\": {\n \"@layer\": \"reset\",\n margin: 0,\n padding: 0\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -42,6 +42,14 @@ ToBTreeSet { ), }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "reset", + ], + }, + ), }, code: "import \"@devup-ui/core/devup-ui-0.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector-2.snap index aa2aa7cd..6a5b7005 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector-2.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".card\": {\n \"@layer\": \"layout\",\n display: \"flex\"\n },\n \".text\": {\n \"@layer\": \"typography\",\n fontSize: \"16px\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".card\": {\n \"@layer\": \"layout\",\n display: \"flex\"\n },\n \".text\": {\n \"@layer\": \"typography\",\n fontSize: \"16px\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -42,6 +42,15 @@ ToBTreeSet { ), }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "layout", + "typography", + ], + }, + ), }, code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector.snap b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector.snap index dd98a9a1..3baa5a8e 100644 --- a/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector.snap +++ b/libs/extractor/src/snapshots/extractor__tests__global_css_with_layer_in_selector.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".button\": {\n \"@layer\": \"components\",\n padding: \"8px\",\n background: \"blue\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from '@devup-ui/core'\nglobalCss({\n \".button\": {\n \"@layer\": \"components\",\n padding: \"8px\",\n background: \"blue\"\n }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -42,6 +42,14 @@ ToBTreeSet { ), }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "components", + ], + }, + ), }, code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__layer_record_places_every_declaration.snap b/libs/extractor/src/snapshots/extractor__tests__layer_record_places_every_declaration.snap index aa462f70..ecc2b97a 100644 --- a/libs/extractor/src/snapshots/extractor__tests__layer_record_places_every_declaration.snap +++ b/libs/extractor/src/snapshots/extractor__tests__layer_record_places_every_declaration.snap @@ -146,6 +146,15 @@ ToBTreeSet { layer: "base", }, ), + LayerOrder( + ExtractLayerOrder { + file: "test.tsx", + layers: [ + "base", + "base.inner", + ], + }, + ), }, code: "import \"@devup-ui/react/devup-ui.css\";\nconst A = ({ style, className, ...rest }) =>
;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__styles_with_no_class_to_switch.snap b/libs/extractor/src/snapshots/extractor__tests__styles_with_no_class_to_switch.snap index 48732dc1..44572394 100644 --- a/libs/extractor/src/snapshots/extractor__tests__styles_with_no_class_to_switch.snap +++ b/libs/extractor/src/snapshots/extractor__tests__styles_with_no_class_to_switch.snap @@ -144,8 +144,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: "body{color:red}", file: "test.tsx", + order: 4, + css: "body{color:red}", }, ), Keyframes( diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_conditional_variables.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_conditional_variables.snap index a8060d1f..8e70793b 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_conditional_variables.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_conditional_variables.snap @@ -26,14 +26,16 @@ ToBTreeSet { ), Css( ExtractCss { - css: ".f{--b:purple;--c:red;}@media(prefers-color-scheme:dark){.f{--b:pink;}}", file: "test.tsx", + order: 1, + css: ":root{--a:white;--b:black;--c:blue;--e:4px;}@media(prefers-color-scheme:dark){:root{--a:teal;--b:white;--c:lightblue;--d:gray;}}@supports(color:oklch(0 0 0)){:root{--c:oklch(0.6 0.2 250);}}@supports(display:grid){@media(prefers-color-scheme:dark){:root{--a:navy;}}}@supports(color:oklch(0 0 0)){@media(prefers-color-scheme:dark){:root{--c:oklch(0.8 0.1 250);}}}", }, ), Css( ExtractCss { - css: ":root{--a:white;--b:black;--c:blue;--e:4px;}@media(prefers-color-scheme:dark){:root{--a:teal;--b:white;--c:lightblue;--d:gray;}}@supports(color:oklch(0 0 0)){:root{--c:oklch(0.6 0.2 250);}}@supports(display:grid){@media(prefers-color-scheme:dark){:root{--a:navy;}}}@supports(color:oklch(0 0 0)){@media(prefers-color-scheme:dark){:root{--c:oklch(0.8 0.1 250);}}}", file: "test.tsx", + order: 2, + css: ".f{--b:purple;--c:red;}@media(prefers-color-scheme:dark){.f{--b:pink;}}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_define_vars_and_create_theme.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_define_vars_and_create_theme.snap index e7f728fa..052c3081 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_define_vars_and_create_theme.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_define_vars_and_create_theme.snap @@ -26,14 +26,16 @@ ToBTreeSet { ), Css( ExtractCss { - css: ".c{--a:navy;}", file: "test.tsx", + order: 1, + css: ":root{--a:blue;--b:grey;}", }, ), Css( ExtractCss { - css: ":root{--a:blue;--b:grey;}", file: "test.tsx", + order: 2, + css: ".c{--a:navy;}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_position_try_and_view_transition_class.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_position_try_and_view_transition_class.snap index 1d231868..0adc7fd7 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_position_try_and_view_transition_class.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_position_try_and_view_transition_class.snap @@ -6,14 +6,16 @@ ToBTreeSet { styles: { Css( ExtractCss { - css: ".b{animation-duration:300ms;}", file: "test.tsx", + order: 1, + css: "@position-try --a{top:0;inset-block-end:auto;}", }, ), Css( ExtractCss { - css: "@position-try --a{top:0;inset-block-end:auto;}", file: "test.tsx", + order: 2, + css: ".b{animation-duration:300ms;}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_join_styles_compiled_elsewhere.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_join_styles_compiled_elsewhere.snap index 2e1b35fa..218611fa 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_props_join_styles_compiled_elsewhere.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_join_styles_compiled_elsewhere.snap @@ -63,8 +63,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: ":root{--b:black;}", file: "test.tsx", + order: 1, + css: ":root{--b:black;}", }, ), Keyframes( diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_theme_apis_with_named_imports.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_theme_apis_with_named_imports.snap index 8313728b..5a428385 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_theme_apis_with_named_imports.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_theme_apis_with_named_imports.snap @@ -16,14 +16,16 @@ ToBTreeSet { ), Css( ExtractCss { - css: ".b{--a:navy;}", file: "test.tsx", + order: 1, + css: ":root{--a:blue;}", }, ), Css( ExtractCss { - css: ":root{--a:blue;}", file: "test.tsx", + order: 2, + css: ".b{--a:navy;}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_theme_contract_and_constants.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_theme_contract_and_constants.snap index 0de4b83d..7d1fb949 100644 --- a/libs/extractor/src/snapshots/extractor__tests__stylex_theme_contract_and_constants.snap +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_theme_contract_and_constants.snap @@ -26,8 +26,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: ".b{--a:navy;}", file: "test.tsx", + order: 1, + css: ".b{--a:navy;}", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__template_interpolations.snap b/libs/extractor/src/snapshots/extractor__tests__template_interpolations.snap index 2594e53f..440251a2 100644 --- a/libs/extractor/src/snapshots/extractor__tests__template_interpolations.snap +++ b/libs/extractor/src/snapshots/extractor__tests__template_interpolations.snap @@ -60,14 +60,16 @@ ToBTreeSet { ), Css( ExtractCss { - css: "a{color:var(--colors-link)}", file: "test.tsx", + order: 1, + css: "body{color:red;padding:4px}", }, ), Css( ExtractCss { - css: "body{color:red;padding:4px}", file: "test.tsx", + order: 2, + css: "a{color:var(--colors-link)}", }, ), Keyframes( diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_all_exports_combined.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_all_exports_combined.snap index 0f8d800f..3489b9d6 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_all_exports_combined.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_all_exports_combined.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"all-combined.css.ts\",\nr#\"import { style, globalStyle, keyframes, createVar, createContainer, layer, fontFace, createGlobalTheme, styleVariants } from '@devup-ui/react'\nexport const myVar = createVar()\nexport const myContainer = createContainer()\nexport const myLayer = layer('components')\nexport const myFont = fontFace({ src: 'local(Arial)' })\nexport const vars = createGlobalTheme(':root', { color: { primary: 'blue' } })\nexport const fade = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nglobalStyle('body', { margin: 0 })\nconst base = style({ padding: 8 })\nexport const buttons = styleVariants({\n primary: [base, { bg: 'blue' }],\n secondary: { bg: 'gray' }\n})\nexport const box = style({ fontFamily: myFont })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"all-combined.css.ts\",\nr\"import { style, globalStyle, keyframes, createVar, createContainer, layer, fontFace, createGlobalTheme, styleVariants } from '@devup-ui/react'\nexport const myVar = createVar()\nexport const myContainer = createContainer()\nexport const myLayer = layer('components')\nexport const myFont = fontFace({ src: 'local(Arial)' })\nexport const vars = createGlobalTheme(':root', { color: { primary: 'blue' } })\nexport const fade = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nglobalStyle('body', { margin: 0 })\nconst base = style({ padding: 8 })\nexport const buttons = styleVariants({\n primary: [base, { bg: 'blue' }],\n secondary: { bg: 'gray' }\n})\nexport const box = style({ fontFamily: myFont })\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -80,13 +80,15 @@ ToBTreeSet { ), Css( ExtractCss { - css: "@layer components-0-2;", file: "all-combined.css.ts", + order: 1, + css: "@layer components-0-2;", }, ), FontFace( ExtractFontFace { file: "all-combined.css.ts", + order: 4, properties: { "font-family": "font-0-3", "src": "local(Arial)", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face-2.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face-2.snap index c66396a1..826e4e91 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face-2.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face-2.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"fonts-multi.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nconst roboto = fontFace({\n src: 'url(\"/fonts/Roboto.woff2\") format(\"woff2\")',\n fontWeight: 400,\n fontStyle: 'normal'\n})\nexport const body = style({\n fontFamily: roboto\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"fonts-multi.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nconst roboto = fontFace({\n src: 'url(\"/fonts/Roboto.woff2\") format(\"woff2\")',\n fontWeight: 400,\n fontStyle: 'normal'\n})\nexport const body = style({\n fontFamily: roboto\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -17,6 +17,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "fonts-multi.css.ts", + order: 1, properties: { "font-family": "font-0-0", "font-style": "normal", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face.snap index 2b8fc7ce..b1cd11ab 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"fonts.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nconst myFont = fontFace({\n src: 'local(\"Comic Sans MS\")'\n})\nexport const text = style({\n fontFamily: myFont\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"fonts.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nconst myFont = fontFace({\n src: 'local(\"Comic Sans MS\")'\n})\nexport const text = style({\n fontFamily: myFont\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -17,6 +17,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "fonts.css.ts", + order: 1, properties: { "font-family": "font-0-0", "src": "local(\"Comic Sans MS\")", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_empty_props.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_empty_props.snap index 02e3a50f..436c21c6 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_empty_props.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_empty_props.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"fontface-minimal.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nexport const minimalFont = fontFace({})\nexport const text = style({ fontFamily: minimalFont })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"fontface-minimal.css.ts\",\nr\"import { fontFace, style } from '@devup-ui/react'\nexport const minimalFont = fontFace({})\nexport const text = style({ fontFamily: minimalFont })\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -17,6 +17,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "fontface-minimal.css.ts", + order: 1, properties: { "font-family": "font-0-0", }, diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_placeholder.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_placeholder.snap index 68e3d398..557a44a2 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_placeholder.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_placeholder.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"fontface-remap.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nexport const customFont = fontFace({\n src: 'url(\"/fonts/custom.woff2\")',\n fontWeight: '400',\n fontDisplay: 'swap'\n})\nexport const secondFont = fontFace({\n src: 'local(\"Helvetica\")'\n})\nexport const text = style({ fontFamily: customFont })\nexport const heading = style({ fontFamily: secondFont })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"fontface-remap.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nexport const customFont = fontFace({\n src: 'url(\"/fonts/custom.woff2\")',\n fontWeight: '400',\n fontDisplay: 'swap'\n})\nexport const secondFont = fontFace({\n src: 'local(\"Helvetica\")'\n})\nexport const text = style({ fontFamily: customFont })\nexport const heading = style({ fontFamily: secondFont })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -27,6 +27,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "fontface-remap.css.ts", + order: 1, properties: { "font-display": "swap", "font-family": "font-0-0", @@ -38,6 +39,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "fontface-remap.css.ts", + order: 2, properties: { "font-family": "font-0-1", "src": "local(\"Helvetica\")", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_with_style.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_with_style.snap index ebc837d8..b66d0bfd 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_with_style.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_font_face_with_style.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"font-usage.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nexport const myFont = fontFace({\n src: 'local(\"Comic Sans MS\")',\n fontWeight: 400\n})\nexport const text = style({\n fontFamily: myFont\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"font-usage.css.ts\",\nr#\"import { fontFace, style } from '@devup-ui/react'\nexport const myFont = fontFace({\n src: 'local(\"Comic Sans MS\")',\n fontWeight: 400\n})\nexport const text = style({\n fontFamily: myFont\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -17,6 +17,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "font-usage.css.ts", + order: 1, properties: { "font-family": "font-0-0", "font-weight": "400", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_imports_combination.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_imports_combination.snap index 3909d8ce..f0635895 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_imports_combination.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_imports_combination.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"imports-combo.css.ts\",\nr#\"import { style, globalStyle, keyframes, fontFace } from '@devup-ui/react'\nexport const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nexport const myFont = fontFace({ src: 'local(Arial)' })\nglobalStyle('body', { margin: 0, fontFamily: myFont })\nexport const animated = style({ animation: fadeIn })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"imports-combo.css.ts\",\nr\"import { style, globalStyle, keyframes, fontFace } from '@devup-ui/react'\nexport const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nexport const myFont = fontFace({ src: 'local(Arial)' })\nglobalStyle('body', { margin: 0, fontFamily: myFont })\nexport const animated = style({ animation: fadeIn })\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -51,6 +51,7 @@ ToBTreeSet { FontFace( ExtractFontFace { file: "imports-combo.css.ts", + order: 3, properties: { "font-family": "font-0-0", "src": "local(Arial)", diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer.snap index 6a8489aa..87685fb4 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"layers.css.ts\",\nr#\"import { layer, style, globalStyle } from '@devup-ui/react'\nexport const reset = layer('reset')\nexport const base = layer('base')\nexport const components = layer('components')\nglobalStyle('*', {\n '@layer': reset,\n margin: 0,\n padding: 0\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"layers.css.ts\",\nr\"import { layer, style, globalStyle } from '@devup-ui/react'\nexport const reset = layer('reset')\nexport const base = layer('base')\nexport const components = layer('components')\nglobalStyle('*', {\n '@layer': reset,\n margin: 0,\n padding: 0\n})\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -44,8 +44,17 @@ ToBTreeSet { ), Css( ExtractCss { + file: "layers.css.ts", + order: 1, css: "@layer reset-0-0,base-0-1,components-0-2;", + }, + ), + LayerOrder( + ExtractLayerOrder { file: "layers.css.ts", + layers: [ + "reset-0-0", + ], }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_and_container.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_and_container.snap index 96971359..4b1c9405 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_and_container.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_and_container.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"layer-container.css.ts\",\nr#\"import { layer, createContainer, style } from '@devup-ui/react'\nexport const resetLayer = layer('reset')\nexport const baseLayer = layer('base')\nexport const myContainer = createContainer()\nexport const containerStyle = style({\n containerName: myContainer,\n containerType: 'inline-size'\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"layer-container.css.ts\",\nr\"import { layer, createContainer, style } from '@devup-ui/react'\nexport const resetLayer = layer('reset')\nexport const baseLayer = layer('base')\nexport const myContainer = createContainer()\nexport const containerStyle = style({\n containerName: myContainer,\n containerType: 'inline-size'\n})\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -26,8 +26,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: "@layer reset-0-0,base-0-1;", file: "layer-container.css.ts", + order: 1, + css: "@layer reset-0-0,base-0-1;", }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_records.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_records.snap index 7a5d2d64..95537b06 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_records.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_layer_records.snap @@ -95,8 +95,18 @@ ToBTreeSet { ), Css( ExtractCss { + file: "layers.css.ts", + order: 1, css: "@layer reset-0-0,components-0-1;", + }, + ), + LayerOrder( + ExtractLayerOrder { file: "layers.css.ts", + layers: [ + "components-0-1", + "reset-0-0", + ], }, ), }, diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_non_exported_styles.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_non_exported_styles.snap index 113b631a..fad0de17 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_non_exported_styles.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_non_exported_styles.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"mixed-exports.css.ts\",\nr#\"import { style, keyframes, createVar, createContainer, layer } from '@devup-ui/react'\nconst internalStyle = style({ padding: 4 })\nconst internalKeyframe = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nconst internalVar = createVar()\nconst internalContainer = createContainer()\nconst internalLayer = layer('internal')\nexport const publicStyle = style({ margin: 8 })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"mixed-exports.css.ts\",\nr\"import { style, keyframes, createVar, createContainer, layer } from '@devup-ui/react'\nconst internalStyle = style({ padding: 4 })\nconst internalKeyframe = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nconst internalVar = createVar()\nconst internalContainer = createContainer()\nconst internalLayer = layer('internal')\nexport const publicStyle = style({ margin: 8 })\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -26,8 +26,9 @@ ToBTreeSet { ), Css( ExtractCss { - css: "@layer internal-0-2;", file: "mixed-exports.css.ts", + order: 1, + css: "@layer internal-0-2;", }, ), Keyframes( diff --git a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_selector_refs_triggers_with_classes.snap b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_selector_refs_triggers_with_classes.snap index 61195fd1..c15159a1 100644 --- a/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_selector_refs_triggers_with_classes.snap +++ b/libs/extractor/src/snapshots/extractor__tests__vanilla_extract_selector_refs_triggers_with_classes.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"refs.css.ts\",\nr#\"import { style, globalStyle, keyframes, createVar, fontFace, createContainer, layer } from '@devup-ui/react'\nexport const colorVar = createVar()\nexport const myContainer = createContainer()\nexport const myLayer = layer('ui')\nexport const myFont = fontFace({ src: 'local(Arial)' })\nexport const fade = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nexport const parent = style({ display: 'flex' })\nexport const child = style({\n selectors: {\n [`${parent}:hover &`]: { color: 'red' }\n }\n})\nglobalStyle('body', { margin: 0 })\n\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"refs.css.ts\",\nr\"import { style, globalStyle, keyframes, createVar, fontFace, createContainer, layer } from '@devup-ui/react'\nexport const colorVar = createVar()\nexport const myContainer = createContainer()\nexport const myLayer = layer('ui')\nexport const myFont = fontFace({ src: 'local(Arial)' })\nexport const fade = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })\nexport const parent = style({ display: 'flex' })\nexport const child = style({\n selectors: {\n [`${parent}:hover &`]: { color: 'red' }\n }\n})\nglobalStyle('body', { margin: 0 })\n\",\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n}).unwrap())" --- ToBTreeSet { styles: { @@ -47,13 +47,15 @@ ToBTreeSet { ), Css( ExtractCss { - css: "@layer ui-0-2;", file: "refs.css.ts", + order: 1, + css: "@layer ui-0-2;", }, ), FontFace( ExtractFontFace { file: "refs.css.ts", + order: 3, properties: { "font-family": "font-0-3", "src": "local(Arial)", diff --git a/libs/extractor/src/style_collector.rs b/libs/extractor/src/style_collector.rs new file mode 100644 index 00000000..b11481e6 --- /dev/null +++ b/libs/extractor/src/style_collector.rs @@ -0,0 +1,198 @@ +use css::style_selector::StyleSelector; +use rustc_hash::FxHashSet; + +use crate::extract_style::{ + extract_layer_order::ExtractLayerOrder, extract_style_value::ExtractStyleValue, +}; + +/// The styles a file writes. Global rules are numbered in the order the file +/// writes them, since that order decides which of them wins, and layers are +/// listed in the order the file first uses them, since that order decides +/// which layer wins. +#[derive(Default)] +pub struct StyleCollector { + styles: FxHashSet, + /// The number the last global rule got; `0` stays for styles without one + written: u32, + layers: Vec, +} + +impl StyleCollector { + pub fn insert(&mut self, mut style: ExtractStyleValue) { + match &mut style { + ExtractStyleValue::Css(css) => css.order = self.next_order(), + ExtractStyleValue::Import(import) => import.order = self.next_order(), + ExtractStyleValue::FontFace(font_face) => font_face.order = self.next_order(), + ExtractStyleValue::Static(style) => { + if matches!(style.selector, Some(StyleSelector::Global(..))) { + style.order = self.next_order(); + } + self.use_layer(style.layer.as_deref()); + } + ExtractStyleValue::Dynamic(style) => self.use_layer(style.layer()), + ExtractStyleValue::Typography(_) + | ExtractStyleValue::Keyframes(_) + | ExtractStyleValue::LayerOrder(_) => {} + } + self.styles.insert(style); + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.styles.is_empty() + } + + /// The styles of `file`, with the order of its layers when it uses any + #[must_use] + pub fn into_styles(mut self, file: &str) -> FxHashSet { + if !self.layers.is_empty() { + self.styles + .insert(ExtractStyleValue::LayerOrder(ExtractLayerOrder { + file: file.to_string(), + layers: self.layers, + })); + } + self.styles + } + + const fn next_order(&mut self) -> u32 { + self.written += 1; + self.written + } + + /// A nested layer comes after the layers it sits in, as CSS declares them + fn use_layer(&mut self, layer: Option<&str>) { + let Some(layer) = layer else { + return; + }; + let ends = layer + .match_indices('.') + .map(|(index, _)| index) + .chain([layer.len()]); + for end in ends { + let name = &layer[..end]; + if !self.layers.iter().any(|used| used == name) { + self.layers.push(name.to_string()); + } + } + } +} + +impl Extend for StyleCollector { + fn extend>(&mut self, styles: I) { + for style in styles { + self.insert(style); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::extract_style::{ + extract_css::ExtractCss, extract_dynamic_style::ExtractDynamicStyle, + extract_font_face::ExtractFontFace, extract_import::ExtractImport, + extract_keyframes::ExtractKeyframes, extract_static_style::ExtractStaticStyle, + }; + + fn global(property: &str, value: &str) -> ExtractStyleValue { + ExtractStyleValue::Static(ExtractStaticStyle::new( + property, + value, + 0, + Some(StyleSelector::Global( + "body".to_string(), + "a.tsx".to_string(), + )), + )) + } + + #[test] + #[allow(clippy::literal_string_with_formatting_args)] + fn numbers_global_rules_in_written_order_and_lists_layers() { + let mut collector = StyleCollector::default(); + assert!(collector.is_empty()); + collector.extend([ + ExtractStyleValue::Import(ExtractImport { + file: "a.tsx".to_string(), + order: 0, + url: "z.css".to_string(), + }), + global("color", "red"), + ExtractStyleValue::Css(ExtractCss { + file: "a.tsx".to_string(), + order: 0, + css: "body{color:blue}".to_string(), + }), + ExtractStyleValue::FontFace(ExtractFontFace { + file: "a.tsx".to_string(), + order: 0, + properties: Default::default(), + }), + ExtractStyleValue::Static(ExtractStaticStyle::new_with_layer( + "color", + "red", + 0, + None, + Some("reset".to_string()), + )), + ExtractStyleValue::Dynamic(ExtractDynamicStyle::new("color", 0, "--a", None)), + ExtractStyleValue::Typography("body".to_string()), + ExtractStyleValue::Keyframes(ExtractKeyframes::default()), + global("color", "blue"), + ]); + for property in ["margin", "padding"] { + let mut layered = ExtractDynamicStyle::new(property, 0, "--b", None); + layered.layer = Some("base.inner".to_string()); + collector.insert(ExtractStyleValue::Dynamic(layered)); + } + assert!(!collector.is_empty()); + + let styles = collector.into_styles("a.tsx"); + let mut orders: Vec<(u32, String)> = styles + .iter() + .filter_map(|style| match style { + ExtractStyleValue::Import(import) => Some((import.order, import.url.clone())), + ExtractStyleValue::Css(css) => Some((css.order, css.css.clone())), + ExtractStyleValue::FontFace(font_face) => { + Some((font_face.order, "@font-face".to_string())) + } + ExtractStyleValue::Static(style) => Some(( + style.order(), + format!("{}:{}", style.property(), style.value()), + )), + _ => None, + }) + .collect(); + orders.sort(); + assert_eq!( + orders, + [ + (0, "color:red".to_string()), + (1, "z.css".to_string()), + (2, "color:red".to_string()), + (3, "body{color:blue}".to_string()), + (4, "@font-face".to_string()), + (5, "color:blue".to_string()), + ] + ); + assert!( + styles.contains(&ExtractStyleValue::LayerOrder(ExtractLayerOrder { + file: "a.tsx".to_string(), + layers: vec![ + "reset".to_string(), + "base".to_string(), + "base.inner".to_string() + ], + })) + ); + } + + #[test] + fn leaves_the_layer_order_out_without_layers() { + let mut collector = StyleCollector::default(); + collector.insert(global("color", "red")); + let styles = collector.into_styles("a.tsx"); + assert_eq!(styles.len(), 1); + } +} diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 93846123..c48e7803 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -24,6 +24,7 @@ use crate::extractor::{ }; use crate::gen_class_name::{gen_class_names, merge_expression_for_class_name}; use crate::prop_modify_utils::{convert_class_name, modify_prop_object, modify_props}; +use crate::style_collector::StyleCollector; use crate::stylex::{ StylexDynamicInfo, StylexFunction, StylexNamespaceValue, create_theme_class, css_variable_block, css_variable_rules, define_vars_variable, variable_values, @@ -130,7 +131,7 @@ pub struct DevupVisitor<'a> { compat_package: String, split_filename: Option, pub css_files: Vec, - pub styles: FxHashSet, + pub styles: StyleCollector, /// Styles the file writes that cannot be extracted at build time, by the /// offset of the code each is about pub errors: Vec<(u32, String)>, @@ -311,7 +312,7 @@ impl<'a> DevupVisitor<'a> { package: package.to_string(), compat_package: format!("{package}/compat"), css_files, - styles: FxHashSet::default(), + styles: StyleCollector::default(), errors: Vec::new(), import_object: None, jsx_object: None, @@ -1390,8 +1391,9 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { let css = css_variable_rules(":root", &variables); if !css.is_empty() { self.styles.insert(ExtractStyleValue::Css(ExtractCss { - css, file: self.filename.clone(), + order: 0, + css, })); } self.stylex_pending_vars = Some(contract); @@ -1446,8 +1448,9 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { let css = css_variable_rules(&format!(".{class_name}"), &variables); if !css.is_empty() { self.styles.insert(ExtractStyleValue::Css(ExtractCss { - css, file: self.filename.clone(), + order: 0, + css, })); } self.stylex_pending_theme_class = Some(class_name.clone()); @@ -1496,8 +1499,9 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { css_variable_block(&format!(".{name}"), &declarations) }; self.styles.insert(ExtractStyleValue::Css(ExtractCss { - css, file: self.filename.clone(), + order: 0, + css, })); } *it = Expression::new_string_literal( @@ -1881,8 +1885,9 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { let optimized_css = optimize_css_block(&build_css_str()); if !optimized_css.is_empty() { let css = ExtractStyleValue::Css(ExtractCss { - css: optimized_css, file: self.filename.clone(), + order: 0, + css: optimized_css, }); self.styles.insert(css); } diff --git a/libs/sheet/src/lib.rs b/libs/sheet/src/lib.rs index 9a989df8..8391b0c9 100644 --- a/libs/sheet/src/lib.rs +++ b/libs/sheet/src/lib.rs @@ -78,7 +78,8 @@ fn global_selector_group(selector: &str) -> (bool, u8) { }) } -type GlobalProp<'a> = (u8, &'a str, &'a StyleSheetProperty); +/// (level, selector, file the rule is written in, property) +type GlobalProp<'a> = (u8, &'a str, &'a str, &'a StyleSheetProperty); #[derive(Debug, Hash, Eq, PartialEq, Deserialize, Serialize, Clone)] #[serde(rename_all = "camelCase")] @@ -97,6 +98,13 @@ pub struct StyleSheetProperty { /// Declaration expanded from a conditional `typography` preset #[serde(rename = "t", default, skip_serializing_if = "std::ops::Not::not")] pub typography: bool, + /// Where a global declaration is written in its file + #[serde(rename = "o", default, skip_serializing_if = "is_zero")] + pub order: u32, +} + +const fn is_zero(order: &u32) -> bool { + *order == 0 } #[derive(Debug, Hash, Eq, PartialEq, Deserialize, Serialize)] @@ -259,15 +267,18 @@ pub struct StyleSheet { #[serde(deserialize_with = "deserialize_btree_map_u8", default)] pub properties: BTreeMap, #[serde(default)] - pub css: BTreeMap>, + pub css: BTreeMap>, #[serde(default)] pub keyframes: KeyframesMap, #[serde(default)] pub global_css_files: BTreeSet, #[serde(default)] - pub imports: BTreeMap>, + pub imports: BTreeMap>, + #[serde(default)] + pub font_faces: BTreeMap>>, + /// The cascade layers of each file, in the order the file first uses them #[serde(default)] - pub font_faces: BTreeMap>>, + pub layer_orders: BTreeMap>, #[serde(skip)] pub theme: Theme, } @@ -319,6 +330,7 @@ impl StyleSheet { selector: selector.cloned(), layer: layer.map(ToString::to_string), typography: false, + order: 0, }, ) } @@ -371,7 +383,9 @@ impl StyleSheet { Some(bucket) => bucket, None => self.imports.entry(file.to_string()).or_default(), }; - bucket.insert(import.to_string()); + if !bucket.iter().any(|written| written == import) { + bucket.push(import.to_string()); + } } pub fn add_font_face(&mut self, file: &str, properties: &BTreeMap) { @@ -384,7 +398,9 @@ impl StyleSheet { Some(bucket) => bucket, None => self.font_faces.entry(file.to_string()).or_default(), }; - bucket.insert(properties.clone()); + if !bucket.contains(properties) { + bucket.push(properties.clone()); + } } pub fn add_css(&mut self, file: &str, css: &str) -> bool { @@ -397,9 +413,13 @@ impl StyleSheet { Some(bucket) => bucket, None => self.css.entry(file.to_string()).or_default(), }; - bucket.insert(StyleSheetCss { + if bucket.iter().any(|written| written.css == css) { + return false; + } + bucket.push(StyleSheetCss { css: css.to_string(), - }) + }); + true } pub fn add_keyframes( @@ -439,6 +459,7 @@ impl StyleSheet { // like `css`/`font_faces`; clear them so an @import removed from source // does not linger across re-extraction (HMR). self.imports.remove(file); + self.layer_orders.remove(file); // `file` is the RAW source filename (globalCss is per-source-file). Atoms // were bucketed by canonical(file) in update_styles, so global-selector // atom removal must read from the canonical bucket while still matching @@ -502,6 +523,9 @@ impl StyleSheet { Some(filename) }; let bucket_scope = if single_css { None } else { Some(filename) }; + let mut rules: Vec<(&str, u32, &str)> = Vec::new(); + let mut imports: Vec<(&str, u32, &str)> = Vec::new(); + let mut font_faces: Vec<(&str, u32, &BTreeMap)> = Vec::new(); for style in styles { match style { // A conditional `typography` preset: its class is the atom, and the @@ -548,6 +572,7 @@ impl StyleSheet { |layer| format!("{layer}.{TYPOGRAPHY_LAYER}"), )), typography: true, + order: 0, }, ) { collected = true; @@ -596,15 +621,19 @@ impl StyleSheet { } }; - if self.add_property_with_layer( - &class_name, - st.property(), + if self.insert_property( st.level(), - &resolved_value, - st.selector(), st.style_order(), bucket_scope, - st.layer(), + StyleSheetProperty { + class_name, + property: st.property().to_string(), + value: resolved_value.into_owned(), + selector: st.selector().cloned(), + layer: st.layer().map(ToString::to_string), + typography: false, + order: st.order(), + }, ) { collected = true; if st.style_order() == Some(0) { @@ -684,22 +713,56 @@ impl StyleSheet { collected = true; } } - ExtractStyleValue::Css(cs) => { - if self.add_css(&cs.file, &cs.css) { - // update global css - updated_base_style = true; - } - } + ExtractStyleValue::Css(cs) => rules.push((&cs.file, cs.order, &cs.css)), ExtractStyleValue::Typography(_) => {} - ExtractStyleValue::Import(st) => { - self.add_import(&st.file, &st.url); - } + ExtractStyleValue::Import(st) => imports.push((&st.file, st.order, &st.url)), ExtractStyleValue::FontFace(font) => { - self.add_font_face(&font.file, &font.properties); + font_faces.push((&font.file, font.order, &font.properties)); + } + ExtractStyleValue::LayerOrder(order) => { + if self.layer_orders.get(&order.file) != Some(&order.layers) { + self.global_css_files.insert(order.file.clone()); + self.layer_orders + .insert(order.file.clone(), order.layers.clone()); + updated_base_style = true; + } } } } - (collected, updated_base_style) + // Each file's global rules, imports and font faces in the order it writes them + rules.sort_unstable(); + imports.sort_unstable(); + font_faces.sort_unstable(); + let rules_changed = replace_by_file( + &mut self.css, + &mut self.global_css_files, + rules.into_iter().map(|(file, _, css)| { + ( + file, + StyleSheetCss { + css: css.to_string(), + }, + ) + }), + ); + let imports_changed = replace_by_file( + &mut self.imports, + &mut self.global_css_files, + imports + .into_iter() + .map(|(file, _, url)| (file, url.to_string())), + ); + let font_faces_changed = replace_by_file( + &mut self.font_faces, + &mut self.global_css_files, + font_faces + .into_iter() + .map(|(file, _, properties)| (file, properties.clone())), + ); + ( + collected, + updated_base_style || rules_changed || imports_changed || font_faces_changed, + ) } #[must_use] @@ -882,8 +945,8 @@ impl StyleSheet { continue; } match &prop.selector { - Some(StyleSelector::Global(selector, _)) => { - global_props.push((*level, selector.as_str(), prop)); + Some(StyleSelector::Global(selector, file)) => { + global_props.push((*level, selector.as_str(), file.as_str(), prop)); } Some(StyleSelector::At { kind, @@ -1031,19 +1094,23 @@ impl StyleSheet { fn write_global_props(&self, css: &mut String, mut global_props: Vec>) { // Same order as class rules: selector group before breakpoint level, so a - // `:hover` set at a wider breakpoint still precedes `:active`. + // `:hover` set at a wider breakpoint still precedes `:active`. Within + // them each file keeps the order it writes its declarations in, as the + // later of two equally specific ones wins. global_props.sort_by(|a, b| { global_selector_group(a.1) .cmp(&global_selector_group(b.1)) .then_with(|| a.0.cmp(&b.0)) + .then_with(|| a.2.cmp(b.2)) + .then_with(|| a.3.order.cmp(&b.3.order)) .then_with(|| a.1.cmp(b.1)) - .then_with(|| a.2.property.cmp(&b.2.property)) - .then_with(|| a.2.value.cmp(&b.2.value)) + .then_with(|| a.3.property.cmp(&b.3.property)) + .then_with(|| a.3.value.cmp(&b.3.value)) }); let mut open_level: Option = None; let mut open_selector: Option<&str> = None; - for (level, selector, prop) in global_props { + for (level, selector, _, prop) in global_props { if open_level != Some(level) { if open_selector.take().is_some() { css.push('}'); @@ -1117,7 +1184,12 @@ impl StyleSheet { pub fn create_css(&self, filename: Option<&str>, import_main_css: bool) -> String { let mut css = String::with_capacity(4096); css.push_str(Self::create_header()); - for import in self.imports.values().flatten() { + // An import written again takes effect where it is written last + let imports: Vec<&String> = self.imports.values().flatten().collect(); + for (index, import) in imports.iter().enumerate() { + if imports[index + 1..].contains(import) { + continue; + } if import.starts_with('"') { push_fmt!(&mut css, "@import {import};"); } else { @@ -1196,24 +1268,24 @@ impl StyleSheet { } // One source file extracted under multiple passes (e.g. Next // server + client compilations) registers identical @font-face rules - // under multiple file keys; emit each distinct rule only once. - let mut seen_font_faces: BTreeSet<&BTreeMap> = BTreeSet::new(); - for font_faces in self.font_faces.values() { - for font_face in font_faces { - if !seen_font_faces.insert(font_face) { - continue; - } - css.push_str("@font-face{"); - let mut first = true; - for (key, value) in font_face { - if !first { - css.push(';'); - } - first = false; - push_fmt!(&mut css, "{key}:{value}"); + // under multiple file keys; emit each distinct rule only once, where it + // is written last, as the last of equal rules is the one used. + let font_faces: Vec<&BTreeMap> = + self.font_faces.values().flatten().collect(); + for (index, font_face) in font_faces.iter().enumerate() { + if font_faces[index + 1..].contains(font_face) { + continue; + } + css.push_str("@font-face{"); + let mut first = true; + for (key, value) in *font_face { + if !first { + css.push(';'); } - css.push('}'); + first = false; + push_fmt!(&mut css, "{key}:{value}"); } + css.push('}'); } // global css @@ -1230,24 +1302,26 @@ impl StyleSheet { push_fmt!(&mut css, "@layer b{{{base_css}}}"); } - // Generate @layer declarations and wrapped styles for custom layers - if !layered_styles.is_empty() { - // Add layer declarations - css.push_str("@layer "); - let mut first = true; - for name in layered_styles.keys() { - if !first { - css.push(','); - } - first = false; - css.push_str(name); - } - css.push(';'); - - for (layer_name, layer_css) in layered_styles { - push_fmt!(&mut css, "@layer {layer_name}{{{layer_css}}}"); + // Declare the custom layers in the order files first use them, as the + // later layer wins, then any others; then their styles + let mut layer_names: Vec<&str> = Vec::new(); + for name in self + .layer_orders + .values() + .flatten() + .map(String::as_str) + .chain(layered_styles.keys().map(String::as_str)) + { + if !layer_names.contains(&name) { + layer_names.push(name); } } + if !layer_names.is_empty() { + push_fmt!(&mut css, "@layer {};", layer_names.join(",")); + } + for (layer_name, layer_css) in &layered_styles { + push_fmt!(&mut css, "@layer {layer_name}{{{layer_css}}}"); + } // Atom hoisting: emit shared (hoisted) order!=0 atoms into the global // stylesheet, aggregated across every file and deduplicated by atom // identity (class_name). @@ -1368,6 +1442,28 @@ impl StyleSheet { } } +/// Put the entries of each file written in `entries` in place of the ones it +/// had, and tell whether any changed +fn replace_by_file<'a, T: PartialEq>( + map: &mut BTreeMap>, + global_css_files: &mut BTreeSet, + entries: impl Iterator, +) -> bool { + let mut by_file: BTreeMap<&str, Vec> = BTreeMap::new(); + for (file, entry) in entries { + by_file.entry(file).or_default().push(entry); + } + let mut changed = false; + for (file, entries) in by_file { + global_css_files.insert(file.to_string()); + if map.get(file) != Some(&entries) { + map.insert(file.to_string(), entries); + changed = true; + } + } + changed +} + #[cfg(test)] #[allow(clippy::expect_used, clippy::unwrap_used)] mod tests { @@ -3122,6 +3218,7 @@ mod tests { selector: None, layer: None, typography: false, + order: 0, }; assert_eq!(make("color", "red").cmp(&make("color", "red")), Equal); assert!(make("color", "red") < make("color", "white")); @@ -3140,6 +3237,7 @@ mod tests { selector, layer: None, typography: false, + order: 0, }; let hover = || Some(StyleSelector::Selector("&:hover".to_string())); @@ -3182,6 +3280,9 @@ mod tests { assert!(sheet.add_css("test.tsx", "html { color:red }")); assert!(sheet.add_css("test.tsx", "body { color:blue }")); + assert!(!sheet.add_css("test.tsx", "html { color:red }")); + sheet.add_import("test.tsx", "base.css"); + sheet.add_font_face("test.tsx", &first_font); assert_eq!(sheet.properties["test.tsx"].len(), 1); assert_eq!(sheet.imports["test.tsx"].len(), 2); @@ -3496,12 +3597,17 @@ mod tests { ), ( "
", - ".c0{color:red}@layer base{.c1{color:blue}.c2{padding:4px}@media(min-width:768px){.c3{padding:8px}}.c4:hover{color:green}}@layer base.c5{.c6{margin:4px}}", + "@layer base,base.c0;.c1{color:red}@layer base{.c2{color:blue}.c3{padding:4px}@media(min-width:768px){.c4{padding:8px}}.c5:hover{color:green}}@layer base.c0{.c6{margin:4px}}", + ), + // Layers are declared in the order the file first uses them. + ( + "
", + "@layer reset,base,alpha;@layer alpha{.c0{margin:4px}}@layer base{.c1{color:blue}}@layer reset{.c2{color:red}}", ), // Layered and unlayered declarations keep apart, dynamic ones included. ( "const A = styled.div({ color: 'red', width: w, '@layer': { base: { color: 'red', width: v } } })", - ".c0{color:red}.c1{width:var(--c)}@layer base{.c2{color:red}.c3{width:var(--f)}}", + "@layer base;.c0{color:red}.c1{width:var(--c)}@layer base{.c2{color:red}.c3{width:var(--f)}}", ), ] { assert_eq!(pipeline_css(Theme::default(), source), expected, "{source}"); @@ -3650,11 +3756,71 @@ mod tests { sheet.update_styles(&output.styles, "global.tsx", true); let css = sheet.create_css(None, false); assert!( - css.contains("body{border:1px solid var(--line-100);color:var(--text)}"), + css.contains("body{color:var(--text);border:1px solid var(--line-100)}"), "{css}" ); } + #[test] + #[serial] + fn test_global_rules_keep_written_order() { + for (source, expected) in [ + ( + "globalCss({ imports: ['z.css', 'a.css', 'z.css'] })\nglobalCss`body{color:red}`\nglobalCss`body{color:blue}`\nglobalCss`body{color:red}`\nglobalCss({ fontFaces: [{ fontFamily: 'B', src: 'b.woff2' }, { fontFamily: 'A', src: 'a.woff2' }, { fontFamily: 'B', src: 'b.woff2' }] })", + "@import \"a.c0\";@import \"z.c0\";@font-face{font-family:A;src:url(a.c1)}@font-face{font-family:B;src:url(b.c1)}body{color:red}body{color:blue}body{color:red}", + ), + ( + "globalCss({ body: { color: 'red' } })\nglobalCss({ body: { color: 'blue' } })\nglobalCss({ '.z': { color: 'red' }, '.a': { color: 'blue' } })", + "@layer b;@layer b{body{color:red;color:blue}.c0{color:red}.c1{color:blue}}", + ), + ( + "globalCss({ '@layer zz': { p: { m: 0 } }, '@layer aa': { p: { m: 1 } } })", + "@layer b;@layer zz,aa;@layer aa{p{margin:4px}}@layer zz{p{margin:0}}", + ), + ] { + assert_eq!(pipeline_css(Theme::default(), source), expected, "{source}"); + } + } + + #[test] + #[serial] + fn test_global_rules_and_layers_update_per_file() { + reset_class_map(); + reset_file_map(); + let option = ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: true, + import_main_css: false, + import_aliases: std::collections::HashMap::new(), + }; + let mut sheet = StyleSheet::default(); + let b = extract( + "b.tsx", + "import {globalCss} from '@devup-ui/core';globalCss({ '@layer base': { p: { m: 0 } } });globalCss`@import \"b.css\";`;globalCss({ imports: ['b.css'], fontFaces: [{ fontFamily: 'B', src: 'b.woff2' }] })", + option.clone(), + ) + .unwrap(); + let a = extract( + "a.tsx", + "import {globalCss} from '@devup-ui/core';globalCss({ '@layer reset': { p: { m: 0 } }, '@layer base': { p: { m: 1 } } })", + option, + ) + .unwrap(); + assert!(sheet.update_styles(&b.styles, "b.tsx", true).1); + assert!(sheet.update_styles(&a.styles, "a.tsx", true).1); + assert!(!sheet.update_styles(&b.styles, "b.tsx", true).1); + assert!(!sheet.update_styles(&a.styles, "a.tsx", true).1); + assert_eq!(sheet.layer_orders["a.tsx"], ["reset", "base"]); + let css = sheet.create_css(None, false); + assert!(css.contains("@layer reset,base;"), "{css}"); + + assert!(sheet.rm_global_css("a.tsx", true)); + assert!(!sheet.layer_orders.contains_key("a.tsx")); + let css = sheet.create_css(None, false); + assert!(css.contains("@layer base;"), "{css}"); + } + #[test] #[serial] fn test_rm_global_css_drops_global_at_rules() { diff --git a/libs/sheet/src/snapshots/sheet__tests__deserialize.snap b/libs/sheet/src/snapshots/sheet__tests__deserialize.snap index bc58b6e6..64256e77 100644 --- a/libs/sheet/src/snapshots/sheet__tests__deserialize.snap +++ b/libs/sheet/src/snapshots/sheet__tests__deserialize.snap @@ -14,6 +14,7 @@ StyleSheet { selector: None, layer: None, typography: false, + order: 0, }, }, }, @@ -24,6 +25,7 @@ StyleSheet { global_css_files: {}, imports: {}, font_faces: {}, + layer_orders: {}, theme: Theme { colors: {}, color_scheme: {}, diff --git a/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap b/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap index d5ecdded..ecf6f035 100644 --- a/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap +++ b/packages/components/src/components/Button/__tests__/__snapshots__/index.browser.test.tsx.snap @@ -201,7 +201,7 @@ exports[`Button should render loading spinner when loading is true 1`] = `