11import type { Collection } from "jscodeshift" ;
22import type { StyledDecl } from "./transform-types.js" ;
33
4+ // Void HTML tags that don't have children
5+ const VOID_TAGS = new Set ( [
6+ "area" ,
7+ "base" ,
8+ "br" ,
9+ "col" ,
10+ "embed" ,
11+ "hr" ,
12+ "img" ,
13+ "input" ,
14+ "link" ,
15+ "meta" ,
16+ "param" ,
17+ "source" ,
18+ "track" ,
19+ "wbr" ,
20+ ] ) ;
21+
22+ /**
23+ * Generates a minimal wrapper component that only destructures the necessary props
24+ * and applies stylex.props() directly without className/style/rest merging.
25+ * Uses props.children directly instead of destructuring it.
26+ */
27+ function emitMinimalWrapper ( args : {
28+ j : any ;
29+ localName : string ;
30+ tagName : string ;
31+ styleArgs : any [ ] ;
32+ destructureProps : string [ ] ;
33+ displayName : string | undefined ;
34+ patternProp : ( keyName : string , valueId ?: any ) => any ;
35+ } ) : any [ ] {
36+ const { j, localName, tagName, styleArgs, destructureProps, displayName, patternProp } = args ;
37+ const isVoidTag = VOID_TAGS . has ( tagName ) ;
38+ const propsId = j . identifier ( "props" ) ;
39+
40+ // Build destructure pattern for dynamic props only (not children)
41+ const patternProps : any [ ] = destructureProps . filter ( Boolean ) . map ( ( name ) => patternProp ( name ) ) ;
42+
43+ const stylexPropsCall = j . callExpression (
44+ j . memberExpression ( j . identifier ( "stylex" ) , j . identifier ( "props" ) ) ,
45+ styleArgs ,
46+ ) ;
47+
48+ const openingEl = j . jsxOpeningElement (
49+ j . jsxIdentifier ( tagName ) ,
50+ [ j . jsxSpreadAttribute ( stylexPropsCall ) ] ,
51+ false ,
52+ ) ;
53+
54+ // Use props.children directly
55+ const propsChildren = j . memberExpression ( propsId , j . identifier ( "children" ) ) ;
56+
57+ const jsx = isVoidTag
58+ ? ( {
59+ type : "JSXElement" ,
60+ openingElement : { ...openingEl , selfClosing : true } ,
61+ closingElement : null ,
62+ children : [ ] ,
63+ } as any )
64+ : j . jsxElement ( openingEl , j . jsxClosingElement ( j . jsxIdentifier ( tagName ) ) , [
65+ j . jsxExpressionContainer ( propsChildren ) ,
66+ ] ) ;
67+
68+ // Only emit destructure statement if there are props to destructure
69+ const bodyStmts : any [ ] = [ ] ;
70+ if ( patternProps . length > 0 ) {
71+ bodyStmts . push (
72+ j . variableDeclaration ( "const" , [
73+ j . variableDeclarator ( j . objectPattern ( patternProps as any ) , propsId ) ,
74+ ] ) ,
75+ ) ;
76+ }
77+ bodyStmts . push ( j . returnStatement ( jsx as any ) ) ;
78+
79+ const result : any [ ] = [
80+ j . functionDeclaration ( j . identifier ( localName ) , [ propsId ] , j . blockStatement ( bodyStmts ) ) ,
81+ ] ;
82+
83+ if ( displayName ) {
84+ result . push (
85+ j . expressionStatement (
86+ j . assignmentExpression (
87+ "=" ,
88+ j . memberExpression ( j . identifier ( localName ) , j . identifier ( "displayName" ) ) ,
89+ j . literal ( displayName ) ,
90+ ) ,
91+ ) ,
92+ ) ;
93+ }
94+
95+ return result ;
96+ }
97+
498export function emitWrappers ( args : {
599 root : Collection < any > ;
6100 j : any ;
@@ -300,6 +394,7 @@ export function emitWrappers(args: {
300394 continue ;
301395 }
302396 const tagName = d . base . tagName ;
397+ const supportsExternalStyles = d . supportsExternalStyles ?? false ;
303398
304399 // Build style arguments: base + extends + dynamic variants (as conditional expressions).
305400 const styleArgs : any [ ] = [
@@ -392,6 +487,22 @@ export function emitWrappers(args: {
392487 const omitRestSpreadForTransientProps =
393488 ! dropPrefix && dropProps . length > 0 && dropProps . every ( ( p ) => p . startsWith ( "$" ) ) ;
394489
490+ // When supportsExternalStyles is false, generate minimal wrapper without className/style/rest merging
491+ if ( ! supportsExternalStyles ) {
492+ emitted . push (
493+ ...emitMinimalWrapper ( {
494+ j,
495+ localName : d . localName ,
496+ tagName,
497+ styleArgs,
498+ destructureProps : destructureParts ,
499+ displayName : d . withConfig ?. displayName ,
500+ patternProp,
501+ } ) ,
502+ ) ;
503+ continue ;
504+ }
505+
395506 const patternProps : any [ ] = [
396507 patternProp ( "className" , classNameId ) ,
397508 // Pull out `children` for non-void elements so we don't forward it as an attribute.
@@ -565,6 +676,7 @@ export function emitWrappers(args: {
565676 }
566677 const tagName = d . base . tagName ;
567678 const displayName = d . withConfig ?. displayName ;
679+ const supportsExternalStyles = d . supportsExternalStyles ?? false ;
568680 const styleArgs : any [ ] = [
569681 ...( d . extendsStyleKey
570682 ? [ j . memberExpression ( j . identifier ( "styles" ) , j . identifier ( d . extendsStyleKey ) ) ]
@@ -578,23 +690,23 @@ export function emitWrappers(args: {
578690 const styleId = j . identifier ( "style" ) ;
579691 const restId = j . identifier ( "rest" ) ;
580692
581- const voidTags = new Set ( [
582- "area" ,
583- "base" ,
584- "br" ,
585- "col" ,
586- "embed" ,
587- "hr" ,
588- "img" ,
589- "input" ,
590- "link" ,
591- "meta" ,
592- "param" ,
593- "source" ,
594- "track" ,
595- "wbr" ,
596- ] ) ;
597- const isVoidTag = voidTags . has ( tagName ) ;
693+ const isVoidTag = VOID_TAGS . has ( tagName ) ;
694+
695+ // When supportsExternalStyles is false, generate minimal wrapper
696+ if ( ! supportsExternalStyles ) {
697+ emitted . push (
698+ ... emitMinimalWrapper ( {
699+ j ,
700+ localName : d . localName ,
701+ tagName ,
702+ styleArgs ,
703+ destructureProps : [ ] ,
704+ displayName ,
705+ patternProp ,
706+ } ) ,
707+ ) ;
708+ continue ;
709+ }
598710
599711 const patternProps : any [ ] = [
600712 patternProp ( "className" , classNameId ) ,
0 commit comments