swc_css_minifier/compressor/
mod.rs1use 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.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 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}