feat(css/minifier): Compress selectors (#3623)

This commit is contained in:
Alexander Akait 2022-02-19 19:17:46 +03:00 committed by GitHub
parent b1190ee203
commit 9e691fe75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 455 additions and 4 deletions

View File

@ -26,7 +26,7 @@ impl VisitMut for CompressEmpty {
| Rule::AtRule(AtRule::Property(PropertyRule { block, .. }))
if block.value.is_empty() =>
{
return false;
false
}
_ => true,
});
@ -72,7 +72,7 @@ impl VisitMut for CompressEmpty {
| ComponentValue::KeyframeBlock(KeyframeBlock { block, .. })
if block.value.is_empty() =>
{
return false;
false
}
_ => true,
});

View File

@ -2,4 +2,5 @@ pub mod angle;
pub mod easing_function;
pub mod empty;
pub mod keyframes;
pub mod selector;
pub mod time;

View File

@ -0,0 +1,282 @@
use swc_common::DUMMY_SP;
use swc_css_ast::*;
use swc_css_visit::{VisitMut, VisitMutWith};
pub fn compress_selector() -> impl VisitMut {
CompressSelector {}
}
struct CompressSelector {}
impl CompressSelector {}
impl VisitMut for CompressSelector {
fn visit_mut_nth(&mut self, nth: &mut Nth) {
nth.visit_mut_children_with(self);
match &nth.nth {
// `2n+1`, `2n-1`, `2n-3`, etc => `odd`
NthValue::AnPlusB(AnPlusB {
a: Some(a),
b: Some(b),
span,
..
}) if *a == 2 && (*b == 1 || b % 2 == -1) => {
nth.nth = NthValue::Ident(Ident {
span: *span,
value: "odd".into(),
raw: "odd".into(),
});
}
// `2n-0`, `2n-2`, `2n-4`, etc => `2n`
NthValue::AnPlusB(AnPlusB {
a: Some(a),
b: Some(b),
span,
..
}) if *a == 2 && *b < 0 && b % 2 == 0 => {
nth.nth = NthValue::AnPlusB(AnPlusB {
span: *span,
a: Some(2),
a_raw: Some("2".into()),
b: None,
b_raw: None,
});
}
// `even` => `2n`
NthValue::Ident(Ident { value, span, .. }) if &*value.to_lowercase() == "even" => {
nth.nth = NthValue::AnPlusB(AnPlusB {
span: *span,
a: Some(2),
a_raw: Some("2".into()),
b: None,
b_raw: None,
});
}
// `0n+5` => `5`, `0n-5` => `-5`, etc
NthValue::AnPlusB(AnPlusB {
a: Some(a),
b,
b_raw,
span,
..
}) if *a == 0 => {
nth.nth = NthValue::AnPlusB(AnPlusB {
span: *span,
a: None,
a_raw: None,
b: *b,
b_raw: b_raw.clone(),
});
}
// `-5n+0` => `-5n`, etc
NthValue::AnPlusB(AnPlusB {
a,
a_raw,
b: Some(b),
span,
..
}) if *b == 0 => {
nth.nth = NthValue::AnPlusB(AnPlusB {
span: *span,
a: *a,
a_raw: a_raw.clone(),
b: None,
b_raw: None,
});
}
_ => {}
}
}
fn visit_mut_subclass_selector(&mut self, subclass_selector: &mut SubclassSelector) {
subclass_selector.visit_mut_children_with(self);
match &subclass_selector {
SubclassSelector::PseudoElement(PseudoElementSelector { name, span, .. }) => {
match &*name.value.to_lowercase() {
"before" | "after" | "first-letter" | "first-line" => {
*subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
span: *span,
name: name.clone(),
children: None,
})
}
_ => {}
}
}
SubclassSelector::PseudoClass(PseudoClassSelector {
name,
children: Some(children),
span,
..
}) if &*name.value.to_lowercase() == "nth-child" && children.len() == 1 => {
match children.get(0) {
Some(PseudoSelectorChildren::Nth(Nth {
nth:
NthValue::AnPlusB(AnPlusB {
a: None,
b: Some(b),
..
}),
..
})) if *b == 1 => {
*subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
span: *span,
name: Ident {
span: DUMMY_SP,
value: "first-child".into(),
raw: "first-child".into(),
},
children: None,
})
}
_ => {}
}
}
SubclassSelector::PseudoClass(PseudoClassSelector {
name,
children: Some(children),
span,
..
}) if &*name.value.to_lowercase() == "nth-last-child" && children.len() == 1 => {
match children.get(0) {
Some(PseudoSelectorChildren::Nth(Nth {
nth:
NthValue::AnPlusB(AnPlusB {
a: None,
b: Some(b),
..
}),
..
})) if *b == 1 => {
*subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
span: *span,
name: Ident {
span: DUMMY_SP,
value: "last-child".into(),
raw: "last-child".into(),
},
children: None,
})
}
_ => {}
}
}
SubclassSelector::PseudoClass(PseudoClassSelector {
name,
children: Some(children),
span,
..
}) if &*name.value.to_lowercase() == "nth-of-type" && children.len() == 1 => {
match children.get(0) {
Some(PseudoSelectorChildren::Nth(Nth {
nth:
NthValue::AnPlusB(AnPlusB {
a: None,
b: Some(b),
..
}),
..
})) if *b == 1 => {
*subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
span: *span,
name: Ident {
span: DUMMY_SP,
value: "first-of-type".into(),
raw: "first-of-type".into(),
},
children: None,
})
}
_ => {}
}
}
SubclassSelector::PseudoClass(PseudoClassSelector {
name,
children: Some(children),
span,
..
}) if &*name.value.to_lowercase() == "nth-last-of-type" && children.len() == 1 => {
match children.get(0) {
Some(PseudoSelectorChildren::Nth(Nth {
nth:
NthValue::AnPlusB(AnPlusB {
a: None,
b: Some(b),
..
}),
..
})) if *b == 1 => {
*subclass_selector = SubclassSelector::PseudoClass(PseudoClassSelector {
span: *span,
name: Ident {
span: DUMMY_SP,
value: "last-of-type".into(),
raw: "last-of-type".into(),
},
children: None,
})
}
_ => {}
}
}
_ => {}
}
}
fn visit_mut_compound_selector(&mut self, compound_selector: &mut CompoundSelector) {
compound_selector.visit_mut_children_with(self);
if let Some(TypeSelector::Universal(UniversalSelector { prefix: None, .. })) =
&compound_selector.type_selector
{
compound_selector.type_selector = None;
}
}
fn visit_mut_attribute_selector(&mut self, attribute_selector: &mut AttributeSelector) {
attribute_selector.visit_mut_children_with(self);
if let Some(AttributeSelectorValue::Str(Str { value, span, .. })) =
&attribute_selector.value
{
// A valid unquoted attribute value in CSS is any string of text that is not the
// empty string, is not just a hyphen (-), consists of escaped characters and/or
// characters matching [-_a-zA-Z0-9\u00A0-\u10FFFF] entirely, and doesnt start
// with a digit or a hyphen followed by a digit.
// is any string of text that is not the empty string, is not just a hyphen (-)
if value.is_empty() || value == "-" {
return;
}
let chars = value.chars();
let mut starts_with_hyphen = false;
for (idx, char) in chars.enumerate() {
match char {
'0'..='9' if idx == 0 || (starts_with_hyphen && idx == 1) => {
return;
}
'-' => {
if idx == 0 {
starts_with_hyphen = true;
}
}
_ if !matches!(char, '-' | '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '\u{00a0}'..='\u{10FFFF}') =>
{
return;
}
_ => {}
}
}
attribute_selector.value = Some(AttributeSelectorValue::Ident(Ident {
span: *span,
value: value.clone(),
raw: value.clone(),
}));
}
}
}

