Skip to content

fix(emotion): avoid css label injection in keyframes#613

Merged
kdy1 merged 1 commit intomainfrom
kdy1/fix-emotion-keyframes-607
Apr 10, 2026
Merged

fix(emotion): avoid css label injection in keyframes#613
kdy1 merged 1 commit intomainfrom
kdy1/fix-emotion-keyframes-607

Conversation

@kdy1
Copy link
Copy Markdown
Member

@kdy1 kdy1 commented Apr 10, 2026

Summary

  • split Emotion runtime handling so keyframes no longer shares the css label path
  • keep css auto-label behavior unchanged, including tagged-template semicolon handling before sourcemap comments
  • add Rust fixture, wasm regressions, and a patch changeset for @swc/plugin-emotion

Testing

  • cargo test -p swc_emotion --test fixture -- --ignored
  • pnpm --filter @swc/plugin-emotion test

Closes #607.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 10, 2026

🦋 Changeset detected

Latest commit: d184998

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@swc/plugin-emotion Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@claude
Copy link
Copy Markdown

claude bot commented Apr 10, 2026

Code Review

This is a clean, well-targeted fix for issue #607. The root cause identification is correct: keyframes was mapped to ExprKind::Css, which caused create_label(true) to inject label:pulse into keyframe arguments — a label: CSS fragment that makes sense for class-based emotion styles but is invalid for animation name strings.

What's good

  • Minimal and focused: the one-line change in import_map.rs (ExprKind::CssExprKind::Keyframes) is the correct root fix.
  • match_runtime_export returning Option<ExprKind> instead of bool: removes the implicit second lookup that the old match_css_export pattern required and propagates the kind cleanly to callers. Nice improvement.
  • Test coverage: the three new wasm tests cover the important axes — with/without sourcemap and namespace imports.
  • Refactoring of wasm.test.ts: the readFixture / transformWithEmotion helpers reduce substantial repetition without over-engineering.

Minor notes

1. Redundant matches!(kind, ExprKind::Css) in create_runtime_label

fn create_runtime_label(&self, kind: ExprKind, terminate_before_sourcemap: bool) -> String {
    let mut label = self.create_label(matches!(kind, ExprKind::Css));
    if terminate_before_sourcemap
        && matches!(kind, ExprKind::Css)   // ← checked twice
        && !label.is_empty()
        && self.options.sourcemap.unwrap_or(false)
        && !label.ends_with(';')
    {
        label.push(';');
    }
    label
}

matches!(kind, ExprKind::Css) appears twice. A small refactor would make the intent clearer and avoid the double dispatch:

fn create_runtime_label(&self, kind: ExprKind, terminate_before_sourcemap: bool) -> String {
    let is_css = matches!(kind, ExprKind::Css);
    let mut label = self.create_label(is_css);
    if terminate_before_sourcemap
        && is_css
        && !label.is_empty()
        && self.options.sourcemap.unwrap_or(false)
        && !label.ends_with(';')
    {
        label.push(';');
    }
    label
}

2. terminate_before_sourcemap parameter name

The name describes the mechanism (add ; before the sourcemap comment) rather than the call site distinction. Since create_call_label_arg always passes false and create_tagged_tpl_label_arg always passes true, a name like is_tagged_tpl or add_css_semicolon might read more naturally at the call sites — though this is subjective.

3. Missing test for keyframes() call expression style

All new wasm tests exercise the tagged-template form (keyframes\...`). The call expression form (keyframes("...")) goes through a different branch in fold_expr(theExpr::Identcreate_call_label_arg` path). A test like:

// keyframes-call-input.js
import { keyframes } from "@emotion/react";
export const pulse = keyframes("0%{opacity:1;}100%{opacity:1;}");

would give confidence that the call-expression path also produces "pulse" and not "label:pulse". The Rust fixture (issues/607) covers this indirectly via the overall transform, but an explicit wasm test would round out coverage.

4. autoLabel: "dev-only" not explicitly tested for keyframes

The new tests use autoLabel: "always". The "dev-only" mode (where labels are only injected in dev env) is an important production path but isn't covered for keyframes. It's possible existing tests catch regressions here, but an explicit case would add confidence.

Summary

The fix is correct, the refactoring is clean, and the added tests cover the primary regression scenario. The notes above are all minor — none of them affect correctness. Happy to see this merged once the team confirms CI passes.

@kdy1 kdy1 merged commit 2a1be0d into main Apr 10, 2026
11 checks passed
@kdy1 kdy1 deleted the kdy1/fix-emotion-keyframes-607 branch April 10, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

@swc/plugin-emotion 14.8.0: keyframes label injection causes css-animation- class name prefix

1 participant