From 20e72f74b5a9d40bdbe425358a8db5a1831663cd Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Wed, 30 Sep 2026 20:54:54 +0900 Subject: [PATCH 1/3] fix(sheet): keep global CSS, imports and cascade layers in written order Global rules, @import and @font-face were stored in sorted sets and custom layers were declared alphabetically, so a later rule or layer could lose to an earlier one. The extractor now numbers global rules and records the first-use order of layers; the sheet emits them in that order. Refs #685 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .changepacks/changepack_log_global_order.json | 7 + .../src/extract_style/extract_css.rs | 4 +- .../src/extract_style/extract_font_face.rs | 2 + .../src/extract_style/extract_import.rs | 4 +- .../src/extract_style/extract_layer_order.rs | 6 + .../src/extract_style/extract_static_style.rs | 9 + .../src/extract_style/extract_style_value.rs | 18 +- libs/extractor/src/extract_style/mod.rs | 1 + .../extract_global_style_from_expression.rs | 8 +- .../extract_style_from_expression.rs | 1 + .../extractor/extract_style_from_stylex.rs | 1 + libs/extractor/src/lib.rs | 3 +- ...l_style_collapses_to_a_null_component.snap | 3 +- ..._extract_global_css_with_font_faces-2.snap | 3 +- ..._extract_global_css_with_font_faces-3.snap | 3 +- ..._extract_global_css_with_font_faces-4.snap | 3 +- ..._extract_global_css_with_font_faces-5.snap | 3 +- ..._extract_global_css_with_font_faces-6.snap | 3 +- ..._extract_global_css_with_font_faces-7.snap | 14 +- ..._extract_global_css_with_font_faces-8.snap | 5 +- ..._extract_global_css_with_font_faces-9.snap | 5 +- ...s__extract_global_css_with_font_faces.snap | 4 +- ...ts__extract_global_css_with_imports-2.snap | 8 +- ...ts__extract_global_css_with_imports-3.snap | 8 +- ...ests__extract_global_css_with_imports.snap | 5 +- ...ct_global_css_with_template_literal-5.snap | 5 +- ...ct_global_css_with_template_literal-9.snap | 5 +- ...extract_global_css_with_wrong_imports.snap | 5 +- ..._fontfaces_multi_value_optimization-2.snap | 1 + ...ss_fontfaces_multi_value_optimization.snap | 1 + ..._global_css_fontfaces_object_syntax-2.snap | 4 +- ...s__global_css_fontfaces_object_syntax.snap | 3 +- ...l_css_layer_blocks_and_import_objects.snap | 14 +- ..._global_css_layer_property_extraction.snap | 10 +- ...or__tests__global_css_with_font_faces.snap | 3 +- ...actor__tests__global_css_with_imports.snap | 5 +- ...actor__tests__global_css_with_layer-2.snap | 11 +- ...tractor__tests__global_css_with_layer.snap | 10 +- ...__global_css_with_layer_in_selector-2.snap | 11 +- ...ts__global_css_with_layer_in_selector.snap | 10 +- ...layer_record_places_every_declaration.snap | 9 + ...tests__styles_with_no_class_to_switch.snap | 3 +- ...__tests__stylex_conditional_variables.snap | 6 +- ...__stylex_define_vars_and_create_theme.snap | 6 +- ...osition_try_and_view_transition_class.snap | 6 +- ..._props_join_styles_compiled_elsewhere.snap | 3 +- ..._stylex_theme_apis_with_named_imports.snap | 6 +- ...__stylex_theme_contract_and_constants.snap | 3 +- ...actor__tests__template_interpolations.snap | 6 +- ..._vanilla_extract_all_exports_combined.snap | 6 +- ...r__tests__vanilla_extract_font_face-2.snap | 3 +- ...tor__tests__vanilla_extract_font_face.snap | 3 +- ...vanilla_extract_font_face_empty_props.snap | 3 +- ...vanilla_extract_font_face_placeholder.snap | 4 +- ..._vanilla_extract_font_face_with_style.snap | 3 +- ...__vanilla_extract_imports_combination.snap | 3 +- ...tractor__tests__vanilla_extract_layer.snap | 11 +- ...__vanilla_extract_layer_and_container.snap | 5 +- ..._tests__vanilla_extract_layer_records.snap | 10 + ...__vanilla_extract_non_exported_styles.snap | 5 +- ...t_selector_refs_triggers_with_classes.snap | 6 +- libs/extractor/src/style_collector.rs | 198 ++++++++++++ libs/extractor/src/visit.rs | 17 +- libs/sheet/src/lib.rs | 297 ++++++++++++++---- .../snapshots/sheet__tests__deserialize.snap | 2 + .../__snapshots__/index.browser.test.tsx.snap | 6 +- 66 files changed, 705 insertions(+), 154 deletions(-) create mode 100644 .changepacks/changepack_log_global_order.json create mode 100644 libs/extractor/src/extract_style/extract_layer_order.rs create mode 100644 libs/extractor/src/style_collector.rs diff --git a/.changepacks/changepack_log_global_order.json b/.changepacks/changepack_log_global_order.json new file mode 100644 index 000000000..228e03af5 --- /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 5913869c9..9e9104693 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 1a9b75385..80ebed69a 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 5c52c82f2..f44f54650 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 000000000..1d2e0a00e --- /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 1adc78be6..b2e73f851 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 588057752..030a124ab 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 e101f8bbb..aacf5ec40 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 a4484b90f..23a8746dc 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(), }), )); } @@ -182,6 +184,7 @@ fn collect_global_styles<'a>( for p in &arr.elements { if let ArrayExpressionElement::ObjectExpression(o) = p { styles.push(ExtractStyleProp::Static(ExtractStyleValue::FontFace(ExtractFontFace { + order: 0, properties: o .properties .iter() @@ -216,6 +219,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 4b8f7f27b..7d6e2ee8c 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 69ff713a8..423ca38eb 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 68981f36c..0a0dbccdc 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 20f438589..4b196b8f7 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 958a0d6d3..4c9e4e2a7 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 fe9a5c84f..b749618b7 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 524004429..5f3293693 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 099e88cc5..c11909b7b 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 9a802683c..3f6a52425 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 acda00ecc..9c2773cb8 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 cd62e5202..09f91c25e 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 1594e4a10..4d8342696 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 7d1e97d26..1cf567e49 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 4e66ff5b9..0fa2068bd 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 518e9a7fd..89f8c5d61 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 150dabb2f..7feaee662 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 dcd24f04d..c240f81d3 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 049213665..2b0b8be9f 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 f677156b4..d6b7e822d 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 c75dd4475..8bed29668 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 243071df5..b84fdc8fa 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 ed8403d31..363c86a9a 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 bfb9c42d5..c0aadd180 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 f039363fc..6dcb14239 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 968cb53e3..00c16a389 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 3b00b65c3..d272e07f0 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 75244afb6..b12ed8a36 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 ff8f0255e..3912eb27a 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 250a40950..b542c61a0 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 aa2aa7cdd..6a5b70052 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 dd98a9a1d..3baa5a8e1 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 aa462f70d..ecc2b97af 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 48732dc11..445723943 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 a8060d1fc..8e70793b1 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 e7f728fad..052c30816 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 1d231868a..0adc7fd78 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 2e1b35fa0..218611fa4 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 8313728b6..5a4283858 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 0de4b83da..7d1fb949a 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 2594e53fe..440251a21 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 0f8d800f2..3489b9d60 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 c66396a1d..826e4e918 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 2b8fc7ced..b1cd11abc 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 02e3a50f0..436c21c6d 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 68e3d3981..557a44a25 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 ebc837d86..b66d0bfdc 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 3909d8ce8..f0635895e 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 6a8489aa5..87685fb48 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 969713595..4b1c94054 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 7a5d2d645..95537b064 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 113b631a8..fad0de172 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 61195fd1d..c15159a14 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 000000000..b11481e67 --- /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 93846123e..c48e78031 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 9a989df8f..c19e30730 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())); @@ -3496,12 +3594,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 +3753,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 bc58b6e61..64256e779 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 d5ecddeda..ecf6f0356 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`] = `