From 2fd3ced40c97fbac162f70e49c76c775e66a4552 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Dec 2022 07:39:48 +0300 Subject: [PATCH] feat(css/minifier): Improve compression of media at-rules (#6665) --- .../swc_css_minifier/src/compressor/media.rs | 341 +++++++++++------- crates/swc_css_minifier/src/compressor/mod.rs | 6 + .../fixture/compress-at-rule/media/input.css | 35 ++ .../compress-at-rule/media/output.min.css | 2 +- .../compress-at-rule/supports/input.css | 12 + .../compress-at-rule/supports/output.min.css | 2 +- 6 files changed, 264 insertions(+), 134 deletions(-) diff --git a/crates/swc_css_minifier/src/compressor/media.rs b/crates/swc_css_minifier/src/compressor/media.rs index 1e7697f9d747..cb4cf2e0e6f9 100644 --- a/crates/swc_css_minifier/src/compressor/media.rs +++ b/crates/swc_css_minifier/src/compressor/media.rs @@ -1,5 +1,6 @@ use std::mem::take; +use swc_atoms::js_word; use swc_common::DUMMY_SP; use swc_css_ast::*; @@ -60,59 +61,59 @@ impl Compressor { }); if !need_compress { - return; - } - - let mut new_conditions = Vec::with_capacity(n.conditions.len()); + let mut new_conditions = Vec::with_capacity(n.conditions.len()); - for item in take(&mut n.conditions) { - match item { - MediaConditionAllType::MediaInParens(MediaInParens::MediaCondition( - media_condition, - )) if self.is_first_or_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => - { - let mut iter = media_condition.conditions.into_iter(); - - if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = - iter.next() - { - new_conditions - .push(MediaConditionAllType::MediaInParens(media_in_parens)); - - new_conditions.extend(iter); - } - } - MediaConditionAllType::Or(media_or) => match media_or.condition { - MediaInParens::MediaCondition(media_condition) - if self.is_first_or_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => + for item in take(&mut n.conditions) { + match item { + MediaConditionAllType::MediaInParens( + MediaInParens::MediaCondition(media_condition), + ) if self.is_first_or_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => { let mut iter = media_condition.conditions.into_iter(); if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = iter.next() { - new_conditions.push(MediaConditionAllType::Or(MediaOr { - span: DUMMY_SP, - keyword: None, - condition: media_in_parens, - })); + new_conditions.push(MediaConditionAllType::MediaInParens( + media_in_parens, + )); new_conditions.extend(iter); } } + MediaConditionAllType::Or(media_or) => match media_or.condition { + MediaInParens::MediaCondition(media_condition) + if self.is_first_or_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => + { + let mut iter = media_condition.conditions.into_iter(); + + if let Some(MediaConditionAllType::MediaInParens( + media_in_parens, + )) = iter.next() + { + new_conditions.push(MediaConditionAllType::Or(MediaOr { + span: DUMMY_SP, + keyword: None, + condition: media_in_parens, + })); + + new_conditions.extend(iter); + } + } + _ => { + new_conditions.push(MediaConditionAllType::Or(media_or)); + } + }, _ => { - new_conditions.push(MediaConditionAllType::Or(media_or)); + new_conditions.push(item); } - }, - _ => { - new_conditions.push(item); } } - } - n.conditions = new_conditions; + n.conditions = new_conditions; + } } Some(MediaConditionAllType::And(_)) => { let need_compress = n.conditions.iter().any(|item| match item { @@ -136,62 +137,64 @@ impl Compressor { }); if !need_compress { - return; - } + let mut new_conditions = Vec::with_capacity(n.conditions.len()); - let mut new_conditions = Vec::with_capacity(n.conditions.len()); - - for item in take(&mut n.conditions) { - match item { - MediaConditionAllType::MediaInParens(MediaInParens::MediaCondition( - media_condition, - )) if self.is_first_and_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => - { - let mut iter = media_condition.conditions.into_iter(); - - if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = - iter.next() - { - new_conditions - .push(MediaConditionAllType::MediaInParens(media_in_parens)); - - new_conditions.extend(iter); - } - } - MediaConditionAllType::And(media_and) => match media_and.condition { - MediaInParens::MediaCondition(media_condition) - if self.is_first_and_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => + for item in take(&mut n.conditions) { + match item { + MediaConditionAllType::MediaInParens( + MediaInParens::MediaCondition(media_condition), + ) if self.is_first_and_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => { let mut iter = media_condition.conditions.into_iter(); if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = iter.next() { - new_conditions.push(MediaConditionAllType::And(MediaAnd { - span: DUMMY_SP, - keyword: None, - condition: media_in_parens, - })); + new_conditions.push(MediaConditionAllType::MediaInParens( + media_in_parens, + )); new_conditions.extend(iter); } } + MediaConditionAllType::And(media_and) => match media_and.condition { + MediaInParens::MediaCondition(media_condition) + if self.is_first_and_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => + { + let mut iter = media_condition.conditions.into_iter(); + + if let Some(MediaConditionAllType::MediaInParens( + media_in_parens, + )) = iter.next() + { + new_conditions.push(MediaConditionAllType::And(MediaAnd { + span: DUMMY_SP, + keyword: None, + condition: media_in_parens, + })); + + new_conditions.extend(iter); + } + } + _ => { + new_conditions.push(MediaConditionAllType::And(media_and)); + } + }, _ => { - new_conditions.push(MediaConditionAllType::And(media_and)); + new_conditions.push(item); } - }, - _ => { - new_conditions.push(item); } } - } - n.conditions = new_conditions; + n.conditions = new_conditions; + } } _ => {} } + + dedup(&mut n.conditions); } pub(super) fn compress_media_condition_without_or(&mut self, n: &mut MediaConditionWithoutOr) { @@ -217,65 +220,23 @@ impl Compressor { }); if !need_compress { - return; - } - - let mut new_conditions = Vec::with_capacity(n.conditions.len()); - - for item in take(&mut n.conditions) { - match item { - MediaConditionWithoutOrType::MediaInParens(MediaInParens::MediaCondition( - media_condition, - )) if self.is_first_and_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => - { - let mut iter = media_condition.conditions.into_iter(); + let mut new_conditions = Vec::with_capacity(n.conditions.len()); - if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = - iter.next() - { - new_conditions - .push(MediaConditionWithoutOrType::MediaInParens(media_in_parens)); - - for new_item in iter { - match new_item { - MediaConditionAllType::Not(media_not) => { - new_conditions - .push(MediaConditionWithoutOrType::Not(media_not)); - } - MediaConditionAllType::And(media_and) => { - new_conditions - .push(MediaConditionWithoutOrType::And(media_and)); - } - MediaConditionAllType::MediaInParens(media_in_parens) => { - new_conditions.push( - MediaConditionWithoutOrType::MediaInParens( - media_in_parens, - ), - ); - } - _ => { - unreachable!(); - } - } - } - } - } - MediaConditionWithoutOrType::And(media_and) => match media_and.condition { - MediaInParens::MediaCondition(media_condition) - if self.is_first_and_media_type(&media_condition) - && self.is_first_media_in_parens(&media_condition) => + for item in take(&mut n.conditions) { + match item { + MediaConditionWithoutOrType::MediaInParens( + MediaInParens::MediaCondition(media_condition), + ) if self.is_first_and_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => { let mut iter = media_condition.conditions.into_iter(); if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = iter.next() { - new_conditions.push(MediaConditionWithoutOrType::And(MediaAnd { - span: DUMMY_SP, - keyword: None, - condition: media_in_parens, - })); + new_conditions.push(MediaConditionWithoutOrType::MediaInParens( + media_in_parens, + )); for new_item in iter { match new_item { @@ -301,18 +262,67 @@ impl Compressor { } } } + MediaConditionWithoutOrType::And(media_and) => match media_and.condition { + MediaInParens::MediaCondition(media_condition) + if self.is_first_and_media_type(&media_condition) + && self.is_first_media_in_parens(&media_condition) => + { + let mut iter = media_condition.conditions.into_iter(); + + if let Some(MediaConditionAllType::MediaInParens(media_in_parens)) = + iter.next() + { + new_conditions.push(MediaConditionWithoutOrType::And( + MediaAnd { + span: DUMMY_SP, + keyword: None, + condition: media_in_parens, + }, + )); + + for new_item in iter { + match new_item { + MediaConditionAllType::Not(media_not) => { + new_conditions.push( + MediaConditionWithoutOrType::Not(media_not), + ); + } + MediaConditionAllType::And(media_and) => { + new_conditions.push( + MediaConditionWithoutOrType::And(media_and), + ); + } + MediaConditionAllType::MediaInParens( + media_in_parens, + ) => { + new_conditions.push( + MediaConditionWithoutOrType::MediaInParens( + media_in_parens, + ), + ); + } + _ => { + unreachable!(); + } + } + } + } + } + _ => { + new_conditions.push(MediaConditionWithoutOrType::And(media_and)); + } + }, _ => { - new_conditions.push(MediaConditionWithoutOrType::And(media_and)); + new_conditions.push(item); } - }, - _ => { - new_conditions.push(item); } } - } - n.conditions = new_conditions; + n.conditions = new_conditions; + } } + + dedup(&mut n.conditions); } pub(super) fn compress_media_in_parens(&mut self, n: &mut MediaInParens) { @@ -377,4 +387,71 @@ impl Compressor { } } } + + pub(super) fn compress_media_feature(&mut self, n: &mut MediaFeature) { + match n { + MediaFeature::Plain(MediaFeaturePlain { + span, + name: MediaFeatureName::Ident(name), + value: box MediaFeatureValue::Number(value), + }) => { + if matches!( + name.value, + js_word!("min-color") + | js_word!("min-color-index") + | js_word!("min-monochrome") + ) && value.value == 1.0 + { + *n = MediaFeature::Boolean(MediaFeatureBoolean { + span: *span, + name: MediaFeatureName::Ident(Ident { + span: name.span, + value: (*name.value).chars().skip(4).collect::().into(), + raw: None, + }), + }); + } + } + MediaFeature::Range(range) => { + if let MediaFeatureValue::Ident(name) = &*range.left { + if matches!( + name.value, + js_word!("color") | js_word!("color-index") | js_word!("monochrome") + ) && matches!(&*range.right, MediaFeatureValue::Number(number) if number.value == 1.0) + && range.comparison == MediaFeatureRangeComparison::Ge + { + *n = MediaFeature::Boolean(MediaFeatureBoolean { + span: range.span, + name: MediaFeatureName::Ident(name.clone()), + }); + } else if range.comparison == MediaFeatureRangeComparison::Eq { + *n = MediaFeature::Plain(MediaFeaturePlain { + span: range.span, + name: MediaFeatureName::Ident(name.clone()), + value: range.right.clone(), + }); + } + } else if let MediaFeatureValue::Ident(name) = &*range.right { + if matches!( + name.value, + js_word!("color") | js_word!("color-index") | js_word!("monochrome") + ) && matches!(&*range.left, MediaFeatureValue::Number(number) if number.value == 1.0) + && range.comparison == MediaFeatureRangeComparison::Le + { + *n = MediaFeature::Boolean(MediaFeatureBoolean { + span: range.span, + name: MediaFeatureName::Ident(name.clone()), + }); + } else if range.comparison == MediaFeatureRangeComparison::Eq { + *n = MediaFeature::Plain(MediaFeaturePlain { + span: range.span, + name: MediaFeatureName::Ident(name.clone()), + value: range.left.clone(), + }); + } + } + } + _ => {} + } + } } diff --git a/crates/swc_css_minifier/src/compressor/mod.rs b/crates/swc_css_minifier/src/compressor/mod.rs index 1a4b3e6c6c71..cecde666d895 100644 --- a/crates/swc_css_minifier/src/compressor/mod.rs +++ b/crates/swc_css_minifier/src/compressor/mod.rs @@ -175,6 +175,12 @@ impl VisitMut for Compressor { self.compress_media_in_parens(n); } + fn visit_mut_media_feature(&mut self, n: &mut MediaFeature) { + n.visit_mut_children_with(self); + + self.compress_media_feature(n); + } + fn visit_mut_media_feature_value(&mut self, n: &mut MediaFeatureValue) { n.visit_mut_children_with(self); diff --git a/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/input.css b/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/input.css index d0367c207708..4a0b61a5076c 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/input.css +++ b/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/input.css @@ -73,3 +73,38 @@ @media (max-width: calc(5px + 1rem)) { body { color: red; } } @media (-webkit-calc(10px + 100px) < width <= calc(1000px + 10px)) { body { color: red; } } + +@media (color) { body { color: red; } } +@media (min-color: 1) { body { color: red; } } +@media (color >= 1) { body { color: red; } } +@media (1 <= color) { body { color: red; } } + +@media (color-index) { body { color: red; } } +@media (min-color-index: 1) { body { color: red; } } +@media (color-index >= 1) { body { color: red; } } +@media (1 <= color-index) { body { color: red; } } + +@media (monochrome) { body { color: red; } } +@media (min-monochrome: 1) { body { color: red; } } +@media (monochrome >= 1) { body { color: red; } } +@media (1 <= monochrome) { body { color: red; } } + +@media (width = 1024px) { body {background: green;} } +@media (1024px = width) { body {background: green;} } +@media (width: 1024px) { body {background: green;} } + +@media (min-width: 900px) and (min-width: 900px) and (min-width: 900px), + (min-width: 900px) or (min-width: 900px) or (min-width: 900px) or (min-width: 900px) +{ + a { + color: red; + } +} + +@media screen and (min-width: 900px) and (min-width: 900px) and (min-width: 900px), + (min-width: 900px) or (min-width: 900px) or (min-width: 900px) or (min-width: 900px) +{ + a { + color: red; + } +} \ No newline at end of file diff --git a/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/output.min.css b/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/output.min.css index 8d32e1ab40b3..424075fdcc5e 100644 --- a/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/output.min.css +++ b/crates/swc_css_minifier/tests/fixture/compress-at-rule/media/output.min.css @@ -1 +1 @@ -@media print{body{font-size:10pt}}@media(min-width:900px){a{color:red}}@media only screen and (min-width:320px){body{line-height:1.4}}@media(400px<=width<=700px){body{line-height:1.4}}@media screen and (min-width:900px){article{padding:1rem 3rem}}@media(height>600px){body{line-height:1.4}}@media(400px<=width<=700px){body{line-height:1.4}}@media(foo:bar){body{line-height:1.4}}@media(min-width:30em)and (orientation:landscape){.test{color:red}}@media screen and (min-width:30em)and (orientation:landscape){.test{color:red}}@media screen and (min-width:30em)and (max-width:300px)and (orientation:landscape){.test{color:red}}@media(min-height:680px),screen and (orientation:portrait){.test{color:red}}@media(not (color))or (hover){.test{color:red}}@media only screen and ((min-width:320px)and (max-width:1480px)){body{background:red}}@media((min-width:320px)and (max-width:1480px)){body{background:red}}@media((min-width:320px)or (max-width:1480px)){body{background:red}}@media(min-width:320px)and (max-width:1480px)and (orientation:landscape){body{background:red}}@media(min-width:320px)or (max-width:1480px)or (orientation:landscape){body{background:red}}@media screen and (min-width:320px)and (max-width:1480px)and (orientation:landscape){body{background:red}}@media not (resolution:-300dpi){body{background:green}}@media(grid)and (max-width:15em){body{background:green}}@media(max-width:calc(5px + 1rem)){body{color:red}}@media(110px600px){body{line-height:1.4}}@media(400px<=width<=700px){body{line-height:1.4}}@media(foo:bar){body{line-height:1.4}}@media(min-width:30em)and (orientation:landscape){.test{color:red}}@media screen and (min-width:30em)and (orientation:landscape){.test{color:red}}@media screen and ((min-width:30em)and (max-width:300px))and (orientation:landscape){.test{color:red}}@media(min-height:680px),screen and (orientation:portrait){.test{color:red}}@media(not (color))or (hover){.test{color:red}}@media only screen and ((min-width:320px)and (max-width:1480px)){body{background:red}}@media((min-width:320px)and (max-width:1480px)){body{background:red}}@media((min-width:320px)or (max-width:1480px)){body{background:red}}@media((min-width:320px)and (max-width:1480px))and (orientation:landscape){body{background:red}}@media(min-width:320px)and ((max-width:1480px)and (orientation:landscape)){body{background:red}}@media((min-width:320px)or (max-width:1480px))or (orientation:landscape){body{background:red}}@media(min-width:320px)or ((max-width:1480px)or (orientation:landscape)){body{background:red}}@media screen and ((min-width:320px)and (max-width:1480px))and (orientation:landscape){body{background:red}}@media screen and (min-width:320px)and ((max-width:1480px)and (orientation:landscape)){body{background:red}}@media not (resolution:-300dpi){body{background:green}}@media(grid)and (max-width:15em){body{background:green}}@media(max-width:calc(5px + 1rem)){body{color:red}}@media(110px