Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(css/compat): media ranges compat #6631

Merged
merged 15 commits into from Dec 13, 2022
22 changes: 22 additions & 0 deletions crates/swc_atoms/words.txt
Expand Up @@ -799,6 +799,7 @@ codebase
col
colgroup
color
color-index
color-mix
color-profile
color-scheme
Expand Down Expand Up @@ -853,7 +854,10 @@ del
delete
desc
details
device-aspect-ratio
device-cmyk
device-height
device-width
dfn
dialog
diffuseConstant
Expand Down Expand Up @@ -1237,10 +1241,18 @@ math-shift
math-style
matrix
matrix3d
max-aspect-ratio
max-block-size
max-color
max-color-index
max-device-aspect-ratio
max-device-height
max-device-width
max-height
max-inline-size
max-lines
max-monochrome
max-resolution
max-width
maxlength
media
Expand All @@ -1251,9 +1263,17 @@ metadata
meter
mglyph
mi
min-aspect-ratio
min-block-size
min-color
min-color-index
min-device-aspect-ratio
min-device-height
min-device-width
min-height
min-inline-size
min-monochrome
min-resolution
min-width
missing-glyph
mix-blend-mode
Expand All @@ -1262,6 +1282,7 @@ mm
mn
mo
module
monochrome
mozmm
mpath
ms
Expand Down Expand Up @@ -1526,6 +1547,7 @@ requiredFeatures
requiredextensions
requiredfeatures
resize
resolution
return
revert
revert-layer
Expand Down
237 changes: 237 additions & 0 deletions crates/swc_css_compat/src/compiler/media_query_ranges.rs
@@ -0,0 +1,237 @@
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_css_ast::{
Dimension, Ident, MediaFeature, MediaFeatureName, MediaFeaturePlain, MediaFeatureRange,
MediaFeatureRangeComparison, MediaFeatureRangeInterval, MediaFeatureValue,
};

use crate::compiler::Compiler;

impl Compiler {
pub(crate) fn get_legacy_media_feature(
&mut self,
n: &mut MediaFeature,
) -> Option<(MediaFeature, Option<MediaFeature>)> {
match n {
MediaFeature::Range(MediaFeatureRange {
span,
left: box left,
comparison,
right: box right,
..
}) => {
if let MediaFeatureValue::Ident(name) = &left {
let name = match comparison {
MediaFeatureRangeComparison::Lt | MediaFeatureRangeComparison::Le => {
self.get_right_media_feature_name(name)
}
MediaFeatureRangeComparison::Eq => {
Some(MediaFeatureName::Ident(name.clone()))
}
_ => self.get_left_media_feature_name(name),
}?;

let original_value = right.clone();
let value = match comparison {
MediaFeatureRangeComparison::Lt => self.get_lt_value(original_value),
MediaFeatureRangeComparison::Gt => self.get_gt_value(original_value),
_ => Some(original_value),
}?;

return Some((
MediaFeature::Plain(MediaFeaturePlain {
span: *span,
name,
value: Box::new(value),
}),
None,
));
} else if let MediaFeatureValue::Ident(name) = &right {
let name = match comparison {
MediaFeatureRangeComparison::Lt | MediaFeatureRangeComparison::Le => {
self.get_left_media_feature_name(name)
}
MediaFeatureRangeComparison::Eq => {
Some(MediaFeatureName::Ident(name.clone()))
}
_ => self.get_right_media_feature_name(name),
}?;

let original_value = left.clone();
let value = match comparison {
MediaFeatureRangeComparison::Lt => self.get_gt_value(original_value),
MediaFeatureRangeComparison::Gt => self.get_lt_value(original_value),
_ => Some(original_value),
}?;

return Some((
MediaFeature::Plain(MediaFeaturePlain {
span: *span,
name,
value: Box::new(value),
}),
None,
));
}
}
MediaFeature::RangeInterval(MediaFeatureRangeInterval {
span,
left: box left,
left_comparison,
name: MediaFeatureName::Ident(name),
right: box right,
right_comparison,
..
}) => {
let left_name = match left_comparison {
MediaFeatureRangeComparison::Gt | MediaFeatureRangeComparison::Ge => {
self.get_right_media_feature_name(name)
}
_ => self.get_left_media_feature_name(name),
}?;

let left_value = match left_comparison {
MediaFeatureRangeComparison::Lt => self.get_gt_value(left.clone()),
MediaFeatureRangeComparison::Gt => self.get_lt_value(left.clone()),
_ => Some(left.clone()),
}?;

let left = MediaFeature::Plain(MediaFeaturePlain {
span: *span,
name: left_name,
value: Box::new(left_value),
});

let right_name = match right_comparison {
MediaFeatureRangeComparison::Gt | MediaFeatureRangeComparison::Ge => {
self.get_left_media_feature_name(name)
}
_ => self.get_right_media_feature_name(name),
}?;

let right_value = match right_comparison {
MediaFeatureRangeComparison::Lt => self.get_lt_value(right.clone()),
MediaFeatureRangeComparison::Gt => self.get_gt_value(right.clone()),
_ => Some(right.clone()),
}?;

let right = MediaFeature::Plain(MediaFeaturePlain {
span: *span,
name: right_name,
value: Box::new(right_value),
});

return Some((left, Some(right)));
}
_ => {}
}

None
}

fn get_left_media_feature_name(&self, name: &Ident) -> Option<MediaFeatureName> {
let value = match name.value {
js_word!("width") => js_word!("min-width"),
js_word!("height") => js_word!("min-height"),
js_word!("device-width") => js_word!("min-device-width"),
js_word!("device-height") => js_word!("min-device-height"),
js_word!("aspect-ratio") => js_word!("min-aspect-ratio"),
js_word!("device-aspect-ratio") => js_word!("min-device-aspect-ratio"),
js_word!("color") => js_word!("min-color"),
js_word!("color-index") => js_word!("min-color-index"),
js_word!("monochrome") => js_word!("min-monochrome"),
js_word!("resolution") => js_word!("min-resolution"),
_ => return None,
};

Some(MediaFeatureName::Ident(Ident {
span: DUMMY_SP,
value,
raw: None,
}))
}

fn get_right_media_feature_name(&self, name: &Ident) -> Option<MediaFeatureName> {
let value = match name.value {
js_word!("width") => js_word!("max-width"),
js_word!("height") => js_word!("max-height"),
js_word!("device-width") => js_word!("max-device-width"),
js_word!("device-height") => js_word!("max-device-height"),
js_word!("aspect-ratio") => js_word!("max-aspect-ratio"),
js_word!("device-aspect-ratio") => js_word!("max-device-aspect-ratio"),
js_word!("color") => js_word!("max-color"),
js_word!("color-index") => js_word!("max-color-index"),
js_word!("monochrome") => js_word!("max-monochrome"),
js_word!("resolution") => js_word!("max-resolution"),
_ => return None,
};

Some(MediaFeatureName::Ident(Ident {
span: DUMMY_SP,
value,
raw: None,
}))
}

fn get_lt_value(&self, mut value: MediaFeatureValue) -> Option<MediaFeatureValue> {
match &mut value {
MediaFeatureValue::Number(number) => {
number.value -= 1.0;
number.raw = None;

Some(value)
}
MediaFeatureValue::Dimension(dimension) => {
match dimension {
Dimension::Length(length) => {
length.value.value -= 0.001;
length.value.raw = None;
}
_ => {
return None;
}
}

Some(value)
}
MediaFeatureValue::Ratio(ration) => {
ration.left.value -= 0.001;
ration.left.raw = None;

Some(value)
}
_ => None,
}
}

fn get_gt_value(&self, mut value: MediaFeatureValue) -> Option<MediaFeatureValue> {
match &mut value {
MediaFeatureValue::Number(number) => {
number.value += 1.0;
number.raw = None;

Some(value)
}
MediaFeatureValue::Dimension(dimension) => {
match dimension {
Dimension::Length(length) => {
length.value.value += 0.001;
length.value.raw = None;
}
_ => {
return None;
}
}

Some(value)
}
MediaFeatureValue::Ratio(ration) => {
ration.left.value += 0.001;
ration.left.raw = None;

Some(value)
}
_ => None,
}
}
}
38 changes: 37 additions & 1 deletion crates/swc_css_compat/src/compiler/mod.rs
@@ -1,10 +1,15 @@
use swc_css_ast::{AtRule, MediaCondition, MediaConditionWithoutOr, MediaQuery, Rule};
use swc_common::{Spanned, DUMMY_SP};
use swc_css_ast::{
AtRule, MediaAnd, MediaCondition, MediaConditionAllType, MediaConditionWithoutOr,
MediaInParens, MediaQuery, Rule,
};
use swc_css_visit::{VisitMut, VisitMutWith};

