Skip to content

Implement StyleX transform scaffold with stylis parsing#1

Closed
skovhus wants to merge 6 commits intomainfrom
codex/plan-migration-from-styled-components-to-stylex
Closed

Implement StyleX transform scaffold with stylis parsing#1
skovhus wants to merge 6 commits intomainfrom
codex/plan-migration-from-styled-components-to-stylex

Conversation

@skovhus
Copy link
Copy Markdown
Owner

@skovhus skovhus commented Dec 29, 2025

Summary

  • add stylis for parsing styled-components template literals into structured CSS
  • build a placeholder-aware StyleX transform that generates wrapper components and style objects
  • retain warning collection while safely injecting StyleX imports and adapter hooks

Testing

  • pnpm test:run --reporter=dot

Codex Task

Base automatically changed from init to main December 30, 2025 07:06
@skovhus skovhus force-pushed the codex/plan-migration-from-styled-components-to-stylex branch from 3306242 to 6de6fee Compare December 30, 2025 07:58
@skovhus skovhus closed this Jan 1, 2026
@skovhus skovhus deleted the codex/plan-migration-from-styled-components-to-stylex branch January 1, 2026 19:49
skovhus pushed a commit that referenced this pull request Feb 21, 2026
… :is(*) workaround

Addresses review comments:
- Issue #1/#4: Use the regex capture group to distinguish + from ~ combinator,
  emit an info-level warning for & + & (adjacent becomes general sibling in StyleX)
- Issue #3: Add TODO to remove :is(*) workaround when StyleX Babel plugin supports
  no-arg siblingBefore() calls
- Issue #2: Skipped — ComputedKeyEntry upstream type already uses unknown, so
  tightening helper params would create a mismatch without upstream changes

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L
skovhus added a commit that referenced this pull request Feb 23, 2026
* feat: support self-referencing sibling selectors (& + & and & ~ &)

Transform CSS sibling combinators to StyleX relational selector APIs:
- `& + &` (adjacent sibling) → `stylex.when.siblingBefore(":is(*)")`
- `& ~ &` (general sibling) → `stylex.when.anySibling(":is(*)")`

Reuses existing `perPropComputedMedia` mechanism for computed style keys
and `ancestorSelectorParents` to add `stylex.defaultMarker()` to components.

Promoted 7 test cases from _unimplemented to supported:
- selector-sibling, selector-adjacentSiblingDestructure, selector-generalSibling
- selector-siblingBaseAfter, selector-siblingMedia
- selector-siblingMarkerScoping, selector-siblingInterpolated

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L

* fix: use siblingBefore() for both & + & and & ~ & selectors

stylex.when.anySibling() matches both directions (uses :has()),
while CSS ~ is forward-only. This caused the first element to
incorrectly receive sibling styles.

siblingBefore() is forward-only ("style me when a sibling before me
has the marker"), which correctly leaves the first element unstyled
— matching CSS ~ semantics. Both & + & and & ~ & now map to
siblingBefore().

Also removes the ESLint no-lookahead-selectors override since
anySibling() is no longer used.

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L

* docs: document known semantic broadenings for sibling selectors

1. siblingBefore() emits `~ *` (general sibling), not `+ *` (adjacent) —
   StyleX has no adjacent-sibling API.
2. defaultMarker() is file-global, not component-scoped — defineMarker()
   needed for strict scoping.

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L

* fix: emit info warning for & + & semantic broadening and add TODO for :is(*) workaround

Addresses review comments:
- Issue #1/#4: Use the regex capture group to distinguish + from ~ combinator,
  emit an info-level warning for & + & (adjacent becomes general sibling in StyleX)
- Issue #3: Add TODO to remove :is(*) workaround when StyleX Babel plugin supports
  no-arg siblingBefore() calls
- Issue #2: Skipped — ComputedKeyEntry upstream type already uses unknown, so
  tightening helper params would create a mismatch without upstream changes

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L

* refactor: apply code quality improvements from PR #212

1. Replace processSiblingDeclarations() + addSiblingComputedEntry() with
   processDeclarationsIntoBucket() + getOrCreateComputedMediaEntry().
   The shared helper also handles cssHelperPropValues defaults which the
   old code missed (used styleObj[prop] ?? null instead).

2. Extract getOrCreateComputedMediaEntry() to deduplicate the get-or-create
   + default-value logic between resolvedSelectorMedia and sibling handling.

3. Extract getStyleArgKey() and hasStyleArgKey() in style-merger.ts to
   centralise the MemberExpression pattern check (was inlined 3 times).

4. Add stylesIdentifier parameter to postProcessTransformedAst() and pass
   it through the pipeline, replacing hardcoded "styles" references.
   Add "unresolved interpolation in sibling selector" warning type.

All PR #210 features preserved: both & + & and & ~ & support, @media
nesting inside sibling selectors, info warning for adjacent broadening.

https://claude.ai/code/session_01VK1biH425fiXkEFzaqnZ4L

---------

Co-authored-by: Claude <noreply@anthropic.com>
cursor Bot pushed a commit that referenced this pull request Feb 27, 2026
Finding #1 (MODERATE): canSafelyInline and resolvePerSiteProps missed
JSXSelfClosingElement nodes (<Foo />) — consumed props on self-closing
elements were not checked for safety or variant creation. Fixed by
extracting findAllJsxSites() helper that searches both JSXElement and
JSXSelfClosingElement, used by all JSX scanning functions.

Finding #2 (MODERATE): attrsAsTag (.attrs({ as: SomeComponent })) was
not checked — component-ref overrides were silently ignored. Fixed by
bailing extractStaticPropsFromAttrs when attrsAsTag is set.

Finding #3 (MODERATE): $-prefixed non-consumed transient props would
leak to the DOM in the wrapper path for inlined intrinsic elements.
Fixed by setting shouldForwardProp.dropPrefix to "$" in applyResolution.

Finding #4 (LOW): findVaryingProps had an always-true condition
(!allSame || hasAnyValue where hasAnyValue was guaranteed true).
Simplified: track all consumed props present at any JSX site.

Finding #5 (LOW): Covered by Finding #3's dropPrefix fix.

Finding #6 (LOW): Inaccurate comment in applyResolution. Updated.

Finding #7 (LOW): Static property inheritance lost for resolved
components. Fixed by saving originalBaseIdent before overwriting base.

Added 3 regression tests:
- attrsAsTag component ref bail
- self-closing element with dynamic consumed prop (canSafelyInline)
- self-closing element with static consumed prop (variant creation)

Co-authored-by: Kenneth Skovhus <skovhus@users.noreply.github.com>
@skovhus skovhus mentioned this pull request Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant