Skip to content

css: scope animation-name to its @keyframes hash in CSS modules (#18921) - #33322

Merged
alii merged 5 commits into
oven-sh:mainfrom
brunorodmoreira:claude/css-module-animation-scope-18921
Jul 4, 2026
Merged

alii merged 5 commits into
oven-sh:mainfrom
brunorodmoreira:claude/css-module-animation-scope-18921

Conversation

@brunorodmoreira

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #18921.

In CSS modules, the animation shorthand and animation-name longhand did not scope their referenced @keyframes name to the hashed name the @keyframes rule receives. The keyframes definition was scoped but the reference was not, so the two diverged and the animation silently broke:

/* before */
.playAnim_HASH { animation: anim forwards ease-out .25s; }  /* "anim" not scoped */
@keyframes anim_HASH { /* ... */ }

/* after (matches Lightning CSS / esbuild) */
.playAnim_HASH { animation: .25s ease-out forwards anim_HASH; }
@keyframes anim_HASH { /* ... */ }

Root cause: animation/animation-name were never wired into Bun's (hand-maintained) structured CSS property table, so they fell back to UnparsedProperty (raw tokens) and their names were emitted verbatim — the CSS-modules scoping that lives in AnimationName::to_css was never reached.

Fix:

  • Wire animation (shorthand) and animation-name (longhand), including the vendor-prefixed forms, as structured properties, mirroring the existing transition property. Their names now flow through the scoping-aware AnimationName::to_css and are scoped to match the @keyframes rule.
  • Fix Animation::to_css so a name-less shorthand (e.g. animation: 3s linear 1s infinite alternate) keeps its components instead of collapsing to none. Bun prints the Animation struct directly and, unlike Lightning CSS, has no declaration handler to keep it off that lossy path.

As a side effect, animation/animation-name are now serialized in canonical form (name last, redundant trailing none dropped, numbers normalized) — consistent with how Bun already handles transition, and with Lightning CSS and esbuild. All changes are lossless.

How did you verify your code works?

New tests (each fails on stable Bun, passes with this change):

  • test/bundler/css/css-modules.test.ts — asserts the animation shorthand and animation-name longhand reference the same hashed name as their @keyframes rule (the exact repro from Inconsistent hashing behavior with CSS animation names and keyframes in CSS modules #18921), and that the unscoped names do not survive.
  • test/js/bun/css/css.test.ts — an animation serialization matrix: canonical ordering, no data loss for name-less shorthands, none, vendor prefix, multiple comma-separated animations, and the animation-name longhand.

All existing CSS suites pass with the change:

  • test/js/bun/css/css.test.ts
  • test/bundler/css/css-modules.test.ts
  • test/bundler/css/view-transition-23600.test.ts
  • test/bundler/esbuild/css.test.ts

🤖 Generated with Claude Code

Fixes oven-sh#18921.

`animation` and `animation-name` were never wired into the hand-maintained
structured CSS property table, so they fell back to UnparsedProperty and their
referenced `@keyframes` name was emitted verbatim — the CSS-modules scoping in
`AnimationName::to_css` was never reached. The `@keyframes` name was scoped but
the reference was not, so the animation silently broke.

Wire `animation` (shorthand) and `animation-name` (longhand), including the
vendor-prefixed forms, as structured properties mirroring the existing
`transition` property. Their names now flow through `AnimationName::to_css` and
are scoped to match the `@keyframes` rule.

Also fix `Animation::to_css` so a name-less shorthand (e.g.
`animation: 3s linear 1s infinite alternate`) keeps its components instead of
collapsing to `none`: Bun prints the `Animation` struct directly and, unlike
lightningcss, has no declaration handler to keep it off that lossy path.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88d5eeda-00c6-4f1e-875a-64643f60501c

📥 Commits

Reviewing files that changed from the base of the PR and between a50a8d0 and e04f9cf.

📒 Files selected for processing (2)
  • src/css/properties/animation.rs
  • src/css/properties/properties_generated.rs

Walkthrough

Adds support for the animation and animation-name CSS properties end to end: schema registration, animation serialization and cloning helpers, generic dispatch bridging, property parsing/serialization integration, and tests for keyframe scoping and minification.

Changes

Animation property support

Layer / File(s) Summary
PropertyIdTag and PropertyId schema additions
src/css/properties/properties_generated.rs
Adds Animation and AnimationName discriminants, vendor-prefix handling, CSS name mapping, lookup dispatch, and Property variants.
Animation type eql, deep_clone, and shorthand serialization
src/css/properties/animation.rs
Adds Animation and AnimationTimeline deep-clone support, refactors shorthand to_css spacing and emission rules, expands AnimationIterationCount, and derives CssEql for Animation.
Trait bridging and generic dispatch registration
src/css/generics.rs, src/css/properties/mod.rs
Registers Animation for DeepClone/CssEql bridging and forwards Animation/AnimationName through the inherent parse/to-css shim.
Property enum parse, serialize, deep_clone, and eql integration
src/css/properties/properties_generated.rs
Extends Property dispatch, parsing, cloning, equality, and CSS serialization for the new animation variants.
Bundler and minifier test coverage
test/bundler/css/css-modules.test.ts, test/js/bun/css/css.test.ts
Adds a CSS-modules keyframe scoping test and minifier assertions for animation and animation-name.

Related Issues: #18921
Suggested labels: css, tests
Suggested reviewers: paperclover, Jarred-Sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: CSS Modules now scope animation-name keyframe references by hash.
Description check ✅ Passed The PR description matches the template and includes the change summary plus verification details.
Linked Issues check ✅ Passed The changes satisfy #18921 by wiring animation and animation-name through structured CSS handling and scoping keyframe names consistently.
Out of Scope Changes check ✅ Passed The added serialization, deep-clone, and test updates are all directly related to the animation CSS Modules fix.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@alii

alii commented Jul 4, 2026

Copy link
Copy Markdown
Member

Thank you, will pick this up shortly.

Comment thread src/css/properties/animation.rs Outdated
Comment thread src/css/properties/animation.rs
Comment thread test/bundler/css/css-modules.test.ts
alii added 2 commits July 4, 2026 10:17
…eywords

Pins code paths that the structured animation property wiring makes
reachable for the first time: the timeline component in the shorthand,
the zero-duration-nonzero-delay guard (mirrors the transition test),
the quoted-string animation-name form in CSS modules, and CSS-wide
keywords surviving the structured parse.
Bridges the five field types that only had PartialEq (the four
component enums plus AnimationTimeline) so the existing
#[derive(CssEql)] proc-macro applies, matching the Font struct
precedent. Removes the need to keep the eql body in sync when
fields are added.
alii
alii previously approved these changes Jul 4, 2026
alii
alii previously approved these changes Jul 4, 2026
- redundant_closure: pass EasingFunction::is_ident directly to is_some_and
- trivially_copy_pass_by_ref: AnimationIterationCount is now Copy, take
  self by value in to_css
@alii
alii enabled auto-merge (squash) July 4, 2026 19:09
@alii
alii merged commit 08c5bc9 into oven-sh:main Jul 4, 2026
76 checks passed
alii added a commit that referenced this pull request Jul 5, 2026
…base

The variants were deleted earlier on this branch; #33322 (merged to main)
added deep_clone with an unreachable arm for them. Rebase merged both
without a textual conflict, leaving a reference to non-existent variants.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent hashing behavior with CSS animation names and keyframes in CSS modules

2 participants