use self::custom_media::CustomMediaHandler;
use crate::feature::Features;

mod custom_media;
mod media_query_ranges;

/// Compiles a modern CSS file to a CSS file which works with old browsers.
#[derive(Debug)]
Expand Down Expand Up @@ -69,4 +74,35 @@ impl VisitMut for Compiler {
self.custom_media.process_rules(n);
}
}

fn visit_mut_media_in_parens(&mut self, n: &mut MediaInParens) {
n.visit_mut_children_with(self);

if self.c.process.contains(Features::MEDIA_QUERY_RANGES) {
if let MediaInParens::Feature(media_feature) = n {
if let Some(legacy_media_feature) = self.get_legacy_media_feature(media_feature) {
match legacy_media_feature {
(legacy_media_feature, None) => {
*media_feature = Box::new(legacy_media_feature);
}
(left, Some(right)) => {
*n = MediaInParens::MediaCondition(MediaCondition {
span: n.span(),
conditions: vec![
MediaConditionAllType::MediaInParens(*Box::new(
MediaInParens::Feature(Box::new(left)),
)),
MediaConditionAllType::And(MediaAnd {
span: DUMMY_SP,
keyword: None,
condition: MediaInParens::Feature(Box::new(right)),
}),
],
});
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions crates/swc_css_compat/src/feature.rs
Expand Up @@ -4,5 +4,6 @@ bitflags! {
pub struct Features: u64 {
const NESTING = 1 << 0;
const CUSTOM_MEDIA = 1 << 1;
const MEDIA_QUERY_RANGES = 1 << 2;
}
}
22 changes: 22 additions & 0 deletions crates/swc_css_compat/tests/fixture.rs
Expand Up @@ -103,3 +103,25 @@ fn test_custom_media_query(input: PathBuf) {
})
.unwrap();
}

#[testing::fixture("tests/media-query-ranges/**/*.css", exclude("expect.css"))]
fn test_media_query_ranges(input: PathBuf) {
let output = input.with_extension("expect.css");

testing::run_test(false, |cm, _| {
//
let fm = cm.load_file(&input).unwrap();
let mut ss = parse_stylesheet(&fm);

ss.visit_mut_with(&mut Compiler::new(Config {
process: Features::MEDIA_QUERY_RANGES,
}));

let s = print_stylesheet(&ss);

NormalizedOutput::from(s).compare_to_file(&output).unwrap();

Ok(())
})
.unwrap();
}