@fluixi/compiler 1.0.0-alpha.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/babel-OGRQKOZA.mjs +2 -0
  4. package/dist/chunk-3SAEGOMQ.mjs +1 -0
  5. package/dist/codegen/backend.cjs +1 -0
  6. package/dist/codegen/backend.d.ts +22 -0
  7. package/dist/codegen/backend.d.ts.map +1 -0
  8. package/dist/codegen/backend.js +1 -0
  9. package/dist/codegen/backend.mjs +0 -0
  10. package/dist/codegen/backends/imperative.cjs +1 -0
  11. package/dist/codegen/backends/imperative.d.ts +3 -0
  12. package/dist/codegen/backends/imperative.d.ts.map +1 -0
  13. package/dist/codegen/backends/imperative.js +152 -0
  14. package/dist/codegen/backends/imperative.mjs +1 -0
  15. package/dist/codegen/contract.cjs +1 -0
  16. package/dist/codegen/contract.d.ts +25 -0
  17. package/dist/codegen/contract.d.ts.map +1 -0
  18. package/dist/codegen/contract.js +30 -0
  19. package/dist/codegen/contract.mjs +1 -0
  20. package/dist/frontend/babel/build-ir.cjs +1 -0
  21. package/dist/frontend/babel/build-ir.d.ts +23 -0
  22. package/dist/frontend/babel/build-ir.d.ts.map +1 -0
  23. package/dist/frontend/babel/build-ir.js +224 -0
  24. package/dist/frontend/babel/build-ir.mjs +1 -0
  25. package/dist/frontend/babel/index.cjs +2 -0
  26. package/dist/frontend/babel/index.d.ts +34 -0
  27. package/dist/frontend/babel/index.d.ts.map +1 -0
  28. package/dist/frontend/babel/index.js +65 -0
  29. package/dist/frontend/babel/index.mjs +2 -0
  30. package/dist/frontend/babel/plugin.cjs +2 -0
  31. package/dist/frontend/babel/plugin.d.ts +122 -0
  32. package/dist/frontend/babel/plugin.d.ts.map +1 -0
  33. package/dist/frontend/babel/plugin.js +1509 -0
  34. package/dist/frontend/babel/plugin.mjs +2 -0
  35. package/dist/frontend/babel/server-functions.cjs +1 -0
  36. package/dist/frontend/babel/server-functions.d.ts +11 -0
  37. package/dist/frontend/babel/server-functions.d.ts.map +1 -0
  38. package/dist/frontend/babel/server-functions.js +88 -0
  39. package/dist/frontend/babel/server-functions.mjs +1 -0
  40. package/dist/frontend/babel/types.cjs +1 -0
  41. package/dist/frontend/babel/types.d.ts +34 -0
  42. package/dist/frontend/babel/types.d.ts.map +1 -0
  43. package/dist/frontend/babel/types.js +1 -0
  44. package/dist/frontend/babel/types.mjs +0 -0
  45. package/dist/frontend/types.cjs +1 -0
  46. package/dist/frontend/types.d.ts +26 -0
  47. package/dist/frontend/types.d.ts.map +1 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.mjs +0 -0
  50. package/dist/index.cjs +1 -0
  51. package/dist/index.d.ts +19 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +20 -0
  54. package/dist/index.mjs +1 -0
  55. package/dist/integrations.cjs +12 -0
  56. package/dist/integrations.d.ts +251 -0
  57. package/dist/integrations.d.ts.map +1 -0
  58. package/dist/integrations.js +790 -0
  59. package/dist/integrations.mjs +11 -0
  60. package/dist/ir/nodes.cjs +1 -0
  61. package/dist/ir/nodes.d.ts +80 -0
  62. package/dist/ir/nodes.d.ts.map +1 -0
  63. package/dist/ir/nodes.js +1 -0
  64. package/dist/ir/nodes.mjs +0 -0
  65. package/dist/options.cjs +1 -0
  66. package/dist/options.d.ts +18 -0
  67. package/dist/options.d.ts.map +1 -0
  68. package/dist/options.js +9 -0
  69. package/dist/options.mjs +1 -0
  70. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  71. package/package.json +70 -0
