Skip to content

Commit 116de4b

Browse files
committed
Optimize
1 parent ef4def7 commit 116de4b

4 files changed

Lines changed: 310 additions & 219 deletions

File tree

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,13 @@ resolver = "2"
33
members = ["libs/extractor", "bindings/devup-ui-wasm", "libs/sheet", "libs/css"]
44

55
[profile.release]
6-
# Tell `rustc` to optimize for small code size.
6+
# Optimize for small code size (critical for WASM binary)
77
opt-level = "s"
8+
# Link-time optimization: enables cross-crate inlining and dead code elimination
9+
lto = true
10+
# Single codegen unit: maximizes optimization at cost of compile time
11+
codegen-units = 1
12+
# Strip debug symbols from release binary
13+
strip = true
14+
# Abort on panic: smaller binary, no unwinding overhead
15+
panic = "abort"

libs/css/src/optimize_value.rs

Lines changed: 106 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,96 @@ use crate::{
77
};
88

99
pub fn optimize_value(value: &str) -> String {
10-
let mut ret = value.trim().to_string();
10+
let trimmed = value.trim();
11+
let mut ret = String::with_capacity(trimmed.len() + 8);
12+
ret.push_str(trimmed);
1113

1214
// Wrap CSS custom property names in var() when used as values
1315
// e.g., "--var-0" becomes "var(--var-0)"
1416
if ret.starts_with("--") && !ret.contains(' ') && !ret.contains(',') {
15-
ret = format!("var({})", ret);
17+
ret.insert_str(0, "var(");
18+
ret.push(')');
1619
}
1720

18-
ret = INNER_TRIM_RE.replace_all(&ret, "(${1})").to_string();
21+
// Use Cow-aware replacement: only allocate when regex matches
22+
let replaced = INNER_TRIM_RE.replace_all(&ret, "(${1})");
23+
if let std::borrow::Cow::Owned(s) = replaced {
24+
ret = s;
25+
}
1926

2027
// Skip RM_MINUS_ZERO_RE for values containing CSS custom property references
2128
// to preserve names like --var-0 (the -0 should not be converted to 0)
2229
if !ret.contains("--") {
23-
ret = RM_MINUS_ZERO_RE.replace_all(&ret, "0${1}").to_string();
30+
let replaced = RM_MINUS_ZERO_RE.replace_all(&ret, "0${1}");
31+
if let std::borrow::Cow::Owned(s) = replaced {
32+
ret = s;
33+
}
34+
}
35+
let replaced = NUM_TRIM_RE.replace_all(&ret, "${1} ${3}");
36+
if let std::borrow::Cow::Owned(s) = replaced {
37+
ret = s;
2438
}
25-
ret = NUM_TRIM_RE.replace_all(&ret, "${1} ${3}").to_string();
2639

27-
if ret.contains(",") {
28-
ret = F_SPACE_RE.replace_all(&ret, ",").trim().to_string();
40+
if ret.contains(',') {
41+
let replaced = F_SPACE_RE.replace_all(&ret, ",");
42+
if let std::borrow::Cow::Owned(s) = replaced {
43+
ret = s;
44+
}
45+
let trimmed = ret.trim();
46+
if trimmed.len() != ret.len() {
47+
ret = trimmed.to_string();
48+
}
49+
}
50+
let replaced = F_RGBA_RE.replace_all(&ret, |c: &regex::Captures| {
51+
let r = c[1].parse::<i32>().unwrap();
52+
let g = c[2].parse::<i32>().unwrap();
53+
let b = c[3].parse::<i32>().unwrap();
54+
let a = c[4].parse::<f32>().unwrap();
55+
format!(
56+
"#{:02X}{:02X}{:02X}{:02X}",
57+
r,
58+
g,
59+
b,
60+
(a * 255.0).round() as i32
61+
)
62+
});
63+
if let std::borrow::Cow::Owned(s) = replaced {
64+
ret = s;
2965
}
30-
ret = F_RGBA_RE
31-
.replace_all(&ret, |c: &regex::Captures| {
32-
let r = c[1].parse::<i32>().unwrap();
33-
let g = c[2].parse::<i32>().unwrap();
34-
let b = c[3].parse::<i32>().unwrap();
35-
let a = c[4].parse::<f32>().unwrap();
36-
format!(
37-
"#{:02X}{:02X}{:02X}{:02X}",
38-
r,
39-
g,
40-
b,
41-
(a * 255.0).round() as i32
42-
)
43-
})
44-
.to_string();
45-
ret = F_RGB_RE
46-
.replace_all(&ret, |c: &regex::Captures| {
47-
let r = c[1].parse::<i32>().unwrap();
48-
let g = c[2].parse::<i32>().unwrap();
49-
let b = c[3].parse::<i32>().unwrap();
50-
format!("#{r:02X}{g:02X}{b:02X}")
51-
})
52-
.to_string();
53-
if ret.contains("#") {
54-
ret = COLOR_HASH
55-
.replace_all(&ret, |c: &regex::Captures| optimize_color(&c[1]))
56-
.to_string();
66+
let replaced = F_RGB_RE.replace_all(&ret, |c: &regex::Captures| {
67+
let r = c[1].parse::<i32>().unwrap();
68+
let g = c[2].parse::<i32>().unwrap();
69+
let b = c[3].parse::<i32>().unwrap();
70+
format!("#{r:02X}{g:02X}{b:02X}")
71+
});
72+
if let std::borrow::Cow::Owned(s) = replaced {
73+
ret = s;
5774
}
58-
if ret.contains("0") {
59-
ret = DOT_ZERO_RE.replace_all(&ret, "${1}0${2}").to_string();
60-
ret = F_DOT_RE.replace_all(&ret, "${1}.${2}").to_string();
61-
ret = ZERO_RE.replace_all(&ret, "${1}0").to_string();
75+
if ret.contains('#') {
76+
let replaced = COLOR_HASH.replace_all(&ret, |c: &regex::Captures| optimize_color(&c[1]));
77+
if let std::borrow::Cow::Owned(s) = replaced {
78+
ret = s;
79+
}
80+
}
81+
if ret.contains('0') {
82+
let replaced = DOT_ZERO_RE.replace_all(&ret, "${1}0${2}");
83+
if let std::borrow::Cow::Owned(s) = replaced {
84+
ret = s;
85+
}
86+
let replaced = F_DOT_RE.replace_all(&ret, "${1}.${2}");
87+
if let std::borrow::Cow::Owned(s) = replaced {
88+
ret = s;
89+
}
90+
let replaced = ZERO_RE.replace_all(&ret, "${1}0");
91+
if let std::borrow::Cow::Owned(s) = replaced {
92+
ret = s;
93+
}
6294

6395
for f in ZERO_PERCENT_FUNCTION.iter() {
6496
let tmp = ret.to_lowercase();
6597
if tmp.contains(f) {
6698
let index = tmp.find(f).unwrap() + f.len();
67-
let mut zero_idx = vec![];
99+
let mut zero_idx = Vec::with_capacity(4);
68100
let mut depth = 0;
69101
let chars: Vec<char> = tmp.chars().collect();
70102
let byte_indices: Vec<usize> = tmp.char_indices().map(|(i, _)| i).collect();
@@ -82,31 +114,51 @@ pub fn optimize_value(value: &str) -> String {
82114
zero_idx.push(byte_indices[char_idx]);
83115
}
84116
}
117+
// In-place replacement: replace each '0' with '0%' from back to front
85118
for i in zero_idx.iter().rev() {
86-
ret = ret[..*i].to_string() + "0%" + &ret[*i + 1..];
119+
ret.replace_range(*i..*i + 1, "0%");
87120
}
88121
}
89122
}
90123
}
91124
// remove ; from dynamic value
125+
// Check suffix patterns directly without format! allocation
92126
for str_symbol in ["", "`", "\"", "'"] {
93-
if ret.ends_with(&format!(";{str_symbol}")) {
94-
ret = format!(
95-
"{}{}",
96-
ret[..ret.len() - str_symbol.len() - 1].trim_end_matches(';'),
97-
str_symbol
98-
);
99-
} else if ret.ends_with(&format!(";{str_symbol})")) {
100-
ret = format!(
101-
"{}{})",
102-
ret[..ret.len() - str_symbol.len() - 2].trim_end_matches(';'),
103-
str_symbol
104-
);
127+
let suffix_with_paren = if str_symbol.is_empty() {
128+
";)".to_string()
129+
} else {
130+
let mut s = String::with_capacity(str_symbol.len() + 2);
131+
s.push(';');
132+
s.push_str(str_symbol);
133+
s.push(')');
134+
s
135+
};
136+
let suffix_without_paren = if str_symbol.is_empty() {
137+
";".to_string()
138+
} else {
139+
let mut s = String::with_capacity(str_symbol.len() + 1);
140+
s.push(';');
141+
s.push_str(str_symbol);
142+
s
143+
};
144+
if ret.ends_with(&suffix_without_paren) {
145+
let base = ret[..ret.len() - suffix_without_paren.len()].trim_end_matches(';');
146+
let mut new_ret = String::with_capacity(base.len() + str_symbol.len());
147+
new_ret.push_str(base);
148+
new_ret.push_str(str_symbol);
149+
ret = new_ret;
150+
} else if ret.ends_with(&suffix_with_paren) {
151+
let base = ret[..ret.len() - suffix_with_paren.len()].trim_end_matches(';');
152+
let mut new_ret = String::with_capacity(base.len() + str_symbol.len() + 1);
153+
new_ret.push_str(base);
154+
new_ret.push_str(str_symbol);
155+
new_ret.push(')');
156+
ret = new_ret;
105157
}
106158
}
107159

108-
if ret.contains("(") || ret.contains(")") {
109-
let mut depth = 0;
160+
if ret.contains('(') || ret.contains(')') {
161+
let mut depth: i32 = 0;
110162
for ch in ret.chars() {
111163
if ch == '(' {
112164
depth += 1;

libs/css/src/style_selector.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,14 @@ pub fn optimize_selector(selector: StyleSelector) -> StyleSelector {
7171
selector,
7272
} => StyleSelector::At {
7373
kind,
74-
query: query.to_string(),
75-
selector: selector
76-
.as_ref()
77-
.map(|s| optimize_selector_string(s.as_str())),
74+
query,
75+
selector: selector.map(|s| optimize_selector_string(&s)),
7876
},
7977
StyleSelector::Selector(selector) => {
8078
StyleSelector::Selector(optimize_selector_string(&selector))
8179
}
8280
StyleSelector::Global(selector, file) => {
83-
StyleSelector::Global(optimize_selector_string(&selector), file.to_string())
81+
StyleSelector::Global(optimize_selector_string(&selector), file)
8482
}
8583
}
8684
}
@@ -231,43 +229,38 @@ impl From<(&StyleSelector, &str)> for StyleSelector {
231229

232230
impl Display for StyleSelector {
233231
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
234-
write!(
235-
f,
236-
"{}",
237-
match self {
238-
StyleSelector::Selector(value) => value.to_string(),
239-
StyleSelector::At {
240-
kind,
241-
query,
242-
selector,
243-
} => {
244-
let space = if query.starts_with('(') { "" } else { " " };
245-
if let Some(selector) = selector {
246-
format!("@{kind}{space}{query} {selector}")
247-
} else {
248-
format!("@{kind}{space}{query}")
249-
}
232+
match self {
233+
StyleSelector::Selector(value) => f.write_str(value),
234+
StyleSelector::At {
235+
kind,
236+
query,
237+
selector,
238+
} => {
239+
write!(f, "@{kind}")?;
240+
if !query.starts_with('(') {
241+
f.write_str(" ")?;
250242
}
251-
StyleSelector::Global(value, _) => value.to_string(),
243+
f.write_str(query)?;
244+
if let Some(selector) = selector {
245+
write!(f, " {selector}")?;
246+
}
247+
Ok(())
252248
}
253-
)
249+
StyleSelector::Global(value, _) => f.write_str(value),
250+
}
254251
}
255252
}
256253

257254
fn get_selector_order(selector: &str) -> u8 {
258-
// & count
259-
let t = if selector.chars().filter(|c| c == &'&').count() == 1 {
260-
selector
261-
.split('&')
262-
.next_back()
263-
.map(|a| a.to_string())
264-
.unwrap_or(selector.to_string())
255+
// Extract the part after the single '&' (avoid String allocation)
256+
let t: &str = if selector.chars().filter(|c| *c == '&').count() == 1 {
257+
selector.split('&').next_back().unwrap_or(selector)
265258
} else {
266-
selector.to_string()
259+
selector
267260
};
268261

269262
// First, try to find the order in the map (for regular selectors like &:hover)
270-
if let Some(order) = SELECTOR_ORDER_MAP.get(&t) {
263+
if let Some(order) = SELECTOR_ORDER_MAP.get(t) {
271264
return *order;
272265
}
273266

@@ -282,7 +275,7 @@ fn get_selector_order(selector: &str) -> u8 {
282275
}
283276
}
284277

285-
if t.starts_with("&") { 0 } else { 99 }
278+
if t.starts_with('&') { 0 } else { 99 }
286279
}
287280

288281
#[cfg(test)]

0 commit comments

Comments
 (0)