Skip to content

Commit 578be2e

Browse files
committed
perf: reduce allocations via regex early-exit, write!() codegen, and iterator optimizations
1 parent 11a5936 commit 578be2e

4 files changed

Lines changed: 96 additions & 64 deletions

File tree

libs/css/src/optimize_value.rs

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,34 @@ pub fn optimize_value(value: &str) -> String {
4343
ret = s;
4444
}
4545
}
46-
let replaced = F_RGBA_RE.replace_all(&ret, |c: &regex_lite::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-
let a = c[4].parse::<f32>().unwrap();
51-
format!(
52-
"#{:02X}{:02X}{:02X}{:02X}",
53-
r,
54-
g,
55-
b,
56-
(a * 255.0).round() as i32
57-
)
58-
});
59-
if let std::borrow::Cow::Owned(s) = replaced {
60-
ret = s;
46+
if ret.contains("rgba(") {
47+
let replaced = F_RGBA_RE.replace_all(&ret, |c: &regex_lite::Captures| {
48+
let r = c[1].parse::<i32>().unwrap();
49+
let g = c[2].parse::<i32>().unwrap();
50+
let b = c[3].parse::<i32>().unwrap();
51+
let a = c[4].parse::<f32>().unwrap();
52+
format!(
53+
"#{:02X}{:02X}{:02X}{:02X}",
54+
r,
55+
g,
56+
b,
57+
(a * 255.0).round() as i32
58+
)
59+
});
60+
if let std::borrow::Cow::Owned(s) = replaced {
61+
ret = s;
62+
}
6163
}
62-
let replaced = F_RGB_RE.replace_all(&ret, |c: &regex_lite::Captures| {
63-
let r = c[1].parse::<i32>().unwrap();
64-
let g = c[2].parse::<i32>().unwrap();
65-
let b = c[3].parse::<i32>().unwrap();
66-
format!("#{r:02X}{g:02X}{b:02X}")
67-
});
68-
if let std::borrow::Cow::Owned(s) = replaced {
69-
ret = s;
64+
if ret.contains("rgb(") {
65+
let replaced = F_RGB_RE.replace_all(&ret, |c: &regex_lite::Captures| {
66+
let r = c[1].parse::<i32>().unwrap();
67+
let g = c[2].parse::<i32>().unwrap();
68+
let b = c[3].parse::<i32>().unwrap();
69+
format!("#{r:02X}{g:02X}{b:02X}")
70+
});
71+
if let std::borrow::Cow::Owned(s) = replaced {
72+
ret = s;
73+
}
7074
}
7175
if ret.contains('#') {
7276
let replaced =
@@ -91,32 +95,33 @@ pub fn optimize_value(value: &str) -> String {
9195

9296
let mut lower = ret.to_lowercase();
9397
for f in ZERO_PERCENT_FUNCTION.iter() {
94-
if lower.contains(f) {
95-
let index = lower.find(f).unwrap() + f.len();
98+
if let Some(start) = lower.find(f) {
99+
let index = start + f.len();
96100
let mut zero_idx = Vec::with_capacity(4);
97-
let mut depth = 0;
98-
let chars: Vec<char> = lower.chars().collect();
99-
let byte_indices: Vec<usize> = lower.char_indices().map(|(i, _)| i).collect();
101+
let mut depth: i32 = 0;
102+
let bytes = lower.as_bytes();
100103

101-
for (char_idx, &ch) in chars.iter().enumerate().skip(index) {
102-
if ch == '(' {
103-
depth += 1;
104-
} else if ch == ')' {
105-
depth -= 1;
106-
} else if ch == '0'
107-
&& (char_idx == 0 || !chars[char_idx - 1].is_ascii_digit())
108-
&& (char_idx + 1 >= chars.len() || !chars[char_idx + 1].is_ascii_digit())
109-
&& depth == 0
110-
{
111-
zero_idx.push(byte_indices[char_idx]);
104+
for i in index..bytes.len() {
105+
match bytes[i] {
106+
b'(' => depth += 1,
107+
b')' => depth -= 1,
108+
b'0' if depth == 0
109+
&& (i == 0 || !bytes[i - 1].is_ascii_digit())
110+
&& (i + 1 >= bytes.len() || !bytes[i + 1].is_ascii_digit()) =>
111+
{
112+
zero_idx.push(i);
113+
}
114+
_ => {}
112115
}
113116
}
114117
// In-place replacement: replace each '0' with '0%' from back to front
115118
for i in zero_idx.iter().rev() {
116119
ret.replace_range(*i..*i + 1, "0%");
117120
}
118-
// Refresh lowercase after modification for subsequent iterations
119-
lower = ret.to_lowercase();
121+
if !zero_idx.is_empty() {
122+
// Refresh lowercase only when modification occurred
123+
lower = ret.to_lowercase();
124+
}
120125
}
121126
}
122127
}

libs/extractor/src/css_utils.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,11 +486,12 @@ pub fn optimize_css_block(css: &str) -> String {
486486
s2
487487
};
488488

489-
let segments: Vec<&str> = trimmed.split(';').collect();
490-
for (i, s) in segments.iter().enumerate() {
491-
if i > 0 {
489+
let mut first_segment = true;
490+
for s in trimmed.split(';') {
491+
if !first_segment {
492492
result.push(';');
493493
}
494+
first_segment = false;
494495
let parts: Vec<&str> = s.split('{').collect();
495496
let first_part_str = if parts.len() > 1 {
496497
parts[..parts.len() - 1]

libs/extractor/src/vanilla_extract.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use oxc_span::{GetSpan, SourceType};
1818
use oxc_transformer::{TransformOptions, Transformer};
1919
use rustc_hash::{FxHashMap, FxHashSet};
2020
use std::cell::RefCell;
21+
use std::fmt::Write;
2122
use std::path::Path;
2223
use std::rc::Rc;
2324

@@ -1196,10 +1197,10 @@ pub fn collected_styles_to_code_partial(
11961197
package: &str,
11971198
style_names: &FxHashSet<String>,
11981199
) -> String {
1199-
let mut code_parts = Vec::new();
1200+
let mut output = String::new();
12001201

12011202
if !style_names.is_empty() {
1202-
code_parts.push(format!("import {{ css }} from '{}'", package));
1203+
write!(output, "import {{ css }} from '{}'", package).unwrap();
12031204
}
12041205

12051206
// Generate only the specified styles
@@ -1212,10 +1213,13 @@ pub fn collected_styles_to_code_partial(
12121213

12131214
for (name, entry) in styles {
12141215
// Generate as non-exported for first pass
1215-
code_parts.push(format!("const {} = css({})", name, entry.json));
1216+
if !output.is_empty() {
1217+
output.push('\n');
1218+
}
1219+
write!(output, "const {} = css({})", name, entry.json).unwrap();
12161220
}
12171221

1218-
code_parts.join("\n")
1222+
output
12191223
}
12201224

12211225
/// Convert collected styles to code with selector references replaced by class names
@@ -1259,6 +1263,19 @@ pub fn collected_styles_to_code_with_classes(
12591263
.map(|(name, entry)| (name.as_str(), entry.json.as_str()))
12601264
.collect();
12611265

1266+
// Pre-build search/replace pairs to avoid format!() per iteration
1267+
let replacements: Vec<_> = class_map
1268+
.iter()
1269+
.map(|(style_name, class_name)| {
1270+
(
1271+
format!("\"{}:", style_name),
1272+
format!("\"{}:", class_name),
1273+
format!("\"{} ", style_name),
1274+
format!("\"{} ", class_name),
1275+
)
1276+
})
1277+
.collect();
1278+
12621279
let mut styles: Vec<_> = collected.styles.iter().collect();
12631280
styles.sort_by_key(|(name, _)| *name);
12641281

@@ -1267,10 +1284,13 @@ pub fn collected_styles_to_code_with_classes(
12671284

12681285
// Replace style name references with class names in JSON
12691286
let mut json = entry.json.clone();
1270-
for (style_name, class_name) in class_map {
1271-
// Replace patterns like "stylename:" with "classname:"
1272-
json = json.replace(&format!("\"{}:", style_name), &format!("\"{}:", class_name));
1273-
json = json.replace(&format!("\"{} ", style_name), &format!("\"{} ", class_name));
1287+
for (search_colon, replace_colon, search_space, replace_space) in &replacements {
1288+
if json.contains(search_colon.as_str()) {
1289+
json = json.replace(search_colon, replace_colon);
1290+
}
1291+
if json.contains(search_space.as_str()) {
1292+
json = json.replace(search_space, replace_space);
1293+
}
12741294
}
12751295

12761296
if entry.bases.is_empty() {

libs/sheet/src/lib.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ impl StyleSheet {
671671
base_styles
672672
.entry(*prop.0)
673673
.or_default()
674-
.extend(prop.1.clone());
674+
.extend(prop.1.iter().cloned());
675675
});
676676
}
677677
});
@@ -680,23 +680,29 @@ impl StyleSheet {
680680
// base style
681681

682682
let theme_css = self.theme.to_css();
683-
let mut layers_vec = Vec::new();
684-
if style_orders.remove(&0) {
685-
layers_vec.push("b".to_string());
686-
}
687-
if !theme_css.is_empty() {
688-
layers_vec.push("t".to_string());
689-
}
690-
layers_vec.extend(style_orders.iter().map(|v| format!("o{v}")));
691-
if !layers_vec.is_empty() {
683+
let has_base = style_orders.remove(&0);
684+
let has_theme = !theme_css.is_empty();
685+
let has_orders = !style_orders.is_empty();
686+
if has_base || has_theme || has_orders {
692687
css.push_str("@layer ");
693688
let mut first = true;
694-
for layer in &layers_vec {
689+
if has_base {
690+
css.push('b');
691+
first = false;
692+
}
693+
if has_theme {
694+
if !first {
695+
css.push(',');
696+
}
697+
css.push('t');
698+
first = false;
699+
}
700+
for v in &style_orders {
695701
if !first {
696702
css.push(',');
697703
}
698704
first = false;
699-
css.push_str(layer);
705+
write!(css, "o{v}").unwrap();
700706
}
701707
css.push(';');
702708
}

0 commit comments

Comments
 (0)