@@ -0,0 +1,1509 @@
1
+ /**
2
+ * @fileoverview Comprehensive Babel Plugin for Reactive JSX Transformation
3
+ * @module @fluixi/babel-plugin-jsx
4
+ *
5
+ * This plugin transforms JSX into optimized reactive DOM operations with:
6
+ * - Fine-grained reactivity detection
7
+ * - Signal/store wrapping
8
+ * - Event delegation
9
+ * - Static hoisting
10
+ * - Lit-HTML integration
11
+ * - Control flow optimization
12
+ */
13
+ import { declare } from '@babel/helper-plugin-utils';
14
+ import { types as t, template, } from '@babel/core';
15
+ import { buildIR } from './build-ir.js';
16
+ import { imperativeBackend } from '../../codegen/backends/imperative.js';
17
+ // ============================================================================
18
+ // Constants
19
+ // ============================================================================
20
+ const CONTROL_FLOW_COMPONENTS = new Set([
21
+ 'Show',
22
+ 'For',
23
+ 'Index',
24
+ 'Switch',
25
+ 'Match',
26
+ 'Portal',
27
+ 'Dynamic',
28
+ 'ErrorBoundary',
29
+ 'Suspense',
30
+ ]);
31
+ // Control-flow components read a single reactive *value* prop inside their own tracking
32
+ // scope. Emit that prop as a getter (like `children`) so reading it re-runs the caller's
33
+ // expression — otherwise `each={items()}` / `when={cond()}` snapshot once and never update.
34
+ // The runtime routes these through `resolveReactiveProp`, which also unwraps the accessor
35
+ // form (`each={items}`), so all three shapes work with no compiler-side ambiguity.
36
+ //
37
+ // Excluded on purpose: `Dynamic.component` (a component IS a function, so the value can't be
38
+ // told apart from an accessor — its idiomatic `component={signal}` form already reacts) and
39
+ // `Portal.mount` (read once at mount, not a reactive value).
40
+ const CONTROL_FLOW_VALUE_PROPS = {
41
+ Show: 'when',
42
+ For: 'each',
43
+ Index: 'each',
44
+ Match: 'when',
45
+ };
46
+ const REACTIVE_PATTERNS = {
47
+ signalCall: /\w+\(\)$/,
48
+ storeAccess: /\w+\.\w+/,
49
+ arrowFunction: /^\(\)\s*=>/,
50
+ functionCall: /^function\s*\(/,
51
+ };
52
+ const EVENT_PROPS = /^on[A-Z]/;
53
+ const DELEGATABLE_EVENTS = [
54
+ 'click',
55
+ 'dblclick',
56
+ 'input',
57
+ 'change',
58
+ 'submit',
59
+ 'focus',
60
+ 'blur',
61
+ 'keydown',
62
+ 'keyup',
63
+ 'keypress',
64
+ 'mousedown',
65
+ 'mouseup',
66
+ 'mouseover',
67
+ 'mouseout',
68
+ 'mouseenter',
69
+ 'mouseleave',
70
+ ];
71
+ // ============================================================================
72
+ // Main Plugin
73
+ // ============================================================================
74
+ export default declare((api, options = {}) => {
75
+ api.assertVersion(7);
76
+ const { runtime = 'automatic', importSource = '@fluixi/jsx', pragma = 'jsx', pragmaFrag = 'Fragment', development = false, detectReactivity = true, useLitHTML = true, hoistStatics = true, delegateEvents = true, delegatedEvents = DELEGATABLE_EVENTS, optimizeControlFlow = true, sourceMaps = true, signalModule = '@fluixi/dom', controlFlowModule = '@fluixi/dom', reactiveModule = '@fluixi/reactive/signal', autoShowTransform = false, backend = 'imperative', codegen = 'inline', } = options;
77
+ const pluginState = {
78
+ backend,
79
+ codegen,
80
+ irImports: new Set(),
81
+ libModule: '@fluixi/core',
82
+ hasJSX: false,
83
+ hasReactivity: false,
84
+ hasLitHTML: false,
85
+ needsSignalImport: false,
86
+ needsControlFlowImport: false,
87
+ needsCreateMemo: false,
88
+ controlFlowComponents: new Set(),
89
+ delegatedEvents: new Set(),
90
+ staticElements: new Map(),
91
+ staticCounter: 0,
92
+ };
93
+ return {
94
+ name: '@fluixi/babel-plugin-jsx',
95
+ manipulateOptions(opts, parserOpts) {
96
+ // Enable JSX parsing
97
+ parserOpts.plugins.push('jsx', 'typescript');
98
+ },
99
+ pre(state) {
100
+ // Initialize plugin state
101
+ Object.assign(state, pluginState);
102
+ },
103
+ visitor: {
104
+ // Map @fluixi/core/rx imports to @fluixi/reactive for client-side code
105
+ ImportDeclaration(path, state) {
106
+ const source = path.node.source.value;
107
+ // Map @fluixi/core/rx to @fluixi/reactive (client-side reactive primitives)
108
+ if (source === '@fluixi/core/rx') {
109
+ path.node.source = t.stringLiteral('@fluixi/reactive');
110
+ }
111
+ // Also handle fluixi server imports - keep as is for SSR
112
+ // @fluixi/core/server stays as is (includes server reactive mappings)
113
+ },
114
+ Program: {
115
+ enter(path, state) {
116
+ // Reset state for new file
117
+ Object.assign(state, {
118
+ hasJSX: false,
119
+ hasReactivity: false,
120
+ hasLitHTML: false,
121
+ needsSignalImport: false,
122
+ needsLitrxImport: false,
123
+ needsControlFlowImport: false,
124
+ needsCreateMemo: false,
125
+ controlFlowComponents: new Set(),
126
+ delegatedEvents: new Set(),
127
+ staticElements: new Map(),
128
+ autoShow: autoShowTransform,
129
+ staticCounter: 0,
130
+ libModule: '@fluixi/core',
131
+ backend,
132
+ codegen,
133
+ irImports: new Set(),
134
+ needsCreateNativeElement: false,
135
+ needsInsert: false,
136
+ needsSpread: false,
137
+ needsCreateComponent: false,
138
+ needsMergeProps: false,
139
+ });
140
+ },
141
+ exit(path, state) {
142
+ const imports = [];
143
+ if (!state.hasJSX) {
144
+ // Add signal directive import if needed
145
+ if (state.needsSignalImport) {
146
+ imports.push(t.importDeclaration([
147
+ t.importSpecifier(t.identifier('signal'), t.identifier('signal')),
148
+ ], t.stringLiteral(signalModule)));
149
+ }
150
+ if (state.needsLitrxImport && !path.scope.hasBinding('litrx')) {
151
+ imports.push(t.importDeclaration([
152
+ t.importSpecifier(t.identifier('litrx'), t.identifier('litrx')),
153
+ ], t.stringLiteral(state.libModule)));
154
+ }
155
+ }
156
+ if (state.hasJSX) {
157
+ if (backend === 'imperative' && codegen === 'ir') {
158
+ // IR pipeline: import exactly the symbols emit() reported, splitting
159
+ // control-flow components to the control-flow module.
160
+ const CF = new Set([
161
+ 'Show', 'For', 'Index', 'Switch', 'Match',
162
+ 'Dynamic', 'ErrorBoundary', 'Portal', 'Suspense',
163
+ ]);
164
+ // Reactive primitives come from the signal core, not the dom module.
165
+ const REACTIVE = new Set([
166
+ 'createMemo', 'createEffect', 'createRenderEffect', 'createRoot',
167
+ 'createSignal', 'onCleanup', 'batch', 'untrack',
168
+ ]);
169
+ const all = Array.from(state.irImports);
170
+ const spec = (n) => t.importSpecifier(t.identifier(n), t.identifier(n));
171
+ const unbound = (s) => !path.scope.hasBinding(s);
172
+ const reactive = all.filter((s) => REACTIVE.has(s) && unbound(s));
173
+ const core = all.filter((s) => !CF.has(s) && !REACTIVE.has(s) && unbound(s));
174
+ const cf = all.filter((s) => CF.has(s) && unbound(s));
175
+ if (reactive.length > 0) {
176
+ imports.push(t.importDeclaration(reactive.map(spec), t.stringLiteral(reactiveModule)));
177
+ }
178
+ if (core.length > 0) {
179
+ imports.push(t.importDeclaration(core.map(spec), t.stringLiteral(signalModule)));
180
+ }
181
+ if (cf.length > 0) {
182
+ imports.push(t.importDeclaration(cf.map(spec), t.stringLiteral(controlFlowModule)));
183
+ }
184
+ }
185
+ else if (backend === 'imperative') {
186
+ // Compiled imperative output: import only the runtime primitives
187
+ // actually emitted, straight from the dom runtime module.
188
+ const want = [
189
+ [!!state.needsCreateNativeElement, 'createNativeElement'],
190
+ [!!state.needsSpread, 'spread'],
191
+ [!!state.needsInsert, 'insert'],
192
+ [!!state.needsCreateComponent, 'createComponent'],
193
+ [!!state.needsMergeProps, 'mergeProps'],
194
+ ];
195
+ const specifiers = want
196
+ .filter(([need, name]) => need && !path.scope.hasBinding(name))
197
+ .map(([, name]) => t.importSpecifier(t.identifier(name), t.identifier(name)));
198
+ if (specifiers.length > 0) {
199
+ imports.push(t.importDeclaration(specifiers, t.stringLiteral(signalModule)));
200
+ }
201
+ }
202
+ else if (runtime === 'automatic') {
203
+ // Add JSX runtime imports
204
+ const specifiers = [
205
+ t.importSpecifier(t.identifier('jsx'), t.identifier('jsx')),
206
+ t.importSpecifier(t.identifier('jsxs'), t.identifier('jsxs')),
207
+ t.importSpecifier(t.identifier('Fragment'), t.identifier('Fragment')),
208
+ ];
209
+ if (development) {
210
+ specifiers.push(t.importSpecifier(t.identifier('jsxDEV'), t.identifier('jsxDEV')));
211
+ }
212
+ // Add mergeProps import if spreads are used
213
+ if (state.needsMergeProps) {
214
+ specifiers.push(t.importSpecifier(t.identifier('mergeProps'), t.identifier('mergeProps')));
215
+ }
216
+ imports.push(t.importDeclaration(specifiers, t.stringLiteral(importSource)));
217
+ }
218
+ // createMemo (wraps conditionals/components) is a reactive primitive,
219
+ // so it comes from the reactive module, not dom.
220
+ if (state.needsCreateMemo && !path.scope.hasBinding('createMemo')) {
221
+ imports.push(t.importDeclaration([t.importSpecifier(t.identifier('createMemo'), t.identifier('createMemo'))], t.stringLiteral(backend === 'imperative'
222
+ ? reactiveModule
223
+ : state.libModule ?? '@fluixi/core')));
224
+ }
225
+ // Add control flow imports for auto-transformed components (Show, etc.)
226
+ // Only add components that aren't already bound in the file scope.
227
+ if (state.needsControlFlowImport &&
228
+ state.controlFlowComponents.size > 0) {
229
+ const unbound = Array.from(state.controlFlowComponents).filter((name) => !path.scope.hasBinding(name));
230
+ if (unbound.length > 0) {
231
+ const specifiers = unbound.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
232
+ imports.push(t.importDeclaration(specifiers, t.stringLiteral(controlFlowModule)));
233
+ }
234
+ }
235
+ // Add event delegation setup if needed
236
+ if (delegateEvents && state.delegatedEvents.size > 0) {
237
+ imports.push(t.importDeclaration([
238
+ t.importSpecifier(t.identifier('delegateEvents'), t.identifier('delegateEvents')),
239
+ ], t.stringLiteral(backend === 'imperative' ? signalModule : '@fluixi/jsx')));
240
+ // Add delegateEvents call at top of program
241
+ const eventsArray = t.arrayExpression(Array.from(state.delegatedEvents).map((event) => t.stringLiteral(event)));
242
+ const delegateCall = t.expressionStatement(t.callExpression(t.identifier('delegateEvents'), [eventsArray]));
243
+ path.node.body.unshift(delegateCall);
244
+ }
245
+ // Add hoisted static elements
246
+ if (hoistStatics && state.staticElements.size > 0) {
247
+ const hoisted = [];
248
+ state.staticElements.forEach((value, key) => {
249
+ hoisted.push(t.variableDeclaration('const', [
250
+ t.variableDeclarator(t.identifier(key), value),
251
+ ]));
252
+ });
253
+ path.node.body.unshift(...hoisted);
254
+ }
255
+ }
256
+ // Add all imports at the top
257
+ if (imports.length > 0) {
258
+ // console.log('imports are', imports);
259
+ // console.log('imports', imports);
260
+ path.node.body.unshift(...imports);
261
+ }
262
+ },
263
+ },
264
+ JSXElement(path, state) {
265
+ state.hasJSX = true;
266
+ // IR pipeline path: build IR -> emit() -> reparse. Handles the whole
267
+ // subtree, so we bypass the inline transform entirely.
268
+ if (backend === 'imperative' && codegen === 'ir') {
269
+ path.replaceWith(emitViaIR(path.node, state));
270
+ return;
271
+ }
272
+ const element = path.node;
273
+ const openingElement = element.openingElement;
274
+ // Check if it's a control flow component
275
+ if (t.isJSXIdentifier(openingElement.name) &&
276
+ CONTROL_FLOW_COMPONENTS.has(openingElement.name.name)) {
277
+ state.controlFlowComponents.add(openingElement.name.name);
278
+ if (optimizeControlFlow) {
279
+ const optimized = optimizeControlFlowComponent(element, state);
280
+ if (optimized) {
281
+ path.replaceWith(optimized);
282
+ return;
283
+ }
284
+ }
285
+ }
286
+ // Try to hoist static elements.
287
+ // Not in imperative mode: hoisting a live DOM node would share one node
288
+ // across renders. Static hoisting there needs template-clone (contract v2).
289
+ if (hoistStatics && backend !== 'imperative' && isStaticElement(element, state)) {
290
+ const hoisted = hoistStaticElement(element, state);
291
+ if (hoisted) {
292
+ path.replaceWith(hoisted);
293
+ return;
294
+ }
295
+ }
296
+ // Transform to JSX runtime calls
297
+ const transformed = transformJSXElement(element, state, {
298
+ runtime,
299
+ pragma,
300
+ development,
301
+ detectReactivity,
302
+ useLitHTML,
303
+ autoShow: autoShowTransform,
304
+ });
305
+ path.replaceWith(transformed);
306
+ },
307
+ JSXFragment(path, state) {
308
+ state.hasJSX = true;
309
+ if (backend === 'imperative' && codegen === 'ir') {
310
+ path.replaceWith(emitViaIR(path.node, state));
311
+ return;
312
+ }
313
+ const transformed = transformJSXFragment(path.node, state, {
314
+ runtime,
315
+ pragmaFrag,
316
+ development,
317
+ });
318
+ path.replaceWith(transformed);
319
+ },
320
+ // Detect reactive patterns in code
321
+ CallExpression(path, state) {
322
+ if (!detectReactivity)
323
+ return;
324
+ const callee = path.node.callee;
325
+ if (callee.name === 'handleToggle') {
326
+ // console.log('call express', path);
327
+ }
328
+ // Detect signal creation
329
+ if (t.isIdentifier(callee) &&
330
+ (callee.name === 'createSignal' ||
331
+ callee.name === 'createStore' ||
332
+ callee.name === 'useContext' ||
333
+ callee.name === 'useLocation')) {
334
+ // console.log('call express', callee.name);
335
+ state.hasReactivity = true;
336
+ }
337
+ },
338
+ // NOTE: JSXExpressionContainer visitor is intentionally a no-op.
339
+ // JSXElement.enter fires BEFORE children are traversed, so by the time
340
+ // Babel would visit nested JSXExpressionContainers the JSXElement has
341
+ // already been replaced via path.replaceWith(). Attribute and children
342
+ // expressions are processed directly in transformAttributeValue /
343
+ // transformChildren above.
344
+ JSXExpressionContainer(_path) {
345
+ // no-op
346
+ },
347
+ TaggedTemplateExpression(path, state) {
348
+ // const expressions = t.isTaggedTemplateExpression(path.node)
349
+ // ? (path as NodePath<t.TemplateLiteral>).get('expressions')
350
+ // : [(path as NodePath<t.JSXExpressionContainer>).get('expression')];
351
+ // if (t.isJSXExpressionContainer(path.node)) {
352
+ // console.log('is jsx');
353
+ // }
354
+ const { tag, quasi } = path.node;
355
+ // 1. Only target html`...`
356
+ // if (!t.isIdentifier(tag) || tag.name !== 'html') return;
357
+ path
358
+ .get('quasi')
359
+ .get('expressions')
360
+ .forEach((expPath, index) => {
361
+ const node = expPath.node;
362
+ // 2. Attribute Check: Look at the string part right before the expression
363
+ // Lit attributes look like: name="${exp}" or @click="${exp}"
364
+ const prevStringPart = quasi.quasis[index].value.raw;
365
+ const isAttribute = prevStringPart.trim().endsWith('=') ||
366
+ prevStringPart.match(/@[\w-]+$/);
367
+ if (isAttribute)
368
+ return; // Skip wrapping event listeners or attributes
369
+ // 3. Narrowing for Signal Calls: count()
370
+ // const isSignalCall = t.isCallExpression(node);
371
+ const isSignalCall = false;
372
+ // 4. Narrowing for Member Access: item.$name
373
+ let isReactiveMember = false;
374
+ if (t.isMemberExpression(node)) {
375
+ const property = node.property;
376
+ // Use Type Guard to narrow from Expression | PrivateName to Identifier
377
+ if (t.isIdentifier(property)) {
378
+ // console.log('exp', node);
379
+ property.name = property.name.replace('$', '');
380
+ state.needsLitrxImport = true;
381
+ isReactiveMember = true;
382
+ // if (property.name.startsWith('$')) {
383
+ // }
384
+ }
385
+ }
386
+ if (isSignalCall || isReactiveMember) {
387
+ // Transform: ${exp} -> ${watch(() => exp)}
388
+ expPath.replaceWith(
389
+ //t.identifier('litrx')
390
+ // t.identifier('(window as any).Fluixi.litrx')
391
+ t.callExpression(t.identifier('((window as any).Fluixi.litrx || litrx)'), [t.arrowFunctionExpression([], node)]));
392
+ }
393
+ });
394
+ },
395
+ // 'JSXExpressionContainer|TaggedTemplateExpression'(path, state) {
396
+ // const expressions = t.isTaggedTemplateExpression(path.node)
397
+ // ? (path as NodePath<t.TemplateLiteral>).get('expressions')
398
+ // : [(path as NodePath<t.JSXExpressionContainer>).get('expression')];
399
+ // if (t.isJSXExpressionContainer(path.node)) {
400
+ // console.log('is jsx');
401
+ // }
402
+ // console.log('exp', expressions);
403
+ // // const { tag, quasi } = path.node;
404
+ // // 1. Only target html`...`
405
+ // // if (!t.isIdentifier(tag) || tag.name !== 'html') return;
406
+ // // path
407
+ // // .get('quasi')
408
+ // // .get('expressions')
409
+ // // .forEach((expPath, index) => {
410
+ // // const node = expPath.node;
411
+ // // // 2. Attribute Check: Look at the string part right before the expression
412
+ // // // Lit attributes look like: name="${exp}" or @click="${exp}"
413
+ // // const prevStringPart = quasi.quasis[index].value.raw;
414
+ // // const isAttribute =
415
+ // // prevStringPart.trim().endsWith('=') ||
416
+ // // prevStringPart.match(/@[\w-]+$/);
417
+ // // if (isAttribute) return; // Skip wrapping event listeners or attributes
418
+ // // // 3. Narrowing for Signal Calls: count()
419
+ // // // const isSignalCall = t.isCallExpression(node);
420
+ // // const isSignalCall = false;
421
+ // // // 4. Narrowing for Member Access: item.$name
422
+ // // let isReactiveMember = false;
423
+ // // if (t.isMemberExpression(node)) {
424
+ // // const property = node.property;
425
+ // // // Use Type Guard to narrow from Expression | PrivateName to Identifier
426
+ // // if (t.isIdentifier(property)) {
427
+ // // if (property.name.startsWith('$')) {
428
+ // // (property as any).name = property.name.replace('$', '');
429
+ // // console.log('node', node);
430
+ // // state.needsLitrxImport = true;
431
+ // // isReactiveMember = true;
432
+ // // }
433
+ // // }
434
+ // // }
435
+ // // if (isSignalCall || isReactiveMember) {
436
+ // // // Transform: ${exp} -> ${watch(() => exp)}
437
+ // // expPath.replaceWith(
438
+ // // t.callExpression(t.identifier('litrx'), [
439
+ // // t.arrowFunctionExpression([], node as t.Expression),
440
+ // // ])
441
+ // // );
442
+ // // }
443
+ // // });
444
+ // // path
445
+ // // .get('quasi')
446
+ // // .get('expressions')
447
+ // if (t.isTaggedTemplateExpression(path.node)) {
448
+ // expressions.forEach((expPath, index) => {
449
+ // const node = expPath.node;
450
+ // if (!node || t.isJSXEmptyExpression(node)) return;
451
+ // // 1. Skip Attributes (@click, id=, etc)
452
+ // if (t.isTaggedTemplateExpression(path.node)) {
453
+ // const { tag, quasi } = path.node;
454
+ // const prevString = quasi.quasis[index].value.raw;
455
+ // if (
456
+ // prevString.trim().endsWith('=') ||
457
+ // prevString.match(/@[\w-]+$/)
458
+ // ) {
459
+ // return;
460
+ // }
461
+ // }
462
+ // let shouldWrap = false;
463
+ // let newNode: t.Expression = node as t.Expression;
464
+ // let property: t.Expression;
465
+ // // 2. Handle Member Expressions (e.g., user.$name)
466
+ // if (t.isMemberExpression(node) && t.isIdentifier(node.property)) {
467
+ // const propName = node.property.name;
468
+ // property = node.property;
469
+ // // Check where the object (e.g., 'user') came from
470
+ // const objectName = t.isIdentifier(node.object)
471
+ // ? node.object.name
472
+ // : null;
473
+ // const binding = objectName
474
+ // ? expPath.scope.getBinding(objectName)
475
+ // : null;
476
+ // // Identify if the source is one of our reactive hooks
477
+ // const isReactiveSource =
478
+ // binding &&
479
+ // t.isVariableDeclarator(binding.path.node) &&
480
+ // t.isCallExpression(binding.path.node.init) &&
481
+ // t.isIdentifier(binding.path.node.init.callee) &&
482
+ // ['useReducer', 'createStore', 'useContext'].includes(
483
+ // binding.path.node.init.callee.name
484
+ // );
485
+ // if (isReactiveSource || propName.startsWith('$')) {
486
+ // shouldWrap = true;
487
+ // state.needsLitrxImport = true;
488
+ // console.log('nnedd wrap');
489
+ // // TRANSFORM: Strip the '$' for the runtime Proxy access
490
+ // // user.$name -> user.name
491
+ // }
492
+ // if (propName.startsWith('$')) {
493
+ // if (property) {
494
+ // (property as any).name = propName.replace('$', '');
495
+ // newNode = t.memberExpression(node.object, property);
496
+ // }
497
+ // }
498
+ // }
499
+ // // // 3. Handle Signal Calls: count()
500
+ // // if (t.isCallExpression(node)) {
501
+ // // shouldWrap = true;
502
+ // // }
503
+ // if (shouldWrap) {
504
+ // state.needsLitrxImport = true;
505
+ // expPath.replaceWith(
506
+ // t.callExpression(t.identifier('litrx'), [
507
+ // t.arrowFunctionExpression([], newNode),
508
+ // ])
509
+ // );
510
+ // }
511
+ // });
512
+ // } else {
513
+ // console.log('jsx', expressions);
514
+ // }
515
+ // },
516
+ },
517
+ };
518
+ });
519
+ function needReativeTransform(node, expPath) {
520
+ if (t.isMemberExpression(node) && t.isIdentifier(node.property)) {
521
+ // const propName = node.property.name;
522
+ // property = node.property;
523
+ // Check where the object (e.g., 'user') came from
524
+ const objectName = t.isIdentifier(node.object) ? node.object.name : null;
525
+ const binding = objectName ? expPath.scope.getBinding(objectName) : null;
526
+ // Identify if the source is one of our reactive hooks
527
+ const isReactiveSource = binding &&
528
+ t.isVariableDeclarator(binding.path.node) &&
529
+ t.isCallExpression(binding.path.node.init) &&
530
+ t.isIdentifier(binding.path.node.init.callee) &&
531
+ ['useReducer', 'createStore', 'useContext', 'useLocation'].includes(binding.path.node.init.callee.name);
532
+ return isReactiveSource || false;
533
+ }
534
+ return false;
535
+ }
536
+ // ============================================================================
537
+ // JSX Transformation
538
+ // ============================================================================
539
+ function transformJSXElement(element, state, options) {
540
+ const { runtime, pragma, development, autoShow = true } = options;
541
+ if (runtime === 'automatic') {
542
+ return transformAutomaticElement(element, state, development, autoShow);
543
+ }
544
+ else {
545
+ return transformClassicElement(element, state, pragma);
546
+ }
547
+ }
548
+ // ============================================================================
549
+ // Imperative backend emission
550
+ //
551
+ // Emits direct calls into the @fluixi/dom runtime instead of jsx()/jsxs():
552
+ // native element -> (() => { const _el = createNativeElement(tag);
553
+ // spread({ element: _el, props });
554
+ // insert(_el, children);
555
+ // return _el; })()
556
+ // component -> createMemo(() => createComponent(Comp, props))
557
+ // This matches @fluixi/jsx's component semantics exactly while removing
558
+ // the per-render jsx() dispatch for host elements. Targets RUNTIME_CONTRACT_V1.
559
+ // ============================================================================
560
+ // Build-time SVG detection, kept in lockstep with isSVGElement in the dom runtime.
561
+ const SVG_TAGS = new Set([
562
+ 'svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline',
563
+ 'ellipse', 'g', 'defs', 'clipPath', 'text',
564
+ ]);
565
+ /** Build the props object, merging spreads via mergeProps (reactivity-preserving). */
566
+ function buildPropsObject(props, spreads, state) {
567
+ if (spreads.length > 0) {
568
+ state.needsMergeProps = true;
569
+ return t.callExpression(t.identifier('mergeProps'), [
570
+ ...spreads,
571
+ t.objectExpression(props),
572
+ ]);
573
+ }
574
+ return t.objectExpression(props);
575
+ }
576
+ /** Wrap children expressions into a single value for `insert` / `children` prop. */
577
+ function childrenValue(children) {
578
+ return children.length === 1 ? children[0] : t.arrayExpression(children);
579
+ }
580
+ function emitImperativeElement(tag, isNative, props, spreads, children, state) {
581
+ if (!isNative) {
582
+ // Component: children go into props as a LAZY getter, so child components are
583
+ // created inside the component's execution (owner nesting for context/Suspense),
584
+ // after the component runs (provider ordering), and lazily (ErrorBoundary catch).
585
+ const allProps = [...props];
586
+ if (children.length > 0) {
587
+ allProps.push(t.objectMethod('get', t.identifier('children'), [], t.blockStatement([t.returnStatement(childrenValue(children))])));
588
+ }
589
+ return emitComponentCall(tag, buildPropsObject(allProps, spreads, state), state);
590
+ }
591
+ // Native element: createNativeElement + spread (attrs only) + insert (children).
592
+ const tagName = tag.value;
593
+ const isSVG = SVG_TAGS.has(tagName);
594
+ const elId = t.identifier('_el$');
595
+ const stmts = [];
596
+ state.needsCreateNativeElement = true;
597
+ const createArgs = [tag];
598
+ if (isSVG)
599
+ createArgs.push(t.booleanLiteral(true));
600
+ stmts.push(t.variableDeclaration('const', [
601
+ t.variableDeclarator(elId, t.callExpression(t.identifier('createNativeElement'), createArgs)),
602
+ ]));
603
+ if (props.length > 0 || spreads.length > 0) {
604
+ state.needsSpread = true;
605
+ const spreadProps = [
606
+ t.objectProperty(t.identifier('element'), elId),
607
+ t.objectProperty(t.identifier('props'), buildPropsObject(props, spreads, state)),
608
+ ];
609
+ if (isSVG) {
610
+ spreadProps.push(t.objectProperty(t.identifier('isSVG'), t.booleanLiteral(true)));
611
+ }
612
+ stmts.push(t.expressionStatement(t.callExpression(t.identifier('spread'), [t.objectExpression(spreadProps)])));
613
+ }
614
+ // Insert each child individually (like the jsx runtime's applyProps), so a
615
+ // reactive/component child that isn't the only child still gets its own
616
+ // reactive binding. A single insert(el, [array]) does NOT bind function
617
+ // elements sitting inside the array.
618
+ if (children.length > 0) {
619
+ state.needsInsert = true;
620
+ for (const child of children) {
621
+ const isFn = t.isArrowFunctionExpression(child) ||
622
+ t.isFunctionExpression(child) ||
623
+ (t.isCallExpression(child) &&
624
+ t.isIdentifier(child.callee) &&
625
+ child.callee.name === 'createMemo');
626
+ const args = isFn
627
+ ? [t.cloneNode(elId), child, t.nullLiteral()]
628
+ : [t.cloneNode(elId), child];
629
+ stmts.push(t.expressionStatement(t.callExpression(t.identifier('insert'), args)));
630
+ }
631
+ }
632
+ stmts.push(t.returnStatement(elId));
633
+ return t.callExpression(t.arrowFunctionExpression([], t.blockStatement(stmts)), []);
634
+ }
635
+ /**
636
+ * Emit a component invocation. jsx backend: `jsx(Comp, props)`. Imperative:
637
+ * `createMemo(() => createComponent(Comp, props))` — same shape jsx-runtime
638
+ * produces for components, so it stays reactive when used as a child.
639
+ */
640
+ function emitComponentCall(comp, propsObj, state) {
641
+ if (state.backend === 'imperative') {
642
+ state.needsCreateComponent = true;
643
+ state.needsCreateMemo = true;
644
+ return t.callExpression(t.identifier('createMemo'), [
645
+ t.arrowFunctionExpression([], t.callExpression(t.identifier('createComponent'), [comp, propsObj])),
646
+ ]);
647
+ }
648
+ return t.callExpression(t.identifier('jsx'), [comp, propsObj]);
649
+ }
650
+ /**
651
+ * IR-pipeline path (codegen: 'ir'): build host-agnostic IR from the JSX node, run
652
+ * the imperative backend's emit(), and reparse the emitted code into an AST
653
+ * expression. Records the runtime symbols used so Program.exit can import them.
654
+ * Produces the same runtime calls as the inline path — this is the route a future
655
+ * swc front-end reuses.
656
+ */
657
+ function emitViaIR(node, state) {
658
+ // `used` collects runtime symbols emitted for JSX nested inside expressions
659
+ // (render-props, ternaries, fallback={<p/>}, …) so we import them too.
660
+ const used = new Set();
661
+ const ir = buildIR(node, used);
662
+ const { code, imports } = imperativeBackend.emit(ir, {});
663
+ for (const s of imports)
664
+ used.add(s);
665
+ for (const s of used)
666
+ state.irImports.add(s);
667
+ return template.expression(code, { placeholderPattern: false })();
668
+ }
669
+ //This method will convert props to lazy props like
670
+ function lazyProps(properties) {
671
+ return properties.map((prop) => {
672
+ // Only transform normal properties (e.g., { open: variable })
673
+ // Skip methods, spread elements, or already existing getters
674
+ if (t.isObjectProperty(prop)) {
675
+ // Handle potential computed properties (like {[someKey]: variable})
676
+ const key = prop.key;
677
+ const computed = prop.computed;
678
+ const value = prop.value;
679
+ // Create a getter method: get key() { return value; }
680
+ return t.objectMethod('get', // Kind of method
681
+ key, // Property key
682
+ [], // Getters take no arguments
683
+ t.blockStatement([
684
+ // Body: { return value; }
685
+ t.returnStatement(value),
686
+ ]), computed // Preserve computed status if true
687
+ );
688
+ }
689
+ // Keep existing ObjectMethods (like get already there, or regular methods) as-is
690
+ return prop;
691
+ });
692
+ }
693
+ ;
694
+ function transformAutomaticElement(element, state, development, autoShow = true) {
695
+ const openingElement = element.openingElement;
696
+ const children = element.children;
697
+ // Get element name/tag
698
+ const tag = getElementName(openingElement.name);
699
+ // Only auto-wrap reactive expressions for native HTML elements (lowercase tag).
700
+ // Component props must NOT be wrapped — the component receives the value
701
+ // as-is and is responsible for its own reactivity.
702
+ const isNativeElement = t.isJSXIdentifier(openingElement.name) &&
703
+ /^[a-z]/.test(openingElement.name.name);
704
+ // Transform attributes to props object
705
+ const attrs = transformAttributes(openingElement.attributes, state, isNativeElement, t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '');
706
+ let props = attrs.props;
707
+ const spreads = attrs.spreads;
708
+ // Component props become lazy getters so the component is created ONCE and reactivity flows
709
+ // through the getter (a reactive `prop={sig()}` read eagerly would re-create the whole
710
+ // component via the enclosing createMemo, so a <Portal> re-mounts and piles up). Static props
711
+ // as getters are harmless. Native-element attrs must NOT be wrapped: they already carry their
712
+ // own reactivity as `() => expr` thunks that `spread` consumes.
713
+ if (!isNativeElement)
714
+ props = lazyProps(props);
715
+ // Transform children
716
+ const transformedChildren = transformChildren(children, state, autoShow);
717
+ // Imperative backend: emit direct runtime calls instead of jsx()/jsxs().
718
+ if (state.backend === 'imperative') {
719
+ return emitImperativeElement(tag, isNativeElement, props, spreads, transformedChildren, state);
720
+ }
721
+ // Add children to props if any
722
+ if (transformedChildren.length > 0) {
723
+ const childrenProp = transformedChildren.length === 1
724
+ ? transformedChildren[0]
725
+ : t.arrayExpression(transformedChildren);
726
+ props.push(t.objectProperty(t.identifier('children'), childrenProp));
727
+ }
728
+ // Build props object with spreads
729
+ let propsObj;
730
+ if (spreads.length > 0) {
731
+ // Use mergeProps helper to preserve reactivity for store proxies
732
+ const baseProps = props.length > 0 ? t.objectExpression(props) : t.objectExpression([]);
733
+ // Import mergeProps from jsx-runtime if spreads are used
734
+ state.needsMergeProps = true;
735
+ propsObj = t.callExpression(t.identifier('mergeProps'), [
736
+ ...spreads,
737
+ baseProps,
738
+ ]);
739
+ }
740
+ else {
741
+ propsObj = t.objectExpression(props);
742
+ }
743
+ // Determine function name
744
+ const functionName = development
745
+ ? 'jsxDEV'
746
+ : transformedChildren.length > 1
747
+ ? 'jsxs'
748
+ : 'jsx';
749
+ // Build jsx call
750
+ const args = [tag, propsObj];
751
+ if (development) {
752
+ // Add key, isStaticChildren, source, self for dev mode
753
+ args.push(t.identifier('undefined'), // key
754
+ t.booleanLiteral(false), // isStaticChildren
755
+ t.identifier('undefined'), // source
756
+ t.identifier('undefined') // self
757
+ );
758
+ }
759
+ return t.callExpression(t.identifier(functionName), args);
760
+ }
761
+ function transformClassicElement(element, state, pragma) {
762
+ const openingElement = element.openingElement;
763
+ const children = element.children;
764
+ const tag = getElementName(openingElement.name);
765
+ const isNativeElement = t.isJSXIdentifier(openingElement.name) &&
766
+ /^[a-z]/.test(openingElement.name.name);
767
+ const { props, spreads } = transformAttributes(openingElement.attributes, state, isNativeElement, t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '');
768
+ const transformedChildren = transformChildren(children, state, state.autoShow ?? true);
769
+ let propsObj;
770
+ if (spreads.length > 0) {
771
+ // Use mergeProps helper to preserve reactivity for store proxies
772
+ // mergeProps(...spreads, baseProps) instead of Object.assign
773
+ const baseProps = props.length > 0 ? t.objectExpression(props) : t.objectExpression([]);
774
+ // Import mergeProps from jsx-runtime if spreads are used
775
+ state.needsMergeProps = true;
776
+ propsObj = t.callExpression(t.identifier('mergeProps'), [
777
+ ...spreads,
778
+ baseProps,
779
+ ]);
780
+ }
781
+ else {
782
+ propsObj = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
783
+ }
784
+ const args = [tag, propsObj, ...transformedChildren];
785
+ return t.callExpression(t.identifier(pragma), args);
786
+ }
787
+ function transformJSXFragment(fragment, state, options) {
788
+ const { runtime, pragmaFrag, development } = options;
789
+ const children = transformChildren(fragment.children, state, state.autoShow ?? true);
790
+ // Imperative backend: a fragment is just its children; insert() flattens arrays.
791
+ if (state.backend === 'imperative') {
792
+ if (children.length === 0)
793
+ return t.nullLiteral();
794
+ return childrenValue(children);
795
+ }
796
+ if (runtime === 'automatic') {
797
+ const functionName = development
798
+ ? 'jsxDEV'
799
+ : children.length > 1
800
+ ? 'jsxs'
801
+ : 'jsx';
802
+ const props = t.objectExpression([
803
+ t.objectProperty(t.identifier('children'), children.length === 1 ? children[0] : t.arrayExpression(children)),
804
+ ]);
805
+ const args = [t.identifier('Fragment'), props];
806
+ if (development) {
807
+ args.push(t.identifier('undefined'), t.booleanLiteral(false), t.identifier('undefined'), t.identifier('undefined'));
808
+ }
809
+ return t.callExpression(t.identifier(functionName), args);
810
+ }
811
+ else {
812
+ return t.callExpression(t.identifier(pragmaFrag), children);
813
+ }
814
+ }
815
+ // ============================================================================
816
+ // Element Name Transformation
817
+ // ============================================================================
818
+ function getElementName(name) {
819
+ if (t.isJSXIdentifier(name)) {
820
+ const nameStr = name.name;
821
+ // Check if it's a native element (lowercase)
822
+ if (nameStr[0] === nameStr[0].toLowerCase()) {
823
+ return t.stringLiteral(nameStr);
824
+ }
825
+ // Component reference
826
+ return t.identifier(nameStr);
827
+ }
828
+ if (t.isJSXMemberExpression(name)) {
829
+ return transformJSXMemberExpression(name);
830
+ }
831
+ if (t.isJSXNamespacedName(name)) {
832
+ return t.stringLiteral(`${name.namespace.name}:${name.name.name}`);
833
+ }
834
+ return t.stringLiteral('div');
835
+ }
836
+ function transformJSXMemberExpression(expr) {
837
+ let object;
838
+ if (t.isJSXIdentifier(expr.object)) {
839
+ object = t.identifier(expr.object.name);
840
+ }
841
+ else {
842
+ object = transformJSXMemberExpression(expr.object);
843
+ }
844
+ return t.memberExpression(object, t.identifier(expr.property.name));
845
+ }
846
+ // ============================================================================
847
+ // Attribute Transformation
848
+ // ============================================================================
849
+ /** Returns true when `name` can be used as an unquoted JS identifier. */
850
+ function isValidJSIdentifier(name) {
851
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
852
+ }
853
+ function transformAttributes(attributes, state, isNativeElement = true, tagName = '') {
854
+ const props = [];
855
+ const spreads = [];
856
+ const cfValueProp = CONTROL_FLOW_VALUE_PROPS[tagName];
857
+ for (const attr of attributes) {
858
+ if (t.isJSXSpreadAttribute(attr)) {
859
+ // Collect spread expressions separately
860
+ spreads.push(attr.argument);
861
+ }
862
+ else if (t.isJSXAttribute(attr)) {
863
+ const name = getAttributeName(attr.name);
864
+ const value = transformAttributeValue(attr.value, state, name, isNativeElement);
865
+ // Detect event handlers for delegation
866
+ if (EVENT_PROPS.test(name)) {
867
+ const eventName = name.slice(2).toLowerCase();
868
+ if (DELEGATABLE_EVENTS.includes(eventName)) {
869
+ state.delegatedEvents.add(eventName);
870
+ }
871
+ }
872
+ const key = isValidJSIdentifier(name)
873
+ ? t.identifier(name)
874
+ : t.stringLiteral(name);
875
+ // Inline JSX passed to a COMPONENT prop (e.g. <Suspense fallback={<div/>}>) becomes a
876
+ // LAZY getter — same as children. Otherwise it's an eager IIFE built when the props object
877
+ // is constructed; during hydration that walks the hydration cursor and adopts the real
878
+ // server node (a fallback `<div>` matches the content `<div>` by tag), duplicating the
879
+ // tree. As a getter the node is only built when the component actually reads the prop.
880
+ const raw = attr.value;
881
+ const isInlineJsx = t.isJSXElement(raw) ||
882
+ t.isJSXFragment(raw) ||
883
+ (t.isJSXExpressionContainer(raw) &&
884
+ (t.isJSXElement(raw.expression) || t.isJSXFragment(raw.expression)));
885
+ // Control-flow reactive value prop (each/when/component/mount): emit as a getter so
886
+ // reading it re-runs the caller's expression inside the component's tracking scope.
887
+ const isCfValueProp = !isNativeElement && name === cfValueProp;
888
+ // A reactive component prop (`prop={sig()}`, `prop={store.x}`) must be a getter, not an
889
+ // eager value: eager reads the signal inside the enclosing `createMemo(() =>
890
+ // createComponent(Comp, props))`, so the memo re-creates the WHOLE component on every
891
+ // change (a <Portal> then re-mounts and piles up). As a getter the component is created
892
+ // once and reactivity flows through the getter into the component's own effects.
893
+ const rawExpr = t.isJSXExpressionContainer(raw) && !t.isJSXEmptyExpression(raw.expression)
894
+ ? raw.expression
895
+ : null;
896
+ const isReactiveComponentProp = !isNativeElement && !!rawExpr && shouldAutoWrapAttrExpression(rawExpr, name);
897
+ if ((!isNativeElement && isInlineJsx) || isCfValueProp || isReactiveComponentProp) {
898
+ props.push(t.objectMethod('get', key, [], t.blockStatement([t.returnStatement(value)])));
899
+ }
900
+ else {
901
+ props.push(t.objectProperty(key, value));
902
+ }
903
+ }
904
+ }
905
+ return { props, spreads };
906
+ }
907
+ function getAttributeName(name) {
908
+ if (t.isJSXIdentifier(name)) {
909
+ return name.name === 'class' ? 'className' : name.name;
910
+ }
911
+ if (t.isJSXNamespacedName(name)) {
912
+ return `${name.namespace.name}:${name.name.name}`;
913
+ }
914
+ return 'unknown';
915
+ }
916
+ function transformAttributeValue(value, state, attrName = '', isNativeElement = true) {
917
+ if (value === null) {
918
+ return t.booleanLiteral(true);
919
+ }
920
+ if (t.isStringLiteral(value)) {
921
+ // JSX allows string attribute values to span multiple lines, but JS string
922
+ // literals cannot contain raw newlines. When multi-line, normalise: collapse
923
+ // any run of whitespace (including newlines) to a single space and trim.
924
+ // We create a *new* StringLiteral so Babel discards the original extra.raw
925
+ // (which still contains the literal newlines) and generates a clean literal.
926
+ if (value.value.includes('\n')) {
927
+ return t.stringLiteral(value.value.replace(/\s+/g, ' ').trim());
928
+ }
929
+ return value;
930
+ }
931
+ if (t.isJSXExpressionContainer(value)) {
932
+ if (t.isJSXEmptyExpression(value.expression)) {
933
+ return t.booleanLiteral(true);
934
+ }
935
+ const expr = value.expression;
936
+ // Auto-wrap reactive expressions so DOM attribute reads happen inside effects.
937
+ // Only applies to native HTML elements — component props must NOT be wrapped
938
+ // because the component receives the value directly and calling it would give
939
+ // () => value instead of value.
940
+ if (isNativeElement && shouldAutoWrapAttrExpression(expr, attrName)) {
941
+ return t.arrowFunctionExpression([], expr);
942
+ }
943
+ return expr;
944
+ }
945
+ if (t.isJSXElement(value)) {
946
+ return transformAutomaticElement(value, state, false, state.autoShow ?? true);
947
+ }
948
+ if (t.isJSXFragment(value)) {
949
+ return transformJSXFragment(value, state, {
950
+ runtime: 'automatic',
951
+ pragmaFrag: 'Fragment',
952
+ development: false,
953
+ });
954
+ }
955
+ return t.booleanLiteral(true);
956
+ }
957
+ /**
958
+ * Decide whether a JSX attribute value expression should be auto-wrapped
959
+ * in `() => expr` for fine-grained reactivity (SolidJS style).
960
+ */
961
+ function shouldAutoWrapAttrExpression(expr, attrName) {
962
+ // Event handlers must NOT be wrapped — they are called by the runtime.
963
+ // Covers both camelCase (onClick) and namespaced (on:click) forms.
964
+ if (EVENT_PROPS.test(attrName) || attrName.startsWith('on:')) {
965
+ return false;
966
+ }
967
+ // ref receives the DOM element directly — never wrap.
968
+ if (attrName === 'ref') {
969
+ return false;
970
+ }
971
+ // use:Directive — the directive itself decides how to handle the value.
972
+ if (attrName.startsWith('use:')) {
973
+ return false;
974
+ }
975
+ // Already an arrow function or function expression — already reactive.
976
+ if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
977
+ return false;
978
+ }
979
+ // Plain identifier — may itself be a signal accessor/function; let runtime handle it.
980
+ if (t.isIdentifier(expr)) {
981
+ return false;
982
+ }
983
+ // Static literals need no wrapping.
984
+ if (t.isStringLiteral(expr) ||
985
+ t.isNumericLiteral(expr) ||
986
+ t.isBooleanLiteral(expr) ||
987
+ t.isNullLiteral(expr)) {
988
+ return false;
989
+ }
990
+ // Call expressions: signal(), count(), store.method()
991
+ if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) {
992
+ return true;
993
+ }
994
+ // Member expressions: store.prop, obj.value
995
+ if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
996
+ return true;
997
+ }
998
+ // Compound expressions that may contain reactive reads.
999
+ if (t.isLogicalExpression(expr) ||
1000
+ t.isConditionalExpression(expr) ||
1001
+ t.isBinaryExpression(expr) ||
1002
+ t.isUnaryExpression(expr)) {
1003
+ return true;
1004
+ }
1005
+ // Template literals: `Hello ${name()}`
1006
+ if (t.isTemplateLiteral(expr)) {
1007
+ return true;
1008
+ }
1009
+ // Object expressions: classList={{ active: isActive() }}
1010
+ if (t.isObjectExpression(expr)) {
1011
+ return true;
1012
+ }
1013
+ // Array expressions: style={[baseStyle, dynamicStyle()]}
1014
+ if (t.isArrayExpression(expr)) {
1015
+ return true;
1016
+ }
1017
+ return false;
1018
+ }
1019
+ // ============================================================================
1020
+ // Children Transformation
1021
+ // ============================================================================
1022
+ /**
1023
+ * Canonical JSX text cleaning (matches babel/React/Solid): trim line-boundary
1024
+ * whitespace, collapse runs to single spaces, and drop text that is only
1025
+ * whitespace spanning a newline. Returns '' when the node is insignificant.
1026
+ */
1027
+ function cleanJSXText(value) {
1028
+ const lines = value.split(/\r\n|\n|\r/);
1029
+ let lastNonEmpty = 0;
1030
+ for (let i = 0; i < lines.length; i++) {
1031
+ if (/[^ \t]/.test(lines[i]))
1032
+ lastNonEmpty = i;
1033
+ }
1034
+ let out = '';
1035
+ for (let i = 0; i < lines.length; i++) {
1036
+ let line = lines[i].replace(/\t/g, ' ');
1037
+ if (i !== 0)
1038
+ line = line.replace(/^ +/, '');
1039
+ if (i !== lines.length - 1)
1040
+ line = line.replace(/ +$/, '');
1041
+ if (!line)
1042
+ continue;
1043
+ if (i !== lastNonEmpty)
1044
+ line += ' ';
1045
+ out += line;
1046
+ }
1047
+ return out;
1048
+ }
1049
+ function transformChildren(children, state, autoShow = true) {
1050
+ const result = [];
1051
+ for (const child of children) {
1052
+ if (t.isJSXText(child)) {
1053
+ // Canonical JSX whitespace handling (as React/Solid): whitespace-only text
1054
+ // that spans a line break is insignificant and dropped; inline runs collapse
1055
+ // to a single space. This matters beyond cosmetics — keeping a `" "` text
1056
+ // node next to a child turns `props.children` into an ARRAY, and an array
1057
+ // insert doesn't reactively bind a memo/component sitting inside it (so a
1058
+ // nested lazy/<Suspense> never updates). Dropping the stray whitespace keeps
1059
+ // a lone child a lone child, so insert binds it reactively.
1060
+ const text = cleanJSXText(child.value);
1061
+ if (text)
1062
+ result.push(t.stringLiteral(text));
1063
+ }
1064
+ else if (t.isJSXExpressionContainer(child)) {
1065
+ if (!t.isJSXEmptyExpression(child.expression)) {
1066
+ const expr = child.expression;
1067
+ // Auto-transform patterns into jsx(Show, ...) so JSX subtrees are
1068
+ // created once and never recreated on every signal change.
1069
+ //
1070
+ // Handled patterns:
1071
+ // () => cond && <JSX /> — arrow logical
1072
+ // () => cond ? <JSX /> : fallback — arrow ternary
1073
+ // cond && <JSX /> — bare logical
1074
+ // cond ? <JSX /> : fallback — bare ternary
1075
+ if (autoShow) {
1076
+ const showNode = tryTransformArrowToShow(expr, state) ??
1077
+ tryTransformRawToShow(expr, state);
1078
+ if (showNode) {
1079
+ result.push(showNode);
1080
+ continue;
1081
+ }
1082
+ }
1083
+ else {
1084
+ // autoShow off: wrap JSX conditional expressions in createMemo(() => expr)
1085
+ // so the condition is still evaluated reactively without recreating the tree.
1086
+ const memoNode = tryWrapConditionalAsMemo(expr, state);
1087
+ if (memoNode) {
1088
+ result.push(memoNode);
1089
+ continue;
1090
+ }
1091
+ }
1092
+ // Auto-wrap reactive expressions for SolidJS-style reactivity
1093
+ if (shouldAutoWrapExpression(expr)) {
1094
+ result.push(t.arrowFunctionExpression([], expr));
1095
+ }
1096
+ else {
1097
+ result.push(expr);
1098
+ }
1099
+ }
1100
+ }
1101
+ else if (t.isJSXElement(child)) {
1102
+ result.push(transformAutomaticElement(child, state, false, state.autoShow ?? true));
1103
+ }
1104
+ else if (t.isJSXFragment(child)) {
1105
+ result.push(transformJSXFragment(child, state, {
1106
+ runtime: 'automatic',
1107
+ pragmaFrag: 'Fragment',
1108
+ development: false,
1109
+ }));
1110
+ }
1111
+ else if (t.isJSXSpreadChild(child)) {
1112
+ result.push(child.expression);
1113
+ }
1114
+ }
1115
+ return result;
1116
+ }
1117
+ // ============================================================================
1118
+ // Auto-Show Transform
1119
+ // ============================================================================
1120
+ /**
1121
+ * Detects the patterns:
1122
+ * () => condition && <JSX />
1123
+ * () => condition ? <JSX /> : null
1124
+ * () => condition ? <JSX /> : <Fallback />
1125
+ *
1126
+ * and rewrites them to:
1127
+ * jsx(Show, { when: () => condition, children: jsx(...), fallback? })
1128
+ *
1129
+ * This prevents the JSX subtree from being recreated on every reactive update.
1130
+ * Show's memo only re-evaluates when the boolean result of `condition` changes.
1131
+ */
1132
+ function tryTransformArrowToShow(expr, state) {
1133
+ if (!t.isArrowFunctionExpression(expr) ||
1134
+ expr.params.length !== 0 ||
1135
+ expr.async) {
1136
+ return null;
1137
+ }
1138
+ const body = expr.body;
1139
+ // Pattern 1: () => condition && <JSX /> (or () => condition && (() => <JSX />))
1140
+ if (t.isLogicalExpression(body) &&
1141
+ body.operator === '&&') {
1142
+ const jsx = getJSXContent(body.right);
1143
+ if (jsx) {
1144
+ return buildShowCall(unwrapZeroArgArrow(body.left), jsx, null, state);
1145
+ }
1146
+ }
1147
+ // Pattern 2: () => condition ? <JSX /> : null | undefined | false
1148
+ // Pattern 3: () => condition ? <JSX /> : <Fallback />
1149
+ // Pattern 4: () => condition ? <JSX /> : otherCond ? <JSX2 /> : <Fallback /> (nested ternary)
1150
+ if (t.isConditionalExpression(body)) {
1151
+ const jsx = getJSXContent(body.consequent);
1152
+ if (jsx) {
1153
+ const fallback = buildFallback(body.alternate, state);
1154
+ return buildShowCall(unwrapZeroArgArrow(body.test), jsx, fallback, state, true);
1155
+ }
1156
+ }
1157
+ return null;
1158
+ }
1159
+ /**
1160
+ * Handles bare (non-arrow-wrapped) ternary and logical JSX expressions:
1161
+ *
1162
+ * cond && <JSX />
1163
+ * cond ? <JSX /> : fallback
1164
+ *
1165
+ * These are transformed to `jsx(Show, { when: () => cond, ... })` exactly like
1166
+ * their arrow-wrapped counterparts, preventing JSX subtrees from being
1167
+ * recreated on every reactive update.
1168
+ */
1169
+ function tryTransformRawToShow(expr, state) {
1170
+ // cond && <JSX /> (or cond && (() => <JSX />))
1171
+ if (t.isLogicalExpression(expr) && expr.operator === '&&') {
1172
+ const jsx = getJSXContent(expr.right);
1173
+ if (jsx) {
1174
+ return buildShowCall(unwrapZeroArgArrow(expr.left), jsx, null, state);
1175
+ }
1176
+ }
1177
+ // cond ? <JSX /> : fallback (consequent may be () => <JSX />)
1178
+ // test may be (() => expr) — unwrap to avoid double arrow wrapping
1179
+ if (t.isConditionalExpression(expr)) {
1180
+ const jsx = getJSXContent(expr.consequent);
1181
+ if (jsx) {
1182
+ const fallback = buildFallback(expr.alternate, state);
1183
+ return buildShowCall(unwrapZeroArgArrow(expr.test), jsx, fallback, state);
1184
+ }
1185
+ }
1186
+ return null;
1187
+ }
1188
+ /**
1189
+ * When autoShow is disabled, wraps ALL ternary and logical-AND expressions in
1190
+ * `createMemo(() => normalizedExpr)` so the condition evaluates reactively and
1191
+ * the result is cached (not re-evaluated on every render cycle).
1192
+ *
1193
+ * Normalization rules applied before wrapping:
1194
+ * - Outer zero-arg arrow unwrapped: `() => cond ? a : b` → body used directly
1195
+ * - Arrow-wrapped test unwrapped: `(() => cond) ? a : b` → `cond ? a : b`
1196
+ * - Arrow-wrapped JSX consequent: `() => <JSX>` → `<JSX>` (so Babel transforms it)
1197
+ * - Arrow-wrapped JSX alternate: `() => <JSX>` → `<JSX>` likewise
1198
+ * - Non-JSX branches left as-is: strings, identifiers, call results, etc.
1199
+ *
1200
+ * Examples:
1201
+ * {cond ? "a" : "b"} → createMemo(() => cond ? "a" : "b")
1202
+ * {cond ? <A/> : <B/>} → createMemo(() => cond ? <A/> : <B/>)
1203
+ * {(() => cond) ? ()=><A/> : <B/>} → createMemo(() => cond ? <A/> : <B/>)
1204
+ * {cond && <A/>} → createMemo(() => cond && <A/>)
1205
+ */
1206
+ function tryWrapConditionalAsMemo(expr, state) {
1207
+ // Unwrap outer zero-arg arrow: () => <inner> → work on inner directly
1208
+ const inner = t.isArrowFunctionExpression(expr) &&
1209
+ expr.params.length === 0 &&
1210
+ !expr.async &&
1211
+ t.isExpression(expr.body)
1212
+ ? expr.body
1213
+ : expr;
1214
+ if (t.isConditionalExpression(inner)) {
1215
+ // Unwrap arrow-wrapped test to prevent always-truthy condition
1216
+ const normalizedTest = unwrapZeroArgArrow(inner.test);
1217
+ // Unwrap arrow-wrapped JSX branches so Babel can transform them;
1218
+ // non-JSX branches (strings, calls, etc.) are kept as-is
1219
+ const consequentJsx = getJSXContent(inner.consequent);
1220
+ const normalizedConsequent = consequentJsx ?? inner.consequent;
1221
+ const altJsx = getJSXContent(inner.alternate);
1222
+ const normalizedAlternate = altJsx ?? inner.alternate;
1223
+ const normalized = t.conditionalExpression(normalizedTest, normalizedConsequent, normalizedAlternate);
1224
+ // If either branch is a control-flow component (For, Switch, Index, Show, …),
1225
+ // do NOT wrap in createMemo — return null so the caller falls through to the
1226
+ // plain `() => expr` path via shouldAutoWrapExpression.
1227
+ if (isControlFlowJSX(inner.consequent) ||
1228
+ isControlFlowJSX(inner.alternate)) {
1229
+ return null;
1230
+ }
1231
+ state.needsCreateMemo = true;
1232
+ return t.callExpression(t.identifier('createMemo'), [
1233
+ t.arrowFunctionExpression([], normalized),
1234
+ ]);
1235
+ }
1236
+ if (t.isLogicalExpression(inner) && inner.operator === '&&') {
1237
+ const normalizedLeft = unwrapZeroArgArrow(inner.left);
1238
+ const rightJsx = getJSXContent(inner.right);
1239
+ const normalizedRight = rightJsx ?? inner.right;
1240
+ const normalized = t.logicalExpression('&&', normalizedLeft, normalizedRight);
1241
+ // Same guard for logical && (e.g. {show && <For ...>})
1242
+ if (isControlFlowJSX(inner.right)) {
1243
+ return null;
1244
+ }
1245
+ state.needsCreateMemo = true;
1246
+ return t.callExpression(t.identifier('createMemo'), [
1247
+ t.arrowFunctionExpression([], normalized),
1248
+ ]);
1249
+ }
1250
+ return null;
1251
+ }
1252
+ /**
1253
+ * Recursively transforms a fallback expression into a reactive value.
1254
+ * If the expression is a nested ternary `cond ? <JSX /> : rest`, it is
1255
+ * transformed into a nested `Show` call so the condition is tracked reactively.
1256
+ */
1257
+ function buildFallback(expr, state) {
1258
+ if (isFalsyNode(expr))
1259
+ return null;
1260
+ // bare JSX or () => <JSX>
1261
+ const directJsx = getJSXContent(expr);
1262
+ if (directJsx) {
1263
+ return transformJSXNode(directJsx, state);
1264
+ }
1265
+ // Nested ternary whose truthy branch is JSX (or arrow-wrapped JSX)
1266
+ if (t.isConditionalExpression(expr)) {
1267
+ const inner = expr;
1268
+ const innerJsx = getJSXContent(inner.consequent);
1269
+ if (innerJsx) {
1270
+ const nestedFallback = buildFallback(inner.alternate, state);
1271
+ return buildShowCall(unwrapZeroArgArrow(inner.test), innerJsx, nestedFallback, state);
1272
+ }
1273
+ }
1274
+ return expr;
1275
+ }
1276
+ function isJSXNode(node) {
1277
+ return t.isJSXElement(node) || t.isJSXFragment(node);
1278
+ }
1279
+ /**
1280
+ * Returns true when `expr` (or a zero-arg arrow wrapping it) is a JSXElement
1281
+ * whose tag is one of the Fluixi control-flow components (For, Switch, Index,
1282
+ * Show, Portal, Dynamic, ErrorBoundary, Suspense). These must NOT be wrapped in
1283
+ * createMemo — a plain `() => expr` arrow is sufficient and avoids double-tracking.
1284
+ */
1285
+ function isControlFlowJSX(expr) {
1286
+ const jsx = getJSXContent(expr);
1287
+ if (!jsx || !t.isJSXElement(jsx))
1288
+ return false;
1289
+ const name = jsx.openingElement.name;
1290
+ return t.isJSXIdentifier(name) && CONTROL_FLOW_COMPONENTS.has(name.name);
1291
+ }
1292
+ /**
1293
+ * Returns the JSX content from either bare `<JSX>` or a zero-arg arrow `() => <JSX>`.
1294
+ * This allows Show transformation to work regardless of whether the author wrapped the
1295
+ * consequent/alternate in an arrow function or not.
1296
+ */
1297
+ function getJSXContent(expr) {
1298
+ if (isJSXNode(expr))
1299
+ return expr;
1300
+ if (t.isArrowFunctionExpression(expr) &&
1301
+ expr.params.length === 0 &&
1302
+ !expr.async &&
1303
+ isJSXNode(expr.body)) {
1304
+ return expr.body;
1305
+ }
1306
+ return null;
1307
+ }
1308
+ /**
1309
+ * Unwraps a zero-argument arrow `() => expr` to `expr`.
1310
+ * Used to avoid double-wrapping when the test/condition is already `() => cond`.
1311
+ * `buildShowCall` always wraps the condition in `() =>`, so passing a bare expr
1312
+ * produces `() => cond`; passing `() => cond` without unwrapping produces `() => () => cond`
1313
+ * which is always truthy.
1314
+ */
1315
+ function unwrapZeroArgArrow(expr) {
1316
+ if (t.isArrowFunctionExpression(expr) &&
1317
+ expr.params.length === 0 &&
1318
+ !expr.async &&
1319
+ t.isExpression(expr.body)) {
1320
+ return expr.body;
1321
+ }
1322
+ return expr;
1323
+ }
1324
+ function isFalsyNode(node) {
1325
+ return (t.isNullLiteral(node) ||
1326
+ (t.isIdentifier(node) && node.name === 'undefined') ||
1327
+ (t.isBooleanLiteral(node) && node.value === false));
1328
+ }
1329
+ function transformJSXNode(node, state) {
1330
+ if (t.isJSXElement(node)) {
1331
+ return transformAutomaticElement(node, state, false, state.autoShow ?? true);
1332
+ }
1333
+ return transformJSXFragment(node, state, {
1334
+ runtime: 'automatic',
1335
+ pragmaFrag: 'Fragment',
1336
+ development: false,
1337
+ });
1338
+ }
1339
+ function buildShowCall(condition, children, fallback, state, fromArrow = false) {
1340
+ // Register Show so it gets auto-imported if needed
1341
+ state.controlFlowComponents.add('Show');
1342
+ state.needsControlFlowImport = true;
1343
+ const transformedChildren = transformJSXNode(children, state);
1344
+ // console.log('build show call', generate(condition).code);
1345
+ const props = [
1346
+ t.objectProperty(t.identifier('when'), t.arrowFunctionExpression([], condition)
1347
+ // fromArrow ? (condition) : t.arrowFunctionExpression([], condition),
1348
+ ),
1349
+ t.objectProperty(t.identifier('children'), transformedChildren),
1350
+ ];
1351
+ if (fallback) {
1352
+ props.push(t.objectProperty(t.identifier('fallback'), fallback));
1353
+ }
1354
+ return emitComponentCall(t.identifier('Show'), t.objectExpression(props), state);
1355
+ }
1356
+ // ============================================================================
1357
+ // Reactivity Detection
1358
+ // ============================================================================
1359
+ /**
1360
+ * Check if an expression should be auto-wrapped in an arrow function for reactivity
1361
+ * This enables SolidJS-style reactivity where you can write {store.property} or {signal()}
1362
+ * without manual wrapping
1363
+ */
1364
+ function shouldAutoWrapExpression(expr) {
1365
+ // Member expressions: store.property, user.name, etc.
1366
+ // These need wrapping so property access happens inside an effect
1367
+ if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
1368
+ // `props.children` (any `.children`) is a stable subtree reference, not a
1369
+ // reactive value. Wrapping it in `() => props.children` adds an insert layer
1370
+ // that fully unwraps the children's Suspense/memo to a static node, severing
1371
+ // the reactive binding — so a nested lazy/<Suspense> that resolves later
1372
+ // never propagates up through the component-children hop. Pass it through
1373
+ // non-reactive (insert(el, props.children)) so insert binds to the child's
1374
+ // own memo, exactly like a directly-rendered <Outlet/>.
1375
+ if (!expr.computed && t.isIdentifier(expr.property, { name: 'children' })) {
1376
+ return false;
1377
+ }
1378
+ return true;
1379
+ }
1380
+ // Call expressions: signal(), count(), etc.
1381
+ // These are already function calls but need wrapping to re-run on updates
1382
+ if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) {
1383
+ return true;
1384
+ }
1385
+ // DON'T wrap plain identifiers - they might be signal accessors or functions
1386
+ // that should be passed as-is. Only wrap when accessing properties or calling.
1387
+ if (t.isIdentifier(expr)) {
1388
+ return false;
1389
+ }
1390
+ // Logical expressions: signal() && other, store.value || default
1391
+ if (t.isLogicalExpression(expr) || t.isConditionalExpression(expr)) {
1392
+ return true;
1393
+ }
1394
+ // Binary expressions: signal() + 1, store.count * 2
1395
+ if (t.isBinaryExpression(expr)) {
1396
+ return true;
1397
+ }
1398
+ // Unary expressions: !signal(), -store.count
1399
+ if (t.isUnaryExpression(expr)) {
1400
+ return true;
1401
+ }
1402
+ // Template literals: `Count: ${count()}`
1403
+ if (t.isTemplateLiteral(expr)) {
1404
+ return true;
1405
+ }
1406
+ // Don't wrap literals, strings, numbers, etc.
1407
+ return false;
1408
+ }
1409
+ // ============================================================================
1410
+ // Static Optimization
1411
+ // ============================================================================
1412
+ function shouldWrapReactive(expr, state) {
1413
+ // Call expressions ending with () - likely signal accessors
1414
+ if (t.isCallExpression(expr) && t.isIdentifier(expr.callee)) {
1415
+ return true;
1416
+ }
1417
+ // Arrow functions with no parameters - could be computed values
1418
+ if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
1419
+ return true;
1420
+ }
1421
+ // Function expressions with no parameters
1422
+ if (t.isFunctionExpression(expr) && expr.params.length === 0) {
1423
+ return true;
1424
+ }
1425
+ // Member expressions - could be store access
1426
+ if (t.isMemberExpression(expr)) {
1427
+ return true;
1428
+ }
1429
+ return false;
1430
+ }
1431
+ function wrapReactiveExpression(expr) {
1432
+ return t.callExpression(t.identifier(''), [
1433
+ t.arrowFunctionExpression([], expr),
1434
+ ]);
1435
+ }
1436
+ // ============================================================================
1437
+ // Static Element Optimization
1438
+ // ============================================================================
1439
+ function isStaticElement(element, state) {
1440
+ const openingElement = element.openingElement;
1441
+ // Check if tag is static (string literal)
1442
+ if (!t.isJSXIdentifier(openingElement.name)) {
1443
+ return false;
1444
+ }
1445
+ const tagName = openingElement.name.name;
1446
+ if (tagName[0] !== tagName[0].toLowerCase()) {
1447
+ // Component, not a native element
1448
+ return false;
1449
+ }
1450
+ // Check if all attributes are static
1451
+ for (const attr of openingElement.attributes) {
1452
+ if (t.isJSXSpreadAttribute(attr)) {
1453
+ return false;
1454
+ }
1455
+ if (t.isJSXAttribute(attr)) {
1456
+ const value = attr.value;
1457
+ if (t.isJSXExpressionContainer(value)) {
1458
+ if (!t.isStringLiteral(value.expression) &&
1459
+ !t.isNumericLiteral(value.expression)) {
1460
+ return false;
1461
+ }
1462
+ }
1463
+ }
1464
+ }
1465
+ // Check if all children are static
1466
+ for (const child of element.children) {
1467
+ if (t.isJSXElement(child) && !isStaticElement(child, state)) {
1468
+ return false;
1469
+ }
1470
+ if (t.isJSXExpressionContainer(child)) {
1471
+ return false;
1472
+ }
1473
+ }
1474
+ return true;
1475
+ }
1476
+ function hoistStaticElement(element, state) {
1477
+ const varName = `_$static${state.staticCounter++}`;
1478
+ const transformed = transformAutomaticElement(element, state, false);
1479
+ state.staticElements.set(varName, transformed);
1480
+ return t.identifier(varName);
1481
+ }
1482
+ // ============================================================================
1483
+ // Control Flow Optimization
1484
+ // ============================================================================
1485
+ function optimizeControlFlowComponent(element, state) {
1486
+ const componentName = element.openingElement.name.name;
1487
+ // Optimize Show component
1488
+ if (componentName === 'Show') {
1489
+ return optimizeShowComponent(element, state);
1490
+ }
1491
+ // Optimize For component
1492
+ if (componentName === 'For') {
1493
+ return optimizeForComponent(element, state);
1494
+ }
1495
+ return null;
1496
+ }
1497
+ function optimizeShowComponent(element, state) {
1498
+ // Extract when, fallback, and children
1499
+ // Apply optimizations based on patterns
1500
+ return null; // Placeholder for optimization logic
1501
+ }
1502
+ function optimizeForComponent(element, state) {
1503
+ // Extract each and children
1504
+ // Apply keyed reconciliation optimizations
1505
+ return null; // Placeholder for optimization logic
1506
+ }
1507
+ // ============================================================================
1508
+ // Exports
1509
+ // ============================================================================