Skip to content

[codemods] Convert static css props to StyleX (Emotion→StyleX, 2/6) - #1769

Open
ahmedsadid wants to merge 9 commits into
facebook:mainfrom
ahmedsadid:codemod/emotion-m1-static
Open

[codemods] Convert static css props to StyleX (Emotion→StyleX, 2/6)#1769
ahmedsadid wants to merge 9 commits into
facebook:mainfrom
ahmedsadid:codemod/emotion-m1-static

Conversation

@ahmedsadid

Copy link
Copy Markdown
Contributor

What changed / motivation ?

Part of adding a tool that migrates Emotion styles to StyleX (#1766). This PR
does its first real conversion: a static Emotion css prop becomes a
stylex.create block plus a stylex.props(...) reference.

// before
<div css={{ color: 'red' }}>Badge</div>

// after
const styles = stylex.create({ badge: { color: 'red' } });
<div {...stylex.props(styles.badge)}>Badge</div>
  • Covers the object forms with static values: css={{ … }} and css({ … }).
  • Converts a file only when it can safely convert everything in it. If a
    file has anything it can't confidently handle yet — :hover/media conditions,
    a value like '8px 16px', a template literal, css on a custom component, or
    a file that already uses StyleX — it leaves the file unchanged and reports why.
    (Converting just the safe parts of a mixed file comes in a later PR.)
  • Written in two layers: general code that generates StyleX (src/core/) and
    Emotion-specific code that reads the styles and edits the JSX
    (src/adapters/emotion/). The general layer knows nothing about Emotion, so it
    can be reused for other source libraries later.

Testing

  • Input → output fixtures, each also verified to compile, lint cleanly, and
    render the same CSS as the Emotion original.
  • Covers conversions (single prop, css(...) call form, two components in one
    file) and refusals (pseudo-selectors, shorthand conflict, template literal,
    custom component, file already using StyleX).
  • The general layer is unit-tested with styles fed in directly (no Emotion),
    confirming it's self-contained.

Linked PR/Issues

Part of #1766 · builds on #1768

Additional Context

Stacked on #1768 (the package scaffold), but this change stands on its own to
review.

Pre-flight checklist

  • I have read the contributing guidelines
  • Performed a self-review of my code

ahmedsadid and others added 9 commits July 23, 2026 17:03
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te proven to fail on a broken pair

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-written IR

Source-neutral: emit is fed hand-written IR (no Emotion) so the engine is
provably library-agnostic. Emits alphabetically-sorted keys (satisfies
stylex/sort-keys with zero autofixes) and refuses conditions/duplicates/
non-static values rather than mis-emitting. styleToObjectAst added to the
rewriter wrapper is emit's AST-rendering bridge, keeping core/emit AST-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orts, orchestrator

Reads static css={{...}} / css({...}) on host elements into neutral
declarations; rewrites each site to {...stylex.props(styles.key)}; inserts a
single per-file registry above the first converted component; cleans up the
@emotion/react import and @jsxImportSource pragma. Whole-file-or-nothing
(M1 policy): any blocker (pseudo/media, template literal, shorthand overlap,
component css prop, pre-existing stylex) skips the file untouched with reasons.
core/ never imported here — the seam holds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CSS treats 'rgb(10, 20, 30)' and 'rgb(10,20,30)' as identical and the StyleX
compiler emits the latter; without this the gate flagged every rgb()/multi-arg
value as a false diff. Comma-whitespace is now canonicalized on both sides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…add 8 fixtures

The two M0-skipped checks (byte-exact match, per-fixture semantic-diff) now run
for real against transformEmotionFile. Skip-fixtures assert the transform
refused loudly and left the file byte-identical. New fixtures: css-call-form,
static-values, two-sites (convert) + skip-{pseudo,template-literal,
shorthand-conflict,component-css,existing-stylex} (refuse).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…red coverage

Replaces the M0 throwing stub with a real StyleX-object -> IR reader (flat
static values + fallback arrays). The harness now round-trips each covered
corpus entry (read -> emit -> compile -> semantic-diff vs original, empty
allowlist) and reports measured coverage; condition/keyframe entries remain
typed, SAFE coverage gaps (2/5 covered at M1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 23, 2026
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

@ahmedsadid is attempting to deploy a commit to the Meta Open Source Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant