fix(compiler): scope animations declared in minified nested rules

The regular expressions in `_scopeAnimationRule` expect an `animation` or `animation-name` property to be preceded by whitespace or a semicolon, and its value to end at a semicolon. Minified CSS breaks both assumptions. Inside an at-rule the property follows a `{`, and the last declaration of a block has no trailing semicolon, so the closing `}` lands inside the captured value. The keyframe name is then left unscoped while the `@keyframes` rule itself is renamed, so the animation does nothing in a production build.

Accept `{` as a leading boundary and stop the value at `}`. The prefix is written back unchanged, and a declaration value cannot contain an unescaped `}`.

Fixes #70316
This commit is contained in:
Lazizbek Ergashev
2026-08-20 22:12:03 +05:00
committed by Leon Senft
parent 1e35de536d
commit 58b0cb4735
2 changed files with 13 additions and 2 deletions
+2 -2
View File
@@ -362,7 +362,7 @@ export class ShadowCss {
unscopedKeyframesSet: ReadonlySet<string>,
): CssRule {
let content = rule.content.replace(
/((?:^|\s+|;)(?:-webkit-)?animation\s*:\s*)([^;]+)/g,
/((?:^|[\s;{])(?:-webkit-)?animation\s*:\s*)([^;}]+)/g,
(_, start, animationDeclarations) =>
start +
animationDeclarations.replace(
@@ -393,7 +393,7 @@ export class ShadowCss {
),
);
content = content.replace(
/((?:^|\s+|;)(?:-webkit-)?animation-name(?:\s*):(?:\s*))([^;]+)/g,
/((?:^|[\s;{])(?:-webkit-)?animation-name(?:\s*):(?:\s*))([^;}]+)/g,
(_match, start, commaSeparatedKeyframes) =>
`${start}${commaSeparatedKeyframes
.split(',')
@@ -259,6 +259,17 @@ describe('ShadowCss, keyframes and animations', () => {
expect(shim(css, 'host-a')).toEqual(expected);
});
it('should correctly process animations defined in minified rules nested in an at-rule', () => {
let css = '@keyframes foo {}@media screen{.test{animation:foo 1s forwards}}';
let expected =
'@keyframes host-a_foo {}@media screen{.test[host-a]{animation:host-a_foo 1s forwards}}';
expect(shim(css, 'host-a')).toEqual(expected);
css = '@keyframes foo {}@supports (display:grid){.test{animation-name:foo}}';
expected =
'@keyframes host-a_foo {}@supports (display:grid){.test[host-a]{animation-name:host-a_foo}}';
expect(shim(css, 'host-a')).toEqual(expected);
});
it('should ignore keywords values when scoping local animations', () => {
const css = `
div {