From ac4f14ad7b70e67c41507cafd997f6b4e346e815 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 13 Apr 2022 11:28:09 +0300 Subject: [PATCH] fix(css/prefixer): Handle at-rules and don't generate unnecessary prefixes (#4318) --- crates/swc_css_prefixer/src/prefixer.rs | 279 +++++++++++++----- .../tests/fixture/keyframes/output.css | 99 +++++++ .../tests/fixture/transform/output.css | 18 ++ .../tests/fixture/viewport/input.css | 4 + .../tests/fixture/viewport/output.css | 12 + .../tests/fixture/writing-mode/output.css | 28 ++ 6 files changed, 360 insertions(+), 80 deletions(-) create mode 100644 crates/swc_css_prefixer/tests/fixture/viewport/input.css create mode 100644 crates/swc_css_prefixer/tests/fixture/viewport/output.css diff --git a/crates/swc_css_prefixer/src/prefixer.rs b/crates/swc_css_prefixer/src/prefixer.rs index c90d5216d39..d4fc2e288d0 100644 --- a/crates/swc_css_prefixer/src/prefixer.rs +++ b/crates/swc_css_prefixer/src/prefixer.rs @@ -437,11 +437,10 @@ macro_rules! str_to_ident { #[derive(Default)] struct Prefixer { - in_stylesheet: bool, - in_media_query_list: bool, in_keyframe_block: bool, - simple_block: Option, added_rules: Vec, + rule_prefix: Option, + simple_block: Option, added_declarations: Vec, } @@ -498,6 +497,7 @@ impl Prefixer { } } +#[derive(Clone, Debug, PartialEq)] pub enum Prefix { Webkit, Moz, @@ -506,15 +506,130 @@ pub enum Prefix { } impl VisitMut for Prefixer { + fn visit_mut_stylesheet(&mut self, n: &mut Stylesheet) { + let mut new = vec![]; + + for mut n in take(&mut n.rules) { + n.visit_mut_children_with(self); + + new.append(&mut self.added_rules); + new.push(n); + } + + n.rules = new; + } + // TODO handle declarations in `@media`/`@support` - // TODO handle `@viewport` - // TODO handle `@keyframes` + // TODO don't generate wrong prefixes in prefixed selectors and at-rules + fn visit_mut_at_rule(&mut self, n: &mut AtRule) { + let mut added_at_rules = vec![]; + + // TODO avoid duplicate + match &n.name { + AtRuleName::Ident(Ident { value, .. }) + if value.as_ref().eq_ignore_ascii_case("viewport") => + { + let new_name = AtRuleName::Ident(Ident { + span: DUMMY_SP, + value: "-ms-viewport".into(), + raw: "-ms-viewport".into(), + }); + + added_at_rules.push(( + Some(Prefix::Ms), + AtRule { + span: DUMMY_SP, + name: new_name, + prelude: n.prelude.clone(), + block: n.block.clone(), + }, + )); + + let new_name = AtRuleName::Ident(Ident { + span: DUMMY_SP, + value: "-o-viewport".into(), + raw: "-o-viewport".into(), + }); + + added_at_rules.push(( + Some(Prefix::O), + AtRule { + span: DUMMY_SP, + name: new_name, + prelude: n.prelude.clone(), + block: n.block.clone(), + }, + )); + } + AtRuleName::Ident(Ident { value, .. }) + if value.as_ref().eq_ignore_ascii_case("keyframes") => + { + let new_name = AtRuleName::Ident(Ident { + span: DUMMY_SP, + value: "-webkit-keyframes".into(), + raw: "-webkit-keyframes".into(), + }); + + added_at_rules.push(( + Some(Prefix::Webkit), + AtRule { + span: DUMMY_SP, + name: new_name, + prelude: n.prelude.clone(), + block: n.block.clone(), + }, + )); + + let new_name = AtRuleName::Ident(Ident { + span: DUMMY_SP, + value: "-moz-keyframes".into(), + raw: "-moz-keyframes".into(), + }); + + added_at_rules.push(( + Some(Prefix::Moz), + AtRule { + span: DUMMY_SP, + name: new_name, + prelude: n.prelude.clone(), + block: n.block.clone(), + }, + )); + + let new_name = AtRuleName::Ident(Ident { + span: DUMMY_SP, + value: "-o-keyframes".into(), + raw: "-o-keyframes".into(), + }); + + added_at_rules.push(( + Some(Prefix::O), + AtRule { + span: DUMMY_SP, + name: new_name, + prelude: n.prelude.clone(), + block: n.block.clone(), + }, + )); + } + _ => {} + } + + n.visit_mut_children_with(self); + + for mut rule in take(&mut added_at_rules) { + let old_rule_prefix = self.rule_prefix.clone(); + + self.rule_prefix = rule.0; + + rule.1.visit_mut_children_with(self); + + self.added_rules.push(Rule::AtRule(rule.1)); + self.rule_prefix = old_rule_prefix; + } + } fn visit_mut_media_query_list(&mut self, media_query_list: &mut MediaQueryList) { - let old_in_media_query_list = self.in_media_query_list; - - self.in_media_query_list = true; - let mut new = vec![]; for mut n in take(&mut media_query_list.queries) { @@ -560,8 +675,6 @@ impl VisitMut for Prefixer { } media_query_list.queries = new; - - self.in_media_query_list = old_in_media_query_list; } fn visit_mut_keyframe_block(&mut self, n: &mut KeyframeBlock) { @@ -574,14 +687,9 @@ impl VisitMut for Prefixer { self.in_keyframe_block = old_in_keyframe_block; } - // TODO don't generate wrong prefixes in prefixed selectors fn visit_mut_qualified_rule(&mut self, n: &mut QualifiedRule) { n.visit_mut_children_with(self); - if !self.in_stylesheet { - return; - } - if let QualifiedRulePrelude::Invalid(_) = n.prelude { return; } @@ -700,23 +808,28 @@ impl VisitMut for Prefixer { } } - fn visit_mut_stylesheet(&mut self, n: &mut Stylesheet) { - let old_in_stylesheet = self.in_stylesheet; + fn visit_mut_simple_block(&mut self, simple_block: &mut SimpleBlock) { + let old_simple_block = self.simple_block.clone(); - self.in_stylesheet = true; + self.simple_block = Some(simple_block.clone()); let mut new = vec![]; - for mut n in take(&mut n.rules) { + for mut n in take(&mut simple_block.value) { n.visit_mut_children_with(self); - new.append(&mut self.added_rules); + new.extend( + self.added_declarations + .drain(..) + .map(DeclarationOrAtRule::Declaration) + .map(ComponentValue::DeclarationOrAtRule), + ); new.push(n); } - n.rules = new; + simple_block.value = new; - self.in_stylesheet = old_in_stylesheet; + self.simple_block = old_simple_block; } fn visit_mut_declaration(&mut self, n: &mut Declaration) { @@ -839,52 +952,82 @@ impl VisitMut for Prefixer { // TODO avoid duplication insert macro_rules! add_declaration { ($prefix:expr,$name:expr) => {{ - let need_prefix = match self.get_declaration_by_name($name) { - Some(_) => false, - _ => true, + // Use only specific prefix in prefixed at-rules or rule, i.e. + // don't use `-moz` prefix for properties in `@-webkit-keyframes` at-rule + let need_prefix = if let Some(rule_prefix) = &self.rule_prefix { + if $prefix != *rule_prefix { + false + } else { + true + } + } else { + true }; if need_prefix { - let name = DeclarationName::Ident(Ident { - span: DUMMY_SP, - value: $name.into(), - raw: $name.into(), - }); - let new_value = match $prefix { - Prefix::Webkit => webkit_value.clone(), - Prefix::Moz => moz_value.clone(), - Prefix::O => o_value.clone(), - Prefix::Ms => ms_value.clone(), + // Check we don't have prefixed property + let need_prefix = match self.get_declaration_by_name($name) { + Some(_) => false, + _ => true, }; - self.added_declarations.push(Declaration { - span: n.span, - name, - value: new_value, - important: n.important.clone(), - }); + if need_prefix { + let name = DeclarationName::Ident(Ident { + span: DUMMY_SP, + value: $name.into(), + raw: $name.into(), + }); + let new_value = match $prefix { + Prefix::Webkit => webkit_value.clone(), + Prefix::Moz => moz_value.clone(), + Prefix::O => o_value.clone(), + Prefix::Ms => ms_value.clone(), + }; + + self.added_declarations.push(Declaration { + span: n.span, + name, + value: new_value, + important: n.important.clone(), + }); + } } }}; ($prefix:expr,$name:expr,$value:expr) => {{ - let need_prefix = match self.get_declaration_by_name($name) { - Some(_) => false, - _ => true, + // Use only specific prefix in prefixed at-rules or rule, i.e. + // don't use `-moz` prefix for properties in `@-webkit-keyframes` at-rule + let need_prefix = if let Some(rule_prefix) = &self.rule_prefix { + if $prefix != *rule_prefix { + false + } else { + true + } + } else { + true }; if need_prefix { - let name = DeclarationName::Ident(Ident { - span: DUMMY_SP, - value: $name.into(), - raw: $name.into(), - }); + // Check we don't have prefixed property + let need_prefix = match self.get_declaration_by_name($name) { + Some(_) => false, + _ => true, + }; - self.added_declarations.push(Declaration { - span: n.span, - name, - value: $value, - important: n.important.clone(), - }); + if need_prefix { + let name = DeclarationName::Ident(Ident { + span: DUMMY_SP, + value: $name.into(), + raw: $name.into(), + }); + + self.added_declarations.push(Declaration { + span: n.span, + name, + value: $value, + important: n.important.clone(), + }); + } } }}; } @@ -2249,28 +2392,4 @@ impl VisitMut for Prefixer { }); } } - - fn visit_mut_simple_block(&mut self, simple_block: &mut SimpleBlock) { - let old_simple_block = self.simple_block.clone(); - - self.simple_block = Some(simple_block.clone()); - - let mut new = vec![]; - - for mut n in take(&mut simple_block.value) { - n.visit_mut_children_with(self); - - new.extend( - self.added_declarations - .drain(..) - .map(DeclarationOrAtRule::Declaration) - .map(ComponentValue::DeclarationOrAtRule), - ); - new.push(n); - } - - simple_block.value = new; - - self.simple_block = old_simple_block; - } } diff --git a/crates/swc_css_prefixer/tests/fixture/keyframes/output.css b/crates/swc_css_prefixer/tests/fixture/keyframes/output.css index bc074c9031b..735841d5dd7 100644 --- a/crates/swc_css_prefixer/tests/fixture/keyframes/output.css +++ b/crates/swc_css_prefixer/tests/fixture/keyframes/output.css @@ -1,3 +1,75 @@ +@-webkit-keyframes anim { + from { + top: -webkit-calc(10% + 10px); + top: -moz-calc(10% + 10px); + top: calc(10% + 10px); + -webkit-transform: rotate(10deg); + transform: rotate(10deg); + } + 50% { + top: 0; + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + to { + top: -webkit-calc(10%); + top: -moz-calc(10%); + top: calc(10%); + -webkit-transform: rotate(0); + transform: rotate(0); + } +} +@-moz-keyframes anim { + from { + top: -webkit-calc(10% + 10px); + top: -moz-calc(10% + 10px); + top: calc(10% + 10px); + -moz-transform: rotate(10deg); + transform: rotate(10deg); + } + 50% { + top: 0; + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + to { + top: -webkit-calc(10%); + top: -moz-calc(10%); + top: calc(10%); + -moz-transform: rotate(0); + transform: rotate(0); + } +} +@-o-keyframes anim { + from { + top: -webkit-calc(10% + 10px); + top: -moz-calc(10% + 10px); + top: calc(10% + 10px); + -o-transform: rotate(10deg); + transform: rotate(10deg); + } + 50% { + top: 0; + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -ms-flexbox; + display: flex; + } + to { + top: -webkit-calc(10%); + top: -moz-calc(10%); + top: calc(10%); + -o-transform: rotate(0); + transform: rotate(0); + } +} @keyframes anim { from { top: -webkit-calc(10% + 10px); @@ -26,9 +98,36 @@ transform: rotate(0); } } +@-webkit-keyframes inside {} +@-moz-keyframes inside {} +@-o-keyframes inside {} @media screen { @keyframes inside {} } +@-webkit-keyframes spaces { + from { + color: black; + } + to { + color: white; + } +} +@-moz-keyframes spaces { + from { + color: black; + } + to { + color: white; + } +} +@-o-keyframes spaces { + from { + color: black; + } + to { + color: white; + } +} @keyframes spaces { from { color: black; diff --git a/crates/swc_css_prefixer/tests/fixture/transform/output.css b/crates/swc_css_prefixer/tests/fixture/transform/output.css index 9e4a3197721..d6091a34bab 100644 --- a/crates/swc_css_prefixer/tests/fixture/transform/output.css +++ b/crates/swc_css_prefixer/tests/fixture/transform/output.css @@ -34,6 +34,24 @@ em { -moz-transform: rotateZ(45deg); transform: rotateZ(45deg); } +@-webkit-keyframes anim { + from { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + } +} +@-moz-keyframes anim { + from { + -moz-transform: rotate(90deg); + transform: rotate(90deg); + } +} +@-o-keyframes anim { + from { + -o-transform: rotate(90deg); + transform: rotate(90deg); + } +} @keyframes anim { from { -webkit-transform: rotate(90deg); diff --git a/crates/swc_css_prefixer/tests/fixture/viewport/input.css b/crates/swc_css_prefixer/tests/fixture/viewport/input.css new file mode 100644 index 00000000000..9be003a7020 --- /dev/null +++ b/crates/swc_css_prefixer/tests/fixture/viewport/input.css @@ -0,0 +1,4 @@ +@viewport { + min-width: 640px; + max-width: 800px; +} diff --git a/crates/swc_css_prefixer/tests/fixture/viewport/output.css b/crates/swc_css_prefixer/tests/fixture/viewport/output.css new file mode 100644 index 00000000000..823e6e60f96 --- /dev/null +++ b/crates/swc_css_prefixer/tests/fixture/viewport/output.css @@ -0,0 +1,12 @@ +@-ms-viewport{ + min-width: 640px; + max-width: 800px; +} +@-o-viewport{ + min-width: 640px; + max-width: 800px; +} +@viewport{ + min-width: 640px; + max-width: 800px; +} diff --git a/crates/swc_css_prefixer/tests/fixture/writing-mode/output.css b/crates/swc_css_prefixer/tests/fixture/writing-mode/output.css index b126f47c80d..e2ca501b995 100644 --- a/crates/swc_css_prefixer/tests/fixture/writing-mode/output.css +++ b/crates/swc_css_prefixer/tests/fixture/writing-mode/output.css @@ -66,12 +66,40 @@ -webkit-writing-mode: sideways-lr; writing-mode: sideways-lr; } +@-ms-viewport{ + -ms-writing-mode: rl-tb; + writing-mode: horizontal-tb; + direction: rtl; +} +@-o-viewport{ + writing-mode: horizontal-tb; + direction: rtl; +} @viewport{ -webkit-writing-mode: horizontal-tb; -ms-writing-mode: rl-tb; writing-mode: horizontal-tb; direction: rtl; } +@-webkit-keyframes test { + 100% { + -webkit-writing-mode: horizontal-tb; + writing-mode: horizontal-tb; + direction: rtl; + } +} +@-moz-keyframes test { + 100% { + writing-mode: horizontal-tb; + direction: rtl; + } +} +@-o-keyframes test { + 100% { + writing-mode: horizontal-tb; + direction: rtl; + } +} @keyframes test { 100% { -webkit-writing-mode: horizontal-tb;