Browse Source

fix(parser): don't drop @container/@layer at-rules on CSS parse (#5969) (#6788)

fix(parser): don't drop @container/@layer at-rules on CSS parse

The browser CSS parser only recognised nestable at-rules (@container,
@layer, ...) when the CSSOM reported a falsy rule `type`. That happened
to work in Chrome, where the legacy `CSSRule.type` is `0` for newer
at-rules, but engines that expose a real numeric type (jsdom reports
`17` for `CSSContainerRule`) fell through every branch and the whole
rule — selectors, styles and condition — was silently discarded.

Detect nestable at-rules via `getNestableAtRule` regardless of the
numeric `type`. `getNestableAtRule` already matches only when the rule's
`cssText` starts with the exact `@<name>`, so normal style/keyframe
rules are unaffected. The parsed condition keeps the container name and
its query separated by a space, so the exported CSS is valid again
(e.g. `@container somename (min-width: 300px){...}`).

Fixes #5969

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
release-v0.23.3
TowyTowy 2 months ago
committed by GitHub
parent
commit
772b9701bc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      packages/core/src/parser/model/BrowserParserCss.ts
  2. 10
      packages/core/test/specs/css_composer/model/CssModels.ts
  3. 22
      packages/core/test/specs/parser/model/ParserCss.ts

2
packages/core/src/parser/model/BrowserParserCss.ts

@ -215,7 +215,7 @@ export const parseNode = (el: CSSStyleSheet | CSSRule) => {
singleAtRule = true;
atRuleType = AT_RULE_NAMES[type];
condition = parseCondition(node);
} else if (AT_RULE_KEYS.indexOf(`${type}`) >= 0 || (!type && getNestableAtRule(node))) {
} else if (AT_RULE_KEYS.indexOf(`${type}`) >= 0 || getNestableAtRule(node)) {
const subRules = parseNode(node);
const subAtRuleType = AT_RULE_NAMES[type] || getNestableAtRule(node);
condition = parseCondition(node);

10
packages/core/test/specs/css_composer/model/CssModels.ts

@ -97,6 +97,16 @@ describe('CssRule', () => {
expect(obj.toCSS()).toEqual(`@media ${media}{.test1{color:red;}}`);
});
test('toCSS keeps the space in a named container rule', () => {
const containerText = 'somename (min-width: 300px)';
obj.set('atRuleType', 'container');
obj.set('mediaText', containerText);
obj.getSelectors().add({ name: 'test1' });
obj.setStyle({ height: '100px' });
expect(obj.getAtRule()).toEqual(`@container ${containerText}`);
expect(obj.toCSS()).toEqual(`@container ${containerText}{.test1{height:100px;}}`);
});
test('toCSS with a generic at-rule', () => {
obj.set('atRuleType', 'supports');
obj.getSelectors().add({ name: 'test1' });

22
packages/core/test/specs/parser/model/ParserCss.ts

@ -375,8 +375,7 @@ describe('ParserCss', () => {
expect(obj.parse(str)).toEqual([result]);
});
// Unsupported by CSSOM parser
test.skip('Parse rule with @container at-rule', () => {
test('Parse rule with @container at-rule', () => {
const atRuleType = 'container';
const atRuleText = 'somename (min-width: 300px)';
const input = `@${atRuleType} ${atRuleText} {
@ -386,14 +385,12 @@ describe('ParserCss', () => {
const output = [
{
selectors: ['cls1'],
selectorsAdd: '',
atRuleType,
mediaText: atRuleText,
style: { height: '100px' },
},
{
selectors: ['cls2'],
selectorsAdd: '',
atRuleType,
mediaText: atRuleText,
style: { height: '200px' },
@ -402,6 +399,23 @@ describe('ParserCss', () => {
expect(obj.parse(input)).toEqual(output);
});
test('Parse rule with anonymous @container at-rule', () => {
const atRuleType = 'container';
const atRuleText = '(min-width: 300px)';
const input = `@${atRuleType} ${atRuleText} {
.cls1{ height: 100px }
}`;
const output = [
{
selectors: ['cls1'],
atRuleType,
mediaText: atRuleText,
style: { height: '100px' },
},
];
expect(obj.parse(input)).toEqual(output);
});
test('Parses multiple font-face at-rules', () => {
const str = `
@font-face {

Loading…
Cancel
Save