Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 113 additions & 2 deletions packages/@stylexjs/babel-plugin/__tests__/transform-process-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,14 @@ describe('@stylexjs/babel-plugin', () => {
.margin-xymmreb:not(#\\#){margin:10px 20px}
.padding-xss17vw:not(#\\#){padding:var(--large-x1ec7iuc)}
.borderColor-x1bg2uv5:not(#\\#):not(#\\#){border-color:green}
@media (max-width: 1000px){.borderColor-x5ugf7c.borderColor-x5ugf7c:not(#\\#):not(#\\#){border-color:var(--blue-xpqh4lw)}}
@media (max-width: 500px){@media (max-width: 1000px){.borderColor-xqiy1ys.borderColor-xqiy1ys.borderColor-xqiy1ys:not(#\\#):not(#\\#){border-color:yellow}}}
.animationName-xckgs0v:not(#\\#):not(#\\#):not(#\\#){animation-name:xi07kvp-B}
.backgroundColor-xrkmrrc:not(#\\#):not(#\\#):not(#\\#){background-color:red}
.color-x14rh7hd:not(#\\#):not(#\\#):not(#\\#){color:var(--x-color)}
html:not([dir='rtl']) .float-x1kmio9f:not(#\\#):not(#\\#):not(#\\#){float:left}
html[dir='rtl'] .float-x1kmio9f:not(#\\#):not(#\\#):not(#\\#){float:right}
.textShadow-x1skrh0i:not(#\\#):not(#\\#):not(#\\#){text-shadow:1px 2px 3px 4px red}
@media (max-width: 1000px){.borderColor-x5ugf7c.borderColor-x5ugf7c:not(#\\#):not(#\\#){border-color:var(--blue-xpqh4lw)}}
@media (max-width: 500px){@media (max-width: 1000px){.borderColor-xqiy1ys.borderColor-xqiy1ys.borderColor-xqiy1ys:not(#\\#):not(#\\#){border-color:yellow}}}
@media (min-width:320px){.textShadow-x1cmij7u.textShadow-x1cmij7u:not(#\\#):not(#\\#):not(#\\#){text-shadow:10px 20px 30px 40px green}}"
`);
});
Expand Down Expand Up @@ -425,5 +425,116 @@ describe('@stylexjs/babel-plugin', () => {
.x57uvma.x57uvma, .x57uvma.x57uvma:root{--large-x1ec7iuc:20px;--medium-xypjos2:10px;--small-x19twipt:5px;}"
`);
});

test('media query grouping - rules with same media query are grouped together', () => {
const { _code, metadata } = transform(
`
import * as stylex from '@stylexjs/stylex';
export const styles = stylex.create({
container: {
'@media (max-width: 768px)': {
width: '10px',
height: '20px',
},
color: {
default: 'black',
'@media (max-width: 308px)': 'white',
'@media (max-width: 768px)': 'red',
},
backgroundColor: {
default: 'white',
'@media (max-width: 768px)': 'blue',
'@media (min-width: 1024px)': 'yellow',
},
fontSize: {
default: '16px',
'@media (max-width: 768px)': '14px',
},
padding: {
default: '10px',
'@media (min-width: 1024px)': '20px',
},
margin: {
default: '5px',
'@media (min-width: 1024px)': '10px',
}
}
});
`,
);

const css = stylexPlugin.processStylexRules(metadata);

expect(css).toMatchInlineSnapshot(`
":root, .xsg933n{--blue-xpqh4lw:blue;}
:root, .xbiwvf9{--small-x19twipt:2px;--medium-xypjos2:4px;--large-x1ec7iuc:8px;}
.margin-x16zck5j:not(#\\#){margin:5px}
.padding-x7z7khe:not(#\\#){padding:10px}
.backgroundColor-x12peec7:not(#\\#):not(#\\#){background-color:white}
.color-x1mqxbix:not(#\\#):not(#\\#){color:black}
.fontSize-x1j61zf2:not(#\\#):not(#\\#){font-size:16px}
@media (min-width: 1024px){
.margin-x1nff4mz.margin-x1nff4mz:not(#\\#){margin:10px}
.padding-x1glw0n9.padding-x1glw0n9:not(#\\#){padding:20px}
.backgroundColor-xkbfoqe.backgroundColor-xkbfoqe:not(#\\#):not(#\\#){background-color:yellow}
}
@media (max-width: 768px){
.backgroundColor-xycim1f.backgroundColor-xycim1f:not(#\\#):not(#\\#){background-color:blue}
.color-x9i7o1z.color-x9i7o1z:not(#\\#):not(#\\#){color:red}
.fontSize-xt5ov9y.fontSize-xt5ov9y:not(#\\#):not(#\\#){font-size:14px}
.height-x12z8348.height-x12z8348:not(#\\#):not(#\\#):not(#\\#){height:20px}
.width-x7lwmry.width-x7lwmry:not(#\\#):not(#\\#):not(#\\#){width:10px}
}
@media (max-width: 308px){.color-x1760m8v.color-x1760m8v:not(#\\#):not(#\\#){color:white}}"
`);
});

test('media query grouping with layers - rules with same media query are grouped together', () => {
const { _code, metadata } = transform(
`
import * as stylex from '@stylexjs/stylex';
export const styles = stylex.create({
container: {
color: {
default: 'black',
'@media (max-width: 768px)': 'red',
},
backgroundColor: {
default: 'white',
'@media (max-width: 768px)': 'blue',
},
fontSize: {
default: '16px',
'@media (max-width: 768px)': '14px',
}
}
});
`,
{
useLayers: true,
},
);

const css = stylexPlugin.processStylexRules(metadata, true);

expect(css).toMatchInlineSnapshot(`
"
@layer priority1, priority2;
@layer priority1{
:root, .xsg933n{--blue-xpqh4lw:blue;}
:root, .xbiwvf9{--small-x19twipt:2px;--medium-xypjos2:4px;--large-x1ec7iuc:8px;}
}
@layer priority2{
.backgroundColor-x12peec7{background-color:white}
.color-x1mqxbix{color:black}
.fontSize-x1j61zf2{font-size:16px}
@media (max-width: 768px){
.backgroundColor-xycim1f.backgroundColor-xycim1f{background-color:blue}
.color-x9i7o1z.color-x9i7o1z{color:red}
.fontSize-xt5ov9y.fontSize-xt5ov9y{font-size:14px}
}
}"
`);
});
});
});
125 changes: 83 additions & 42 deletions packages/@stylexjs/babel-plugin/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,58 +470,99 @@ function processStylexRules(
';\n'
: '';

const collectedCSS = grouped
const globalMediaQueryGroups: Map<string, Array<string>> = new Map();
const globalNonMediaRules: Array<string> = [];

const perGroupOutput = grouped
.map((group, index) => {
const pri = group[0][2];
const collectedCSS = Array.from(
new Map(group.map(([a, b]) => [a, b])).values(),
)
.flatMap((rule) => {
const { ltr, rtl } = rule;
let ltrRule = ltr,
rtlRule = rtl;

if (!useLayers) {
ltrRule = addSpecificityLevel(ltrRule, index);
rtlRule = rtlRule && addSpecificityLevel(rtlRule, index);
}
const rules = Array.from(new Map(group.map(([a, b]) => [a, b])).values());

const mediaQueryGroups = useLayers ? new Map() : globalMediaQueryGroups;
const nonMediaRules = useLayers ? [] : globalNonMediaRules;

rules.forEach((rule) => {
const { ltr, rtl } = rule;
let ltrRule = ltr,
rtlRule = rtl;

// check if the selector looks like .xtrlmmh, .xtrlmmh:root
// if so, turn it into .xtrlmmh.xtrlmmh, .xtrlmmh.xtrlmmh:root
// This is to ensure the themes always have precedence over the
// default variable values
ltrRule = ltrRule.replace(
if (!useLayers) {
ltrRule = addSpecificityLevel(ltrRule, index);
rtlRule = rtlRule && addSpecificityLevel(rtlRule, index);
}

ltrRule = ltrRule.replace(
/\.([a-zA-Z0-9]+), \.([a-zA-Z0-9]+):root/g,
'.$1.$1, .$1.$1:root',
);
if (rtlRule) {
rtlRule = rtlRule.replace(
/\.([a-zA-Z0-9]+), \.([a-zA-Z0-9]+):root/g,
'.$1.$1, .$1.$1:root',
);
if (rtlRule) {
rtlRule = rtlRule.replace(
/\.([a-zA-Z0-9]+), \.([a-zA-Z0-9]+):root/g,
'.$1.$1, .$1.$1:root',
);
}
}

return rtlRule
? enableLTRRTLComments
? [
`/* @ltr begin */${ltrRule}/* @ltr end */`,
`/* @rtl begin */${rtlRule}/* @rtl end */`,
]
: [
addAncestorSelector(ltrRule, "html:not([dir='rtl'])"),
addAncestorSelector(rtlRule, "html[dir='rtl']"),
]
: [ltrRule];
})
.join('\n');

// Don't put @property, @keyframe, @position-try in layers
return useLayers && pri > 0
? `@layer priority${index + 1}{\n${collectedCSS}\n}`
: collectedCSS;
const processedRules = rtlRule
? enableLTRRTLComments
? [
`/* @ltr begin */${ltrRule}/* @ltr end */`,
`/* @rtl begin */${rtlRule}/* @rtl end */`,
]
: [
addAncestorSelector(ltrRule, "html:not([dir='rtl'])"),
addAncestorSelector(rtlRule, "html[dir='rtl']"),
]
: [ltrRule];

processedRules.forEach((processedRule) => {
const mediaQueryMatch = processedRule.match(
/^(@media[^{]+)\{([\s\S]*)\}$/m,
);
if (mediaQueryMatch) {
const [, rawMQ, innerContent] = mediaQueryMatch;
const mq = rawMQ.trim().replace(/\s+/g, ' ');
if (!mediaQueryGroups.has(mq)) mediaQueryGroups.set(mq, []);
const arr = mediaQueryGroups.get(mq);
if (arr) arr.push(innerContent.trim());
} else {
nonMediaRules.push(processedRule);
}
});
});

if (useLayers) {
const allRules = [
...nonMediaRules,
...Array.from(mediaQueryGroups.entries())
.filter(([, inner]) => inner.length > 0)
.map(([mq, inner]) =>
inner.length === 1
? `${mq}{${inner[0]}}`
: `${mq}{\n${inner.join('\n')}\n}`,
),
];
const collected = allRules.join('\n');
return pri > 0
? `@layer priority${index + 1}{\n${collected}\n}`
: collected;
}
return '';
})
.join('\n');

const collectedCSS = useLayers
? perGroupOutput
: [
...globalNonMediaRules,
...Array.from(globalMediaQueryGroups.entries())
.filter(([, inner]) => inner.length > 0)
.map(([mq, inner]) =>
inner.length === 1
? `${mq}{${inner[0]}}`
: `${mq}{\n${inner.join('\n')}\n}`,
),
].join('\n');

return header + collectedCSS;
}

Expand Down