View File

@ -3,13 +3,14 @@ use swc_css_visit::VisitMutWith;
use self::compress::{
angle::compress_angle, easing_function::compress_easing_function, empty::compress_empty,
keyframes::compress_keyframes, time::compress_time,
keyframes::compress_keyframes, selector::compress_selector, time::compress_time,
};
mod compress;
pub fn minify(stylesheet: &mut Stylesheet) {
stylesheet.visit_mut_with(&mut compress_empty());
stylesheet.visit_mut_with(&mut compress_selector());
stylesheet.visit_mut_with(&mut compress_keyframes());
stylesheet.visit_mut_with(&mut compress_easing_function());
stylesheet.visit_mut_with(&mut compress_time());

View File

@ -0,0 +1,166 @@
a[color="blue"] {color:blue}
a[color=""]{color:blue}
a[color="-"]{color:blue}
a[color="."] {color:blue}
a[color=" "]{color:blue}
a[color=" a "]{color:blue}
a[color=" a"]{color:blue}
a[color="a "]{color:blue}
a[color="\22"]{color:blue}
a[color="B\26 W\3F "]{color:blue}
a[color="\47"]{color:blue}
a[color="😂"]{color:blue}
a[color="👩‍🦼"]{color:blue}
a[color="1"]{color:blue}
a[color="--"]{color:blue}
a[color="-1"]{color:blue}
a[color="-404"]{color:blue}
a[color="-x"]{color:blue}
a[ color= "blue" ] {color:blue}
a[color="blue" i ] {color:blue}
a[class="woop \
woop woop"] {color:blue}
a[class="woop_woop_woop"]{color:blue}
h1[class=" *.js "] + *.js{color:blue}
h1::before {color:blue}
h1::BEfoRE {color:blue}
h1::after {color:blue}
h1::first-letter {color:blue}
h1::first-line {color:blue}
* {color:blue}
*[hreflang|=en]{color:blue}
*.warning{color:blue}
*#myid{color:blue}
*::before{content:"test";color:blue}
*:hover{}
*.class[hreflang|=en]{color:blue}
foo|*[hreflang|=en]{color:blue}
*|*[hreflang|=en]{color:blue}
div {& *[hreflang|=en]{color:blue}}
div {&*[hreflang|=en]{color:blue}}
div {&div {color:blue}}
div {&* {color:blue}}
div {& {color:blue}}
div {&*.class {color:blue}}
/* For example, the following selector matches any element that is being hovered or focused, regardless of its namespace. In particular, it is not limited to only matching elements in the default namespace that are being hovered or focused. */
*|*:is(:hover, :focus) {color:blue}
/* The following selector, however, represents only hovered or focused elements that are in the default namespace, because it uses an explicit universal selector within the :is() notation: */
*|*:is(*:hover, *:focus) {color: blue}
div *:first-child{color:blue}
legend + * {color:blue}
* + legend {color:blue}
* + * {color:blue}
p:nth-child(1){color:blue}
p:NTH-CHILD(1){color:blue}
p:nth-child(2n + 1){color:blue}
p:nth-child(2N + 1){color:blue}
p:nth-child(even){color:blue}
p:nth-child(EVEN){color:blue}
p:nth-child(2n){color:blue}
p:nth-child(1){color: blue}
p:nth-child(0n+1){color: blue}
p:nth-child(-0n+1){color: blue}
p:nth-child(+0n+1) {color:blue}
p:nth-child(0n+5){color: blue}
p:nth-child(0n-5){color: blue}
p:nth-child(+0n-5){color: blue}
p:nth-child(-0n-5){color: blue}
p:nth-child(-1n+3) {color: blue}
p:nth-child(+1n+3) {color: blue}
p:nth-child(1n+3) {color: blue}
p:nth-child(n+3) {color: blue}
p:nth-child(-n+3) {color: blue}
p:nth-child(+n+3) {color: blue}
p:nth-child(+n-3) {color: blue}
p:nth-child(0n-3) {color: blue}
p:nth-child(-0n-3) {color: blue}
p:nth-child(+0n-3) {color: blue}
p:nth-child(n+0) {color: blue}
p:nth-child(+n+0) {color: blue}
p:nth-child(-n+0) {color: blue}
p:nth-child(5n+0) {color: blue}
p:nth-child(-5n+0) {color: blue}
p:nth-child(+5n+0) {color: blue}
p:nth-child(-2n+1) {color:blue}
p:nth-child(n - 0){color:blue}
p:nth-child(n + 0){color:blue}
p:nth-child(-n + 0){color:blue}
p:nth-child(+n + 0){color:blue}
p:nth-of-type(2n+1) {color:blue}
p:nth-last-col(2n+1) {color:blue}
p:nth-col(2n+1) {color:blue}
p:nth-child(1n - 2){color:blue}
p:nth-child(1n + 2){color:blue}
p:nth-child(-1n + 2){color:blue}
p:nth-child(1n){color:blue}
p:nth-child(-1n){color:blue}
p:nth-child(+1n){color:blue}
p:nth-child(0n+5){color:blue}
p:nth-child(1n+0){color:blue}
p:nth-child(n+0){color:blue}
p:nth-child(-n+ 6){color:blue}
p:nth-child(2n-1){color:blue}
p:nth-child(2n+5){color:blue}
p:nth-child(2n+4){color:blue}
p:nth-child(2n+3){color:blue}
p:nth-child(2n+2){color:blue}
p:nth-child(2n+1){color:blue}
p:nth-child(2n-1){color:blue}
p:nth-child(2n-2){color:blue}
p:nth-child(2n-3){color:blue}
p:nth-child(2n-4){color:blue}
p:nth-child(2n-5){color:blue}
p:nth-child(2n-6){color:blue}
p:nth-child(2n-7){color:blue}
p:nth-child(2n-8){color:blue}
p:nth-child(2n-9){color:blue}
p:nth-child(2n-10){color:blue}
p:nth-child(2n-0){color:blue}
p:nth-child(2n+10){color:blue}
p:nth-child(n+8):nth-child(-n+15){color:blue}
p:nth-last-of-type(2n + 2){color:blue}
body>h2:not(:first-of-type):not(:last-of-type){color:blue}
.class:nth-child(1){color:red}
.class:nth-child(+1){color:red}
/* No */
.class:nth-child(-1){color:red}
.class:nth-last-child(1){color:red}
.class:nth-last-child(+1){color:red}
/* No */
.class:nth-last-child(-1){color:red}
.class:nth-of-type(1){color:red}
.class:nth-of-type(+1){color:red}
/* No */
.class:nth-of-type(-1){color:red}
.class:nth-last-of-type(1){color:red}
.class:nth-last-of-type(+1){color:red}
/* No */
.class:nth-last-of-type(-1){color:red}
.foo.foo.foo{}
.foo.foo.foo{color:red}
.class#id#id {color:red}
#id#id.class {color:red}
#id.class#id {color:red}
#id#id#id {color:red}
[attr][attr][attr] {color:red}
[attr].class[attr].class[attr] {color:red}
.foo.foo.foo#id#id#id[attr][attr][attr]{}

View File

@ -0,0 +1 @@
a[color=blue]{color:blue}a[color=""]{color:blue}a[color="-"]{color:blue}a[color="."]{color:blue}a[color=" "]{color:blue}a[color=" a "]{color:blue}a[color=" a"]{color:blue}a[color="a "]{color:blue}a[color='"']{color:blue}a[color="B&W?"]{color:blue}a[color=G]{color:blue}a[color=😂]{color:blue}a[color=👩🦼]{color:blue}a[color="1"]{color:blue}a[color=--]{color:blue}a[color="-1"]{color:blue}a[color="-404"]{color:blue}a[color=-x]{color:blue}a[color=blue]{color:blue}a[color=blue i]{color:blue}a[class="woop woop woop"]{color:blue}a[class=woop_woop_woop]{color:blue}h1[class=" *.js "]+.js{color:blue}h1:before{color:blue}h1:BEfoRE{color:blue}h1:after{color:blue}h1:first-letter{color:blue}h1:first-line{color:blue}{color:blue}[hreflang|=en]{color:blue}.warning{color:blue}#myid{color:blue}:before{content:"test";color:blue}.class[hreflang|=en]{color:blue}foo|*[hreflang|=en]{color:blue}*|*[hreflang|=en]{color:blue}div{& [hreflang|=en]{color:blue}}div{&[hreflang|=en]{color:blue}}div{&div{color:blue}}div{&{color:blue}}div{&{color:blue}}div{&.class{color:blue}}*|*:is(:hover, :focus){color:blue}*|*:is(*:hover, *:focus){color:blue}div :first-child{color:blue}legend+{color:blue}+legend{color:blue}+{color:blue}p:first-child{color:blue}p:first-child{color:blue}p:nth-child(odd){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(2n){color:blue}p:nth-child(2n){color:blue}p:first-child{color:blue}p:first-child{color:blue}p:first-child{color:blue}p:first-child{color:blue}p:nth-child(5){color:blue}p:nth-child(-5){color:blue}p:nth-child(-5){color:blue}p:nth-child(-5){color:blue}p:nth-child(-n+3){color:blue}p:nth-child(n+3){color:blue}p:nth-child(n+3){color:blue}p:nth-child(n+3){color:blue}p:nth-child(-n+3){color:blue}p:nth-child(n+3){color:blue}p:nth-child(n-3){color:blue}p:nth-child(-3){color:blue}p:nth-child(-3){color:blue}p:nth-child(-3){color:blue}p:nth-child(n){color:blue}p:nth-child(n){color:blue}p:nth-child(-n){color:blue}p:nth-child(5n){color:blue}p:nth-child(-5n){color:blue}p:nth-child(5n){color:blue}p:nth-child(-2n+1){color:blue}p:nth-child(n){color:blue}p:nth-child(n){color:blue}p:nth-child(-n){color:blue}p:nth-child(n){color:blue}p:nth-of-type(odd){color:blue}p:nth-last-col(odd){color:blue}p:nth-col(odd){color:blue}p:nth-child(n-2){color:blue}p:nth-child(n+2){color:blue}p:nth-child(-n+2){color:blue}p:nth-child(n){color:blue}p:nth-child(-n){color:blue}p:nth-child(n){color:blue}p:nth-child(5){color:blue}p:nth-child(n){color:blue}p:nth-child(n){color:blue}p:nth-child(-n+6){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n+5){color:blue}p:nth-child(2n+4){color:blue}p:nth-child(2n+3){color:blue}p:nth-child(2n+2){color:blue}p:nth-child(odd){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(odd){color:blue}p:nth-child(2n){color:blue}p:nth-child(2n){color:blue}p:nth-child(2n+10){color:blue}p:nth-child(n+8):nth-child(-n+15){color:blue}p:nth-last-of-type(2n+2){color:blue}body>h2:not(:first-of-type):not(:last-of-type){color:blue}.class:first-child{color:red}.class:first-child{color:red}.class:nth-child(-1){color:red}.class:last-child{color:red}.class:last-child{color:red}.class:nth-last-child(-1){color:red}.class:first-of-type{color:red}.class:first-of-type{color:red}.class:nth-of-type(-1){color:red}.class:last-of-type{color:red}.class:last-of-type{color:red}.class:nth-last-of-type(-1){color:red}.foo.foo.foo{color:red}.class#id#id{color:red}#id#id.class{color:red}#id.class#id{color:red}#id#id#id{color:red}[attr][attr][attr]{color:red}[attr].class[attr].class[attr]{color:red}

File diff suppressed because one or more lines are too long