Skip to content

feat(emotion): add sourcemap and auto-label for css prop with object styles#599

Merged
kdy1 merged 1 commit into
mainfrom
claude/issue-160-20260321-0122
Mar 21, 2026
Merged

feat(emotion): add sourcemap and auto-label for css prop with object styles#599
kdy1 merged 1 commit into
mainfrom
claude/issue-160-20260321-0122

Conversation

@kdy1
Copy link
Copy Markdown
Member

@kdy1 kdy1 commented Mar 21, 2026

When using the Emotion css prop with object styles (e.g. css={{ color: 'red' }}), the plugin now wraps the expression in a css() call and adds sourceMappingURL and auto-label arguments, matching the behavior of the css template literal syntax.

Fixes #160

Generated with Claude Code

…styles

When using the Emotion css prop with object styles (e.g. `css={{ color: 'red' }}`),
the plugin now wraps the expression in a `css()` call and adds sourceMappingURL
and auto-label arguments, matching the behavior of the css template literal syntax.

The transformation is applied after `fold_children_with` to avoid double-processing
by `fold_call_expr`. The label context is captured before folding to preserve the
correct class/function name in auto-label output.

Fixes #160

Co-authored-by: Donny/강동윤 <kdy1@users.noreply.github.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 21, 2026

⚠️ No Changeset found

Latest commit: 1770290

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@claude
Copy link
Copy Markdown

claude Bot commented Mar 21, 2026

PR Review: feat(emotion): add sourcemap and auto-label for css prop with object styles

This is a well-targeted fix for issue #160. The approach of deferring the rewrite to after fold_children_with to avoid double-processing through fold_call_expr is correct and clearly explained.

Issues

1. Unconditional wrapping when no label/sourcemap will be added

The rewrite_css_prop_attr function always wraps the expression in a css() call, even when both auto_label and sourcemap are disabled. In that case args ends up with only the original expression as a single argument, producing css={css({ color: 'red' })} with no benefit. The wrapping (and the add_pure_comment) should be conditional on at least one extra arg being added:

// Build args, then only rewrite if something was added
let mut args = vec![raw_expr.as_arg()];
// ... push label/sourcemap ...
if args.len() == 1 {
    // Nothing to add — restore the expression and skip
    *expr = args.remove(0).expr;
    continue;
}

2. Missing changeset

The changeset-bot already flagged this — a .changeset/xxx.md bumping @swc/plugin-emotion is needed for the package to be released with this fix.

Observations / Minor Notes

3. expr_pos points to the container {, not the object literal

expr_pos = container_span.lo is the position of the JSXExprContainer's opening { (i.e. the brace in css={{ ... }}), not the inner object literal. For consistency with how fold_call_expr handles css() calls (using expr.span.lo()), it would be slightly more accurate to capture the object expression's span before calling .take(). This is a minor inaccuracy but could affect sourcemap precision.

4. std::mem::replace save/restore pattern

The save-and-restore of current_context via std::mem::replace works correctly, but the intent is just to call create_label with a specific context. Since create_label only reads self.current_context, a simpler alternative is to set it, call the method, and restore — which is what the code does — but a doc comment clarifying why the save/restore is needed (i.e., fold_children_with may have mutated current_context to a property key) would help future readers.

5. Test coverage gaps

The added fixture covers the happy path (class and function components). Consider adding fixtures for:

  • When css is not imported from emotion — the function correctly returns early via the let Some(...) guard, but a test would confirm no transform occurs
  • css prop value that is already a css() call — correctly skipped by the Expr::Object | Expr::Array match, but worth a regression test
  • When the css prop has a namespace import path (e.g. import * as emotion from '@emotion/react' then css={...}) — to verify the PackageMeta::Named(ExprKind::Css) check handles or explicitly excludes this case

Summary

The core logic is sound and the fix addresses the reported issue. The main actionable items before merging are:

  1. Fix the unconditional wrapping when no label/sourcemap args will be added
  2. Add a changeset file

@kdy1 kdy1 merged commit e2775d6 into main Mar 21, 2026
10 of 11 checks passed
@kdy1 kdy1 deleted the claude/issue-160-20260321-0122 branch March 21, 2026 02:00
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17702908a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +436 to +446
// Find a css identifier (ExprKind::Css) in import_packages
let css_id = self
.import_packages
.iter()
.find_map(|(id, meta)| {
if matches!(meta, PackageMeta::Named(ExprKind::Css)) {
Some(id.clone())
} else {
None
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Support namespace imports when wrapping css props

rewrite_css_prop_attr() only looks for PackageMeta::Named(ExprKind::Css), so it bails out for files that import Emotion as a namespace. That import style is explicitly supported elsewhere in this transformer (generate_import_info() records import * as emotionReact from '@emotion/react' as PackageMeta::Namespace, and tests/fixture/namespace-import covers it), but <div css={{ color: 'red' }} /> will still skip this new path and lose the added label/sourcemap metadata. Please handle the namespace case by building a member-expression callee instead of requiring a named css binding.

Useful? React with 👍 / 👎.

_ => continue,
}

let expr_pos = container_span.lo;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Attach the purity annotation to the generated css() call

Using container_span.lo here points at the JSX expression braces rather than the rewritten css(...) call. In the new tests/fixture/issues/160/output.ts, the object/array css props are emitted as css: css(...) without the /*#__PURE__*/ marker that template-literal css props still get in tests/fixture/css-in-callback/output.ts, so bundlers can no longer tree-shake these injected calls when the surrounding JSX becomes dead code. The annotation needs to be attached to the generated call site, not the JSX container span.

Useful? React with 👍 / 👎.

kdy1 added a commit that referenced this pull request Mar 21, 2026
## Summary
- Addressed `chatgpt-codex-connector[bot]` review follow-ups across
merged PRs: #585, #591, #592, #593, #594, #595, #597, #598, #599.
- Applied the fixes as 9 separate commits (one commit per original PR)
in a single follow-up branch.

## What Changed
- #585 / #592 (`loadable-components`)
- Fixed source-less default-import matching to honor the configured
local name.
- Updated `ssr: false` detection to respect final object-literal
override order.
  - Added/updated fixtures for both behaviors.
- #591 / #594 (`formatjs`)
- Added JSX member-expression message component support (e.g.
`ReactIntl.FormattedMessage`).
  - Updated #532 regression coverage to validate the `ast: true` path.
- #593 / #595 (`graphql-codegen-client-preset`)
  - Extended `namingConvention` parsing to accept string/object forms.
- Preserved names for `keep`/unknown conventions instead of forcing
PascalCase.
- Fixed Windows absolute `filename` path handling in WASM runtime path
resolution.
  - Added unit coverage for the new config/path behaviors.
- #597 / #599 (`emotion`)
- Ensured tagged-template labels are terminated before sourcemap
comments.
- Added css-prop rewrite support for namespace imports
(`emotionReact.css`).
  - Attached PURE comments to the generated call site span.
  - Updated emotion fixtures accordingly.
- #598 (docs)
- Corrected capability descriptions in `packages/jest/README.tmpl.md`
and `packages/swc-sdk/README.tmpl.md`.

## Validation
- `cargo test -p swc_plugin_loadable_components --test fixture --
--ignored`
- `cargo test -p swc_plugin_graphql_codegen_client_preset`
- `cargo test -p swc_emotion --test fixture -- --ignored`
- `pnpm -C /Users/kdy1/.codex/worktrees/17e6/plugins/packages/formatjs
test`

All passed (formatjs has an existing non-blocking Vitest warning about
an un-awaited rejects assertion).
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.

Emotion css prop using object styles produces no sourceMaps

2 participants