swc_css_minifier/compressor/
mod.rs

1use swc_css_ast::*;
2use swc_css_utils::serialize_ident;
3use swc_css_visit::{VisitMut, VisitMutWith};
4
5use self::ctx::Ctx;
6
7mod alpha_value;
8mod angle;
9mod calc_sum;
10mod color;
11mod container;
12mod ctx;
13mod declaration;
14mod easing_function;
15mod frequency;
16mod import;
17mod keyframes;
18mod length;
19mod math;
20mod media;
21mod rules;
22mod selector;
23mod supports;
24mod time;
25mod transform_function;
26mod unicode_range;
27mod url;
28
29pub fn compressor() -> impl VisitMut {
30    Compressor::default()
31}
32
33#[derive(Default)]
34struct Compressor {
35    ctx: Ctx,
36    need_utf8_at_rule: bool,
37    in_supports_condition: bool,
38}
39
40impl Compressor {
41    #[inline]
42    fn is_ident_shorter_than_str(&self, input: &str) -> bool {
43        let escaped = serialize_ident(input, true);
44
45        // escaped: without double quotes, so need plus 2 here
46        escaped.len() < input.len() + 2
47    }
48}
49
50impl VisitMut for Compressor {
51    fn visit_mut_alpha_value(&mut self, n: &mut AlphaValue) {
52        n.visit_mut_children_with(self);
53
54        self.compress_alpha_value(n);
55    }
56
57    fn visit_mut_an_plus_b(&mut self, n: &mut AnPlusB) {
58        if let AnPlusB::Ident(n) = n {
59            n.value = n.value.to_ascii_lowercase();
60            n.raw = None;
61        }
62
63        n.visit_mut_children_with(self);
64
65        self.compress_an_plus_b(n);
66    }
67
68    fn visit_mut_angle(&mut self, n: &mut Angle) {
69        n.visit_mut_children_with(self);
70
71        self.compress_angle(n);
72    }
73
74    fn visit_mut_at_rule(&mut self, n: &mut AtRule) {
75        n.visit_mut_children_with(self);
76
77        self.compress_keyframes_at_rule(n);
78    }
79
80    fn visit_mut_at_rule_name(&mut self, n: &mut AtRuleName) {
81        if let AtRuleName::Ident(n) = n {
82            n.value = n.value.to_ascii_lowercase();
83            n.raw = None;
84        }
85
86        n.visit_mut_children_with(self);
87    }
88
89    fn visit_mut_attribute_selector(&mut self, n: &mut AttributeSelector) {
90        n.visit_mut_children_with(self);
91
92        self.compress_attribute_selector(n);
93    }
94
95    fn visit_mut_calc_sum(&mut self, n: &mut CalcSum) {
96        n.visit_mut_children_with(&mut *self.with_ctx(Ctx {
97            in_math_function: true,
98            ..self.ctx
99        }));
100
101        // Don't touch `@supports`, it can be used to check a browser's support for one
102        // or more specific CSS features
103        if !self.in_supports_condition {
104            self.compress_calc_sum(n);
105        }
106    }
107
108    fn visit_mut_color(&mut self, n: &mut Color) {
109        n.visit_mut_children_with(self);
110
111        self.compress_color(n);
112    }
113
114    fn visit_mut_component_value(&mut self, n: &mut ComponentValue) {
115        n.visit_mut_children_with(self);
116
117        if self.in_supports_condition {
118            return;
119        }
120
121        self.compress_calc_sum_in_component_value(n);
122
123        self.compress_alpha_value_in_component_value(n);
124
125        self.compress_component_value_for_length(n);
126
127        self.compress_easing_function(n);
128
129        self.compress_transform_function(n);
130
131        self.compress_angle_in_component_value(n);
132    }
133
134    fn visit_mut_compound_selector(&mut self, n: &mut CompoundSelector) {
135        n.visit_mut_children_with(self);
136
137        self.compress_compound_selector(n);
138    }
139
140    fn visit_mut_custom_ident(&mut self, custom_ident: &mut CustomIdent) {
141        custom_ident.visit_mut_children_with(self);
142
143        if !self.need_utf8_at_rule {
144            self.need_utf8_at_rule = !contains_only_ascii_characters(&custom_ident.value);
145        }
146    }
147
148    fn visit_mut_custom_property_name(&mut self, custom_property_name: &mut CustomPropertyName) {
149        custom_property_name.visit_mut_children_with(self);
150
151        if !self.need_utf8_at_rule {
152            self.need_utf8_at_rule = !contains_only_ascii_characters(&custom_property_name.value);
153        }
154    }
155
156    fn visit_mut_dashed_ident(&mut self, dashed_ident: &mut DashedIdent) {
157        dashed_ident.visit_mut_children_with(self);
158
159        if !self.need_utf8_at_rule {
160            self.need_utf8_at_rule = !contains_only_ascii_characters(&dashed_ident.value);
161        }
162    }
163
164    fn visit_mut_declaration(&mut self, n: &mut Declaration) {
165        if self.in_supports_condition {
166            n.visit_mut_children_with(self);
167
168            return;
169        }
170
171        self.compress_declaration(n);
172
173        if let DeclarationName::Ident(Ident { value, .. }) = &n.name {
174            if matches_eq_ignore_ascii_case!(
175                value,
176                "opacity",
177                "fill-opacity",
178                "stroke-opacity",
179                "shape-image-threshold"
180            ) {
181                n.visit_mut_children_with(&mut *self.with_ctx(Ctx {
182                    preserve_alpha_value: false,
183                    ..self.ctx
184                }));
185            } else {
186                n.visit_mut_children_with(self);
187            }
188        } else {
189            n.visit_mut_children_with(self);
190        }
191    }
192
193    fn visit_mut_forgiving_relative_selector_list(
194        &mut self,
195        n: &mut ForgivingRelativeSelectorList,
196    ) {
197        n.visit_mut_children_with(self);
198
199        self.compress_forgiving_relative_selector_list(n);
200    }
201
202    fn visit_mut_forgiving_selector_list(&mut self, n: &mut ForgivingSelectorList) {
203        n.visit_mut_children_with(self);
204
205        self.compress_forgiving_selector_list(n);
206    }
207
208    fn visit_mut_frequency(&mut self, n: &mut Frequency) {
209        n.unit.value = n.unit.value.to_ascii_lowercase();
210        n.unit.raw = None;
211
212        n.visit_mut_children_with(self);
213
214        self.compress_frequency(n);
215    }
216
217    fn visit_mut_function(&mut self, n: &mut Function) {
218        if let FunctionName::Ident(n) = &mut n.name {
219            n.value = n.value.to_ascii_lowercase();
220            n.raw = None;
221        }
222
223        if matches_eq!(
224            n.name, "rotate", "skew", "skewx", "skewy", "rotate3d", "rotatex", "rotatey", "rotatez"
225        ) {
226            n.visit_mut_children_with(&mut *self.with_ctx(Ctx {
227                in_transform_function: true,
228                ..self.ctx
229            }));
230        } else {
231            n.visit_mut_children_with(self);
232        }
233    }
234
235    fn visit_mut_hex_color(&mut self, n: &mut HexColor) {
236        let new = n.value.to_ascii_lowercase();
237        if new != n.value {
238            n.value = new;
239            n.raw = None;
240        }
241
242        n.visit_mut_children_with(self);
243    }
244
245    fn visit_mut_ident(&mut self, ident: &mut Ident) {
246        ident.visit_mut_children_with(self);
247
248        if !self.need_utf8_at_rule {
249            self.need_utf8_at_rule = !contains_only_ascii_characters(&ident.value);
250        }
251    }
252
253    fn visit_mut_import_href(&mut self, n: &mut ImportHref) {
254        n.visit_mut_children_with(self);
255
256        self.compress_import_href(n);
257    }
258
259    fn visit_mut_keyframe_block(&mut self, n: &mut KeyframeBlock) {
260        n.visit_mut_children_with(&mut *self.with_ctx(Ctx {
261            in_keyframe_block: true,
262            ..self.ctx
263        }));
264    }
265
266    fn visit_mut_keyframe_selector(&mut self, n: &mut KeyframeSelector) {
267        n.visit_mut_children_with(self);
268
269        self.compress_keyframe_selector(n);
270    }
271
272    fn visit_mut_length(&mut self, n: &mut Length) {
273        n.unit.value = n.unit.value.to_ascii_lowercase();
274        n.unit.raw = None;
275
276        n.visit_mut_children_with(self);
277
278        self.compress_length(n);
279    }
280
281    fn visit_mut_media_condition(&mut self, n: &mut MediaCondition) {
282        n.visit_mut_children_with(self);
283
284        self.compress_media_condition(n);
285    }
286
287    fn visit_mut_media_condition_without_or(&mut self, n: &mut MediaConditionWithoutOr) {
288        n.visit_mut_children_with(self);
289
290        self.compress_media_condition_without_or(n);
291    }
292
293    fn visit_mut_media_feature(&mut self, n: &mut MediaFeature) {
294        n.visit_mut_children_with(self);
295
296        self.compress_media_feature(n);
297    }
298
299    fn visit_mut_media_feature_value(&mut self, n: &mut MediaFeatureValue) {
300        n.visit_mut_children_with(self);
301
302        self.compress_calc_sum_in_media_feature_value(n);
303        self.compress_media_feature_value_length(n);
304    }
305
306    fn visit_mut_media_in_parens(&mut self, n: &mut MediaInParens) {
307        n.visit_mut_children_with(self);
308
309        self.compress_media_in_parens(n);
310    }
311
312    fn visit_mut_media_query_list(&mut self, n: &mut MediaQueryList) {
313        n.visit_mut_children_with(self);
314
315        self.compress_media_query_list(n);
316    }
317
318    fn visit_mut_pseudo_class_selector(&mut self, n: &mut PseudoClassSelector) {
319        match &mut n.name {
320            Ident { value, .. }
321                if matches_eq_ignore_ascii_case!(
322                    &**value,
323                    "not",
324                    "is",
325                    "where",
326                    "matches",
327                    "-moz-any",
328                    "-webkit-any"
329                ) =>
330            {
331                n.name.value = n.name.value.to_ascii_lowercase();
332                n.name.raw = None;
333
334                n.visit_mut_children_with(&mut *self.with_ctx(Ctx {
335                    in_logic_combinator_selector: true,
336                    ..self.ctx
337                }));
338            }
339            _ => {
340                n.visit_mut_children_with(self);
341            }
342        }
343    }
344
345    fn visit_mut_pseudo_element_selector(&mut self, n: &mut PseudoElementSelector) {
346        n.name.value = n.name.value.to_ascii_lowercase();
347        n.name.raw = None;
348
349        n.visit_mut_children_with(self);
350    }
351
352    fn visit_mut_relative_selector_list(&mut self, n: &mut RelativeSelectorList) {
353        n.visit_mut_children_with(self);
354
355        self.compress_relative_selector_list(n);
356    }
357
358    fn visit_mut_selector_list(&mut self, n: &mut SelectorList) {
359        n.visit_mut_children_with(self);
360
361        self.compress_selector_list(n);
362    }
363
364    fn visit_mut_simple_block(&mut self, n: &mut SimpleBlock) {
365        n.visit_mut_children_with(self);
366
367        self.compress_simple_block(n);
368    }
369
370    fn visit_mut_size_feature_value(&mut self, n: &mut SizeFeatureValue) {
371        n.visit_mut_children_with(self);
372
373        self.compress_calc_sum_in_size_feature_value(n);
374        self.compress_size_feature_value_length(n);
375    }
376
377    fn visit_mut_str(&mut self, string: &mut Str) {
378        string.visit_mut_children_with(self);
379
380        if !self.need_utf8_at_rule {
381            self.need_utf8_at_rule = !contains_only_ascii_characters(&string.value);
382        }
383    }
384
385    fn visit_mut_stylesheet(&mut self, n: &mut Stylesheet) {
386        n.visit_mut_children_with(self);
387
388        self.compress_stylesheet(n);
389
390        if !self.need_utf8_at_rule
391            && n.rules
392                .first()
393                .and_then(|rule| rule.as_at_rule())
394                .and_then(|at_rule| at_rule.prelude.as_ref())
395                .and_then(|prelude| prelude.as_charset_prelude())
396                .filter(|x| x.value.eq_ignore_ascii_case("utf-8"))
397                .is_some()
398        {
399            n.rules.remove(0);
400        }
401    }
402
403    fn visit_mut_subclass_selector(&mut self, n: &mut SubclassSelector) {
404        if let SubclassSelector::PseudoClass(PseudoClassSelector { name, .. }) = n {
405            name.value = name.value.to_ascii_lowercase();
406            name.raw = None;
407        }
408
409        n.visit_mut_children_with(self);
410
411        self.compress_subclass_selector(n);
412    }
413
414    fn visit_mut_supports_condition(&mut self, n: &mut SupportsCondition) {
415        let old_in_support_condition = self.in_supports_condition;
416
417        self.in_supports_condition = true;
418
419        n.visit_mut_children_with(self);
420
421        self.in_supports_condition = old_in_support_condition;
422
423        self.compress_supports_condition(n);
424    }
425
426    fn visit_mut_supports_in_parens(&mut self, n: &mut SupportsInParens) {
427        n.visit_mut_children_with(self);
428
429        self.compress_supports_in_parens(n);
430    }
431
432    fn visit_mut_tag_name_selector(&mut self, n: &mut TagNameSelector) {
433        n.name.value.value = n.name.value.value.to_ascii_lowercase();
434        n.name.value.raw = None;
435
436        n.visit_mut_children_with(self);
437    }
438
439    fn visit_mut_time(&mut self, n: &mut Time) {
440        n.visit_mut_children_with(self);
441
442        self.compress_time(n);
443    }
444
445    fn visit_mut_token_and_span(&mut self, token_and_span: &mut TokenAndSpan) {
446        token_and_span.visit_mut_children_with(self);
447
448        if !self.need_utf8_at_rule {
449            match &token_and_span.token {
450                Token::Ident { value, .. }
451                | Token::Function { value, .. }
452                | Token::AtKeyword { value, .. }
453                | Token::String { value, .. }
454                | Token::Url { value, .. }
455                    if !contains_only_ascii_characters(value) =>
456                {
457                    self.need_utf8_at_rule = true;
458                }
459                Token::BadString { raw: value, .. } if !contains_only_ascii_characters(value) => {
460                    self.need_utf8_at_rule = true;
461                }
462                Token::BadUrl { raw: value, .. } if !contains_only_ascii_characters(value) => {
463                    self.need_utf8_at_rule = true;
464                }
465                Token::Dimension {
466                    dimension: dimension_token,
467                } if !contains_only_ascii_characters(&dimension_token.unit) => {
468                    self.need_utf8_at_rule = true;
469                }
470                _ => {}
471            }
472        }
473    }
474
475    fn visit_mut_unicode_range(&mut self, n: &mut UnicodeRange) {
476        n.visit_mut_children_with(self);
477
478        self.compress_unicode_range(n);
479    }
480
481    fn visit_mut_url(&mut self, n: &mut Url) {
482        n.name.value = n.name.value.to_ascii_lowercase();
483        n.name.raw = None;
484
485        n.visit_mut_children_with(self);
486
487        self.compress_url(n);
488    }
489}
490
491fn contains_only_ascii_characters(string: &str) -> bool {
492    string.is_ascii()
493}