Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_global_order.json
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 3 additions & 1 deletion libs/extractor/src/extract_style/extract_css.rs
Original file line number Diff line number Diff line change
@@ -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,
}
2 changes: 2 additions & 0 deletions libs/extractor/src/extract_style/extract_font_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String>,
}
4 changes: 3 additions & 1 deletion libs/extractor/src/extract_style/extract_import.rs
Original file line number Diff line number Diff line change
@@ -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,
}
6 changes: 6 additions & 0 deletions libs/extractor/src/extract_style/extract_layer_order.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}
9 changes: 9 additions & 0 deletions libs/extractor/src/extract_style/extract_static_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct ExtractStaticStyle {
pub layer: Option<String>,
/// 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 {
Expand Down Expand Up @@ -105,6 +107,7 @@ impl ExtractStaticStyle {
style_order: None,
layer: None,
theme_token_resolution: ThemeTokenResolution::CssVariable,
order: 0,
}
}

Expand Down Expand Up @@ -137,6 +140,7 @@ impl ExtractStaticStyle {
style_order: Some(0),
layer: None,
theme_token_resolution: ThemeTokenResolution::CssVariable,
order: 0,
}
}

Expand Down Expand Up @@ -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
Expand Down
18 changes: 14 additions & 4 deletions libs/extractor/src/extract_style/extract_style_value.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -14,6 +14,7 @@ pub enum ExtractStyleValue {
Import(ExtractImport),
FontFace(ExtractFontFace),
Keyframes(ExtractKeyframes),
LayerOrder(ExtractLayerOrder),
}

impl ExtractStyleValue {
Expand All @@ -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) {
Expand Down Expand Up @@ -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());
}
Expand Down
1 change: 1 addition & 0 deletions libs/extractor/src/extract_style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
));
}
Expand All @@ -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(),
}),
));
}
Expand All @@ -181,28 +183,47 @@ fn collect_global_styles<'a>(
if let Expression::ArrayExpression(arr) = &o.value {
for p in &arr.elements {
if let ArrayExpressionElement::ObjectExpression(o) = p {
styles.push(ExtractStyleProp::Static(ExtractStyleValue::FontFace(ExtractFontFace {
properties: o
.properties
.iter()
.filter_map(|p| {
if let ObjectPropertyKind::ObjectProperty(o) = p
&& let Some(property_name) = get_str_by_property_key(&o.key)
&& let Some(s) = get_string_by_literal_expression(&o.value)
{
let it = disassemble_property(&property_name).map(|p| {
let v = if check_multi_css_optimize(&p) { optimize_multi_css_value(&s) } else { Cow::Borrowed(&*s) };
if p == "src" { (p.into_owned(), wrap_url(&v).into_owned()) } else { (p.into_owned(), v.into_owned()) }
});
Some(it.collect::<Vec<_>>())
} else {
None
}
})
.flatten()
.collect(),
file: file.to_string(),
})));
let properties = o
.properties
.iter()
.filter_map(|p| {
if let ObjectPropertyKind::ObjectProperty(o) = p
&& let Some(property_name) =
get_str_by_property_key(&o.key)
&& let Some(s) =
get_string_by_literal_expression(&o.value)
{
let it = disassemble_property(&property_name)
.map(|p| {
let v = if check_multi_css_optimize(&p)
{
optimize_multi_css_value(&s)
} else {
Cow::Borrowed(&*s)
};
if p == "src" {
(
p.into_owned(),
wrap_url(&v).into_owned(),
)
} else {
(p.into_owned(), v.into_owned())
}
});
Some(it.collect::<Vec<_>>())
} else {
None
}
})
.flatten()
.collect();
styles.push(ExtractStyleProp::Static(
ExtractStyleValue::FontFace(ExtractFontFace {
file: file.to_string(),
order: 0,
properties,
}),
));
} else if let ArrayExpressionElement::TemplateLiteral(t) = p {
let css_styles = css_to_style_literal(t, 0, &None)
.into_iter()
Expand All @@ -216,6 +237,7 @@ fn collect_global_styles<'a>(
.collect::<Vec<_>>();
styles.push(ExtractStyleProp::Static(
ExtractStyleValue::FontFace(ExtractFontFace {
order: 0,
properties: css_styles
.iter()
.filter_map(|p| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ fn typography_atom(name: &str, level: u8, selector: &Option<StyleSelector>) -> E
style_order: None,
layer: None,
theme_token_resolution: ThemeTokenResolution::default(),
order: 0,
}
}

Expand Down
1 change: 1 addition & 0 deletions libs/extractor/src/extractor/extract_style_from_stylex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fn raw_static_style<'a>(
style_order: None,
layer: None,
theme_token_resolution: Default::default(),
order: 0,
}))
}

Expand Down
3 changes: 2 additions & 1 deletion libs/extractor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ ToBTreeSet {
),
Css(
ExtractCss {
css: "body{margin:0}",
file: "test.tsx",
order: 1,
css: "body{margin:0}",
},
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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')",
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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')",
Expand Down
Loading
Loading