tailwind_rs_postcss/css_optimizer/
selector_optimizer.rs1use regex::Regex;
4use std::collections::HashMap;
5use super::types::*;
6
7pub struct SelectorOptimizer {
9 selector_rules: HashMap<String, SelectorRule>,
10}
11
12impl SelectorOptimizer {
13 pub fn new() -> Self {
15 Self {
16 selector_rules: Self::build_selector_rules(),
17 }
18 }
19
20 pub fn optimize_selectors(&self, css: &str) -> Result<SelectorOptimizationResult, OptimizationError> {
22 let start_time = std::time::Instant::now();
23 let mut selectors_optimized = 0;
24 let mut optimized_css = css.to_string();
25
26 let redundant_selectors = self.find_redundant_selectors(css)?;
28
29 for selector in redundant_selectors {
30 optimized_css = self.remove_redundant_selector(&optimized_css, &selector)?;
31 selectors_optimized += 1;
32 }
33
34 Ok(SelectorOptimizationResult {
35 optimized_css,
36 selectors_optimized,
37 optimization_time: start_time.elapsed(),
38 })
39 }
40
41 fn find_redundant_selectors(&self, css: &str) -> Result<Vec<String>, OptimizationError> {
43 let mut redundant = Vec::new();
44 let rule_pattern = Regex::new(r"([^{]+)\s*\{([^}]+)\}").unwrap();
45 let mut selector_map: HashMap<String, Vec<String>> = HashMap::new();
46
47 for cap in rule_pattern.captures_iter(css) {
48 let selector = cap[1].trim().to_string();
49 let properties = cap[2].trim().to_string();
50
51 selector_map.entry(properties).or_insert_with(Vec::new).push(selector);
52 }
53
54 for (_, selectors) in selector_map {
56 if selectors.len() > 1 {
57 let mut sorted_selectors = selectors;
59 sorted_selectors.sort_by(|a, b| self.compare_specificity(a, b));
60
61 for selector in sorted_selectors.iter().skip(1) {
62 redundant.push(selector.clone());
63 }
64 }
65 }
66
67 Ok(redundant)
68 }
69
70 fn remove_redundant_selector(&self, css: &str, selector: &str) -> Result<String, OptimizationError> {
72 let rule_pattern = Regex::new(&format!(r"{}[^{{]*\{{[^}}]*\}}", regex::escape(selector))).unwrap();
73 Ok(rule_pattern.replace_all(css, "").to_string())
74 }
75
76 fn compare_specificity(&self, a: &str, b: &str) -> std::cmp::Ordering {
78 let specificity_a = self.calculate_specificity(a);
79 let specificity_b = self.calculate_specificity(b);
80 specificity_b.cmp(&specificity_a) }
82
83 fn calculate_specificity(&self, selector: &str) -> usize {
85 let mut specificity = 0;
86
87 specificity += selector.matches('#').count() * 100;
89
90 specificity += selector.matches('.').count() * 10;
92 specificity += selector.matches('[').count() * 10;
93
94 specificity += selector.split_whitespace().count();
96
97 specificity
98 }
99
100 fn build_selector_rules() -> HashMap<String, SelectorRule> {
102 let mut rules = HashMap::new();
103
104 rules.insert("universal".to_string(), SelectorRule {
105 name: "universal".to_string(),
106 pattern: "*".to_string(),
107 optimization: SelectorOptimization::Remove,
108 });
109
110 rules
111 }
112}