tailwind_rs_postcss/css_optimizer/
rule_merger.rs1use super::types::*;
4use regex::Regex;
5use std::collections::HashMap;
6
7pub struct RuleMerger {
9 compatibility_matrix: HashMap<String, Vec<String>>,
10}
11
12impl RuleMerger {
13 pub fn new() -> Self {
15 Self {
16 compatibility_matrix: Self::build_compatibility_matrix(),
17 }
18 }
19
20 pub fn merge_rules(&self, css: &str) -> Result<RuleMergeResult, OptimizationError> {
22 let start_time = std::time::Instant::now();
23 let mut rules_merged = 0;
24 let mut optimized_css = css.to_string();
25
26 let duplicate_selectors = self.find_duplicate_selectors(css)?;
28
29 for (selector, rules) in duplicate_selectors {
30 if rules.len() > 1 {
31 let merged_rule = self.merge_rule_properties(&rules)?;
32 optimized_css =
33 self.replace_duplicate_rules(&optimized_css, &selector, &merged_rule)?;
34 rules_merged += 1;
35 }
36 }
37
38 Ok(RuleMergeResult {
39 optimized_css,
40 rules_merged,
41 merge_time: start_time.elapsed(),
42 })
43 }
44
45 fn find_duplicate_selectors(
47 &self,
48 css: &str,
49 ) -> Result<HashMap<String, Vec<CSSRule>>, OptimizationError> {
50 let mut selector_map: HashMap<String, Vec<CSSRule>> = HashMap::new();
51 let rule_pattern = Regex::new(r"([^{]+)\s*\{([^}]+)\}").unwrap();
52
53 for cap in rule_pattern.captures_iter(css) {
54 let selector = cap[1].trim().to_string();
55 let properties = cap[2].trim();
56
57 let rule = CSSRule {
58 selector: selector.clone(),
59 properties: self.parse_properties(properties)?,
60 };
61
62 selector_map
63 .entry(selector)
64 .or_insert_with(Vec::new)
65 .push(rule);
66 }
67
68 Ok(selector_map)
69 }
70
71 fn merge_rule_properties(&self, rules: &[CSSRule]) -> Result<CSSRule, OptimizationError> {
73 let mut merged_properties = Vec::new();
74 let mut seen_properties: HashMap<String, String> = HashMap::new();
75
76 for rule in rules {
77 for property in &rule.properties {
78 seen_properties.insert(property.name.clone(), property.value.clone());
79 }
80 }
81
82 for (name, value) in seen_properties {
83 merged_properties.push(CSSProperty { name, value });
84 }
85
86 Ok(CSSRule {
87 selector: rules[0].selector.clone(),
88 properties: merged_properties,
89 })
90 }
91
92 fn replace_duplicate_rules(
94 &self,
95 css: &str,
96 selector: &str,
97 merged_rule: &CSSRule,
98 ) -> Result<String, OptimizationError> {
99 let mut result = css.to_string();
100 let rule_pattern =
101 Regex::new(&format!(r"{}[^{{]*\{{[^}}]*\}}", regex::escape(selector))).unwrap();
102
103 result = rule_pattern.replace_all(&result, "").to_string();
105
106 let merged_css = format!(
108 "{} {{\n{}\n}}",
109 merged_rule.selector,
110 merged_rule
111 .properties
112 .iter()
113 .map(|p| format!(" {}: {};", p.name, p.value))
114 .collect::<Vec<_>>()
115 .join("\n")
116 );
117
118 result.push_str(&merged_css);
119 Ok(result)
120 }
121
122 fn parse_properties(
124 &self,
125 properties_str: &str,
126 ) -> Result<Vec<CSSProperty>, OptimizationError> {
127 let mut properties = Vec::new();
128 let property_pattern = Regex::new(r"([^:]+):\s*([^;]+);").unwrap();
129
130 for cap in property_pattern.captures_iter(properties_str) {
131 properties.push(CSSProperty {
132 name: cap[1].trim().to_string(),
133 value: cap[2].trim().to_string(),
134 });
135 }
136
137 Ok(properties)
138 }
139
140 fn build_compatibility_matrix() -> HashMap<String, Vec<String>> {
142 let mut matrix = HashMap::new();
143
144 matrix.insert(
146 "class".to_string(),
147 vec!["class".to_string(), "element".to_string()],
148 );
149
150 matrix.insert(
152 "element".to_string(),
153 vec!["element".to_string(), "class".to_string()],
154 );
155
156 matrix
157 }
158}