@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,790 @@
1
+ /**
2
+ * @fileoverview Bundler integrations for the Fluixi compiler.
3
+ * @module @fluixi/compiler/integrations
4
+ *
5
+ * Build-tool adapters (vite/webpack/rollup/esbuild) plus the prop/template
6
+ * analysis helpers that drive them. These wrap the babel front-end so a single
7
+ * package owns all compile-time code. @fluixi/core re-exports
8
+ * createVitePlugin from here.
9
+ *
10
+ * Helpers:
11
+ * - Detecting reactive expressions
12
+ * - Wrapping signals for optimal updates
13
+ * - Template optimization
14
+ * - Static vs dynamic prop detection
15
+ */
16
+ // ============================================================================
17
+ // Constants
18
+ // ============================================================================
19
+ const STATIC_PROPS = new Set(['key', 'ref', 'children']);
20
+ const REACTIVE_PATTERNS = [
21
+ // Signal accessor patterns
22
+ /\(\)\s*=>/, // Arrow function
23
+ /function\s*\(\)/, // Function expression
24
+ /\w+\(\)/, // Function call that might be accessor
25
+ // Store patterns
26
+ /\w+\.\w+/, // Property access (might be store proxy)
27
+ /\w+\[.+\]/, // Bracket notation
28
+ // Reactive operators
29
+ /createSignal/,
30
+ /createStore/,
31
+ /createMemo/,
32
+ /createEffect/,
33
+ ];
34
+ const EVENT_PATTERN = /^on[A-Z]/;
35
+ // ============================================================================
36
+ // Prop Analysis
37
+ // ============================================================================
38
+ /**
39
+ * Analyze a prop to determine if it's static or dynamic
40
+ */
41
+ export function analyzeProp(name, value) {
42
+ const analysis = {
43
+ name,
44
+ isStatic: true,
45
+ isReactive: false,
46
+ isEvent: EVENT_PATTERN.test(name),
47
+ isRef: name === 'ref',
48
+ isSpread: name.startsWith('...'),
49
+ };
50
+ // Static props are always static
51
+ if (STATIC_PROPS.has(name)) {
52
+ return analysis;
53
+ }
54
+ // Events and refs are handled specially
55
+ if (analysis.isEvent || analysis.isRef) {
56
+ analysis.isStatic = false;
57
+ return analysis;
58
+ }
59
+ // Check if value appears reactive
60
+ if (typeof value === 'string') {
61
+ const code = value;
62
+ // Check for reactive patterns
63
+ for (const pattern of REACTIVE_PATTERNS) {
64
+ if (pattern.test(code)) {
65
+ analysis.isStatic = false;
66
+ analysis.isReactive = true;
67
+ break;
68
+ }
69
+ }
70
+ // Check for template literals with expressions
71
+ if (code.includes('${')) {
72
+ analysis.isStatic = false;
73
+ analysis.isReactive = true;
74
+ }
75
+ }
76
+ else if (typeof value === 'function') {
77
+ // Functions are potentially reactive
78
+ analysis.isStatic = false;
79
+ analysis.isReactive = true;
80
+ }
81
+ else if (typeof value === 'object' && value !== null) {
82
+ // Objects might contain reactive values
83
+ analysis.isStatic = false;
84
+ }
85
+ return analysis;
86
+ }
87
+ /**
88
+ * Analyze all props on an element
89
+ */
90
+ export function analyzeProps(props) {
91
+ const analyses = new Map();
92
+ for (const [name, value] of Object.entries(props)) {
93
+ analyses.set(name, analyzeProp(name, value));
94
+ }
95
+ return analyses;
96
+ }
97
+ // ============================================================================
98
+ // Template Detection
99
+ // ============================================================================
100
+ /**
101
+ * Check if JSX should be compiled to lit-html template
102
+ */
103
+ export function shouldUseLitTemplate(tag, props, children) {
104
+ // Native elements can use lit-html
105
+ if (typeof tag !== 'string') {
106
+ return false;
107
+ }
108
+ // Check if all props are compatible with lit-html
109
+ const propAnalyses = analyzeProps(props);
110
+ for (const analysis of propAnalyses.values()) {
111
+ // Spread props require native JSX
112
+ if (analysis.isSpread) {
113
+ return false;
114
+ }
115
+ // Complex reactive props might be better in native JSX
116
+ if (analysis.isReactive && !analysis.isEvent) {
117
+ // Allow simple reactive bindings in lit-html
118
+ // but complex ones are better in native JSX
119
+ }
120
+ }
121
+ return true;
122
+ }
123
+ /**
124
+ * Check if a value is a lit-html template
125
+ */
126
+ export function isLitTemplate(value) {
127
+ return value && typeof value === 'object' && '_$litType$' in value;
128
+ }
129
+ // ============================================================================
130
+ // Code Generation Helpers
131
+ // ============================================================================
132
+ /**
133
+ * Generate code for creating an element
134
+ */
135
+ export function generateCreateElement(tag, props, children, options = {}) {
136
+ const isSVG = ['svg', 'path', 'circle', 'rect', 'g'].includes(tag);
137
+ // Generate props code
138
+ const propsCode = generatePropsCode(props, options);
139
+ // Generate children code
140
+ const childrenCode = children.length > 0 ? `, ${generateChildrenCode(children)}` : '';
141
+ if (options.useLitHTML !== false &&
142
+ shouldUseLitTemplate(tag, props, children)) {
143
+ return generateLitHTMLTemplate(tag, props, children, isSVG);
144
+ }
145
+ return `jsx('${tag}', ${propsCode}${childrenCode})`;
146
+ }
147
+ /**
148
+ * Generate code for props object
149
+ */
150
+ export function generatePropsCode(props, options = {}) {
151
+ if (Object.keys(props).length === 0) {
152
+ return '{}';
153
+ }
154
+ const propAnalyses = analyzeProps(props);
155
+ const entries = [];
156
+ for (const [name, value] of Object.entries(props)) {
157
+ const analysis = propAnalyses.get(name);
158
+ if (analysis.isReactive && !analysis.isStatic) {
159
+ // Wrap reactive values
160
+ entries.push(`${name}: () => ${value}`);
161
+ }
162
+ else if (typeof value === 'string') {
163
+ entries.push(`${name}: "${value}"`);
164
+ }
165
+ else if (typeof value === 'number' || typeof value === 'boolean') {
166
+ entries.push(`${name}: ${value}`);
167
+ }
168
+ else {
169
+ entries.push(`${name}: ${value}`);
170
+ }
171
+ }
172
+ return `{ ${entries.join(', ')} }`;
173
+ }
174
+ /**
175
+ * Generate code for children
176
+ */
177
+ export function generateChildrenCode(children) {
178
+ if (children.length === 0) {
179
+ return 'undefined';
180
+ }
181
+ if (children.length === 1) {
182
+ return String(children[0]);
183
+ }
184
+ return `[${children.join(', ')}]`;
185
+ }
186
+ /**
187
+ * Generate lit-html template code
188
+ */
189
+ export function generateLitHTMLTemplate(tag, props, children, isSVG = false) {
190
+ const templateFunc = isSVG ? 'svg' : 'html';
191
+ // Build template string
192
+ let template = `<${tag}`;
193
+ const values = [];
194
+ // Add props
195
+ for (const [name, value] of Object.entries(props)) {
196
+ if (name === 'children' || name === 'key' || name === 'ref') {
197
+ continue;
198
+ }
199
+ const analysis = analyzeProp(name, value);
200
+ if (analysis.isEvent) {
201
+ const eventName = name.slice(2).toLowerCase();
202
+ template += ` @${eventName}=\${${values.length}}`;
203
+ values.push(String(value));
204
+ }
205
+ else if (analysis.isReactive) {
206
+ template += ` ${name}=\${${values.length}}`;
207
+ values.push(`signal(() => ${value})`);
208
+ }
209
+ else if (typeof value === 'string') {
210
+ template += ` ${name}="${value}"`;
211
+ }
212
+ else {
213
+ template += ` ${name}=\${${values.length}}`;
214
+ values.push(String(value));
215
+ }
216
+ }
217
+ template += '>';
218
+ // Add children
219
+ if (children.length > 0) {
220
+ for (const child of children) {
221
+ if (typeof child === 'string') {
222
+ template += child;
223
+ }
224
+ else {
225
+ template += `\${${values.length}}`;
226
+ values.push(String(child));
227
+ }
228
+ }
229
+ }
230
+ template += `</${tag}>`;
231
+ // Generate template literal
232
+ return `${templateFunc}\`${template}\`${values.length > 0 ? `, ${values.join(', ')}` : ''}`;
233
+ }
234
+ // ============================================================================
235
+ // Optimization Passes
236
+ // ============================================================================
237
+ /**
238
+ * Optimize static content
239
+ */
240
+ export function optimizeStatic(code) {
241
+ // Combine consecutive static strings
242
+ code = code.replace(/jsx\('([^']+)',\s*{},\s*"([^"]+)"\s*\)/g, (_, tag, text) => `_$staticNode('${tag}', '${text}')`);
243
+ return code;
244
+ }
245
+ /**
246
+ * Optimize prop spreads
247
+ */
248
+ export function optimizeSpreads(code) {
249
+ // DISABLED: This string-based transformation was incorrectly transforming JSX spreads
250
+ // The babel plugin handles spread attributes properly using Object.assign
251
+ // This was causing spread(storeProxy) to be called instead of proper prop merging
252
+ return code;
253
+ }
254
+ /**
255
+ * Hoist static elements
256
+ */
257
+ export function hoistStatic(code) {
258
+ const hoisted = [];
259
+ let counter = 0;
260
+ // Find static JSX elements
261
+ code = code.replace(/jsx\('([^']+)',\s*{([^}]*)},\s*([^)]*)\)/g, (match, tag, props, children) => {
262
+ // Simple heuristic: if no reactive props, hoist
263
+ if (!props.includes('()') && !props.includes('=>')) {
264
+ const varName = `_$static${counter++}`;
265
+ hoisted.push(`const ${varName} = ${match};`);
266
+ return varName;
267
+ }
268
+ return match;
269
+ });
270
+ if (hoisted.length > 0) {
271
+ return hoisted.join('\n') + '\n' + code;
272
+ }
273
+ return code;
274
+ }
275
+ // ============================================================================
276
+ // Event Delegation
277
+ // ============================================================================
278
+ /**
279
+ * Generate event delegation setup
280
+ */
281
+ export function generateEventDelegation(events, options = {}) {
282
+ const delegatedEvents = options.delegatedEvents || [
283
+ 'click',
284
+ 'input',
285
+ 'change',
286
+ 'submit',
287
+ ];
288
+ const filtered = events.filter((e) => delegatedEvents.includes(e.toLowerCase()));
289
+ if (filtered.length === 0) {
290
+ return '';
291
+ }
292
+ return `delegateEvents([${filtered.map((e) => `'${e}'`).join(', ')}]);`;
293
+ }
294
+ // ============================================================================
295
+ // Transform Pipeline
296
+ // ============================================================================
297
+ /**
298
+ * Transform JSX code with optimizations
299
+ */
300
+ export function transform(code, options = {}) {
301
+ const metadata = {
302
+ hasSignals: /createSignal|signal\(/.test(code),
303
+ hasStores: /createStore|store\(/.test(code),
304
+ hasLitHTML: /html`|svg`/.test(code),
305
+ staticProps: new Set(),
306
+ dynamicProps: new Set(),
307
+ components: new Set(),
308
+ };
309
+ let transformed = code;
310
+ // Apply optimizations
311
+ if (options.optimize !== false) {
312
+ transformed = optimizeStatic(transformed);
313
+ // optimizeSpreads is disabled - babel plugin handles spreads correctly
314
+ // transformed = optimizeSpreads(transformed);
315
+ transformed = hoistStatic(transformed);
316
+ }
317
+ // Add event delegation if needed
318
+ if (options.delegateEvents !== false) {
319
+ const events = extractEvents(code);
320
+ const delegation = generateEventDelegation(events, options);
321
+ if (delegation) {
322
+ transformed = delegation + '\n' + transformed;
323
+ }
324
+ }
325
+ // Add imports if needed
326
+ transformed = addImports(transformed, metadata, options);
327
+ return {
328
+ code: transformed,
329
+ metadata,
330
+ };
331
+ }
332
+ /**
333
+ * Extract event names from code
334
+ */
335
+ function extractEvents(code) {
336
+ const events = new Set();
337
+ const regex = /on([A-Z][a-z]+)/g;
338
+ let match;
339
+ while ((match = regex.exec(code)) !== null) {
340
+ events.add(match[1].toLowerCase());
341
+ }
342
+ return Array.from(events);
343
+ }
344
+ /**
345
+ * Add necessary imports
346
+ */
347
+ function addImports(code, metadata, options) {
348
+ const imports = [];
349
+ const source = options.importSource || '@fluixi/jsx';
350
+ // Always import jsx basics
351
+ imports.push(`import { jsx, jsxs, Fragment, spread } from '${source}';`);
352
+ // Import signal directive if needed
353
+ if (metadata.hasSignals) {
354
+ imports.push(`import { signal } from '@fluixi/dom/reactive';`);
355
+ }
356
+ // Import control flow if used
357
+ if (code.includes('<Show') ||
358
+ code.includes('<For') ||
359
+ code.includes('<Switch')) {
360
+ imports.push(`import { Show, For, Index, Switch, Match } from '@fluixi/dom/control-flow';`);
361
+ }
362
+ // Import event delegation if used
363
+ if (options.delegateEvents !== false) {
364
+ imports.push(`import { delegateEvents } from '@fluixi/dom/runtime';`);
365
+ }
366
+ return imports.join('\n') + '\n\n' + code;
367
+ }
368
+ // ============================================================================
369
+ // Babel Plugin Helpers
370
+ // ============================================================================
371
+ /**
372
+ * Helper for creating a Babel plugin
373
+ */
374
+ export async function createBabelPlugin(options = {}) {
375
+ // Import the actual Babel plugin using dynamic import for ESM
376
+ let pluginModule = null;
377
+ try {
378
+ // The babel front-end lives in this same package now.
379
+ pluginModule = await import('./frontend/babel/index.js');
380
+ }
381
+ catch (e) {
382
+ console.warn('Could not load the babel front-end:', e);
383
+ }
384
+ return {
385
+ name: 'babel-fluixi-jsx',
386
+ plugin: pluginModule ? pluginModule.default || pluginModule : null,
387
+ // Provide options mapping
388
+ getOptions() {
389
+ return {
390
+ runtime: 'automatic',
391
+ importSource: options.importSource || '@fluixi/jsx',
392
+ pragma: options.pragma || 'jsx',
393
+ pragmaFrag: options.pragmaFrag || 'Fragment',
394
+ development: options.development || false,
395
+ detectReactivity: true,
396
+ useLitHTML: options.useLitHTML !== false,
397
+ hoistStatics: options.optimize !== false,
398
+ delegateEvents: options.delegateEvents !== false,
399
+ delegatedEvents: options.delegatedEvents || [
400
+ 'click',
401
+ 'input',
402
+ 'change',
403
+ 'submit',
404
+ ],
405
+ optimizeControlFlow: options.optimize !== false,
406
+ sourceMaps: options.sourceMaps !== false,
407
+ // Codegen backend selection (default 'imperative').
408
+ backend: options.backend || 'imperative',
409
+ ...(options.codegen ? { codegen: options.codegen } : {}),
410
+ ...(options.signalModule ? { signalModule: options.signalModule } : {}),
411
+ ...(options.controlFlowModule
412
+ ? { controlFlowModule: options.controlFlowModule }
413
+ : {}),
414
+ ...(options.reactiveModule ? { reactiveModule: options.reactiveModule } : {}),
415
+ };
416
+ },
417
+ };
418
+ }
419
+ // ============================================================================
420
+ // TypeScript Transformer Helpers
421
+ // ============================================================================
422
+ /**
423
+ * Helper for creating a TypeScript transformer
424
+ */
425
+ export function createTypeScriptTransformer(options = {}) {
426
+ const ts = tryRequire('typescript');
427
+ if (!ts) {
428
+ console.warn('TypeScript not found, transformer unavailable');
429
+ return (context) => (sourceFile) => sourceFile;
430
+ }
431
+ return (context) => {
432
+ const visitor = (node) => {
433
+ // Transform JSX elements
434
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
435
+ // Use the same transformation logic as Babel
436
+ return transformTSJSXNode(node, options, ts);
437
+ }
438
+ return ts.visitEachChild(node, visitor, context);
439
+ };
440
+ return (sourceFile) => {
441
+ return ts.visitNode(sourceFile, visitor);
442
+ };
443
+ };
444
+ }
445
+ /**
446
+ * Transform TypeScript JSX node
447
+ */
448
+ function transformTSJSXNode(node, options, ts) {
449
+ // This is a placeholder - full implementation would mirror Babel transformation
450
+ // For now, return the node unchanged
451
+ return node;
452
+ }
453
+ /**
454
+ * Try to require a module, return null if not available
455
+ */
456
+ function tryRequire(moduleName) {
457
+ try {
458
+ return require(moduleName);
459
+ }
460
+ catch (e) {
461
+ return null;
462
+ }
463
+ }
464
+ // ============================================================================
465
+ // Vite Plugin
466
+ // ============================================================================
467
+ /**
468
+ * Create a Vite plugin for JSX transformation
469
+ */
470
+ export function createVitePlugin(options = {}) {
471
+ return {
472
+ name: 'vite-fluixi-jsx',
473
+ enforce: 'pre',
474
+ config() {
475
+ return {
476
+ esbuild: {
477
+ jsx: 'automatic',
478
+ jsxImportSource: options.importSource || '@fluixi/jsx',
479
+ },
480
+ };
481
+ },
482
+ async transform(code, id) {
483
+ // Load babel plugin dynamically on first transform
484
+ const babelPlugin = await createBabelPlugin(options);
485
+ // Only process JSX/TSX files
486
+ // if (!/\.[jt]sx$/.test(id)) {
487
+ if (!/\.(jsx|tsx|ts)$/i.test(id)) {
488
+ return null;
489
+ }
490
+ // console.log('[createVitePlugin] Transforming file:', id);
491
+ try {
492
+ // Use dynamic import instead of require for ESM compatibility
493
+ const babel = await import('@babel/core').catch(() => null);
494
+ if (!babel) {
495
+ // console.log('[createVitePlugin] Babel not found, using fallback');
496
+ // Fallback to simple transform
497
+ return {
498
+ code: transform(code, options).code,
499
+ map: null,
500
+ };
501
+ }
502
+ // console.log('[createVitePlugin] Using babel transformation');
503
+ const isTSX = /\.tsx$/.test(id);
504
+ const isTS = /\.ts$/.test(id);
505
+ const presets = [];
506
+ const plugins = [];
507
+ // Add TypeScript preset if needed (but with JSX preservation)
508
+ if (isTSX || isTS) {
509
+ const tsPreset = await import('@babel/preset-typescript')
510
+ .then((m) => m.default || m)
511
+ .catch(() => null);
512
+ if (tsPreset) {
513
+ presets.push([
514
+ tsPreset,
515
+ {
516
+ isTSX: isTSX,
517
+ allExtensions: true,
518
+ // Important: preserve JSX so our plugin can transform it
519
+ jsxPragma: options.pragma || 'jsx',
520
+ jsxPragmaFrag: options.pragmaFrag || 'Fragment',
521
+ onlyRemoveTypeImports: true,
522
+ },
523
+ ]);
524
+ }
525
+ }
526
+ // Only our custom babel plugin handles JSX — never @babel/preset-react, which
527
+ // rewrites spreads with Object.assign and breaks store-proxy reactivity.
528
+ if (babelPlugin && babelPlugin.plugin) {
529
+ plugins.push([babelPlugin.plugin, babelPlugin.getOptions()]);
530
+ }
531
+ else {
532
+ console.warn('[createVitePlugin] custom babel plugin not found');
533
+ }
534
+ const result = await babel.transformAsync(code, {
535
+ filename: id,
536
+ configFile: false,
537
+ babelrc: false,
538
+ presets: presets,
539
+ plugins: plugins,
540
+ sourceMaps: options.sourceMaps !== false,
541
+ });
542
+ // console.log('[createVitePlugin] Transformation complete for:', id);
543
+ // console.log(
544
+ // '[createVitePlugin] Has mergeProps in output:',
545
+ // result?.code?.includes('mergeProps')
546
+ // );
547
+ return {
548
+ code: result?.code || code,
549
+ map: result?.map,
550
+ };
551
+ }
552
+ catch (e) {
553
+ console.error('Transform error:', e);
554
+ // Return fallback transform on error
555
+ try {
556
+ return {
557
+ code: transform(code, options).code,
558
+ map: null,
559
+ };
560
+ }
561
+ catch (fallbackError) {
562
+ console.error('Fallback transform also failed:', fallbackError);
563
+ return null;
564
+ }
565
+ }
566
+ },
567
+ };
568
+ }
569
+ // ============================================================================
570
+ // Webpack Loader
571
+ // ============================================================================
572
+ /**
573
+ * Create a Webpack loader for JSX transformation
574
+ */
575
+ export function createWebpackLoader(options = {}) {
576
+ return async function (source) {
577
+ const callback = this.async();
578
+ const babel = tryRequire('@babel/core');
579
+ const babelPlugin = await createBabelPlugin(options);
580
+ if (!babel || !babelPlugin.plugin) {
581
+ // Fallback to simple transform
582
+ const result = transform(source, options);
583
+ callback(null, result.code);
584
+ return;
585
+ }
586
+ babel
587
+ .transformAsync(source, {
588
+ filename: this.resourcePath,
589
+ plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
590
+ sourceMaps: options.sourceMaps !== false,
591
+ })
592
+ .then((result) => {
593
+ callback(null, result?.code, result?.map);
594
+ })
595
+ .catch((err) => {
596
+ callback(err);
597
+ });
598
+ };
599
+ }
600
+ // ============================================================================
601
+ // Rollup Plugin
602
+ // ============================================================================
603
+ /**
604
+ * Create a Rollup plugin for JSX transformation
605
+ */
606
+ export function createRollupPlugin(options = {}) {
607
+ return {
608
+ name: 'rollup-fluixi-jsx',
609
+ async transform(code, id) {
610
+ const babelPlugin = await createBabelPlugin(options);
611
+ if (!/\.[jt]sx$/.test(id)) {
612
+ return null;
613
+ }
614
+ try {
615
+ const babel = tryRequire('@babel/core');
616
+ if (!babel || !babelPlugin.plugin) {
617
+ const result = transform(code, options);
618
+ return {
619
+ code: result.code,
620
+ map: null,
621
+ };
622
+ }
623
+ const result = await babel.transformAsync(code, {
624
+ filename: id,
625
+ plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
626
+ sourceMaps: options.sourceMaps !== false,
627
+ });
628
+ return {
629
+ code: result?.code || code,
630
+ map: result?.map,
631
+ };
632
+ }
633
+ catch (e) {
634
+ console.error('Transform error:', e);
635
+ return null;
636
+ }
637
+ },
638
+ };
639
+ }
640
+ // ============================================================================
641
+ // ESBuild Plugin
642
+ // ============================================================================
643
+ /**
644
+ * Create an ESBuild plugin for JSX transformation
645
+ */
646
+ export function createESBuildPlugin(options = {}) {
647
+ return {
648
+ name: 'esbuild-fluixi-jsx',
649
+ setup(build) {
650
+ build.onLoad({ filter: /\.[jt]sx$/ }, async (args) => {
651
+ const babel = tryRequire('@babel/core');
652
+ const babelPlugin = await createBabelPlugin(options);
653
+ const fs = require('fs');
654
+ const source = fs.readFileSync(args.path, 'utf8');
655
+ try {
656
+ if (!babel || !babelPlugin.plugin) {
657
+ const result = transform(source, options);
658
+ return {
659
+ contents: result.code,
660
+ loader: 'js',
661
+ };
662
+ }
663
+ const result = await babel.transformAsync(source, {
664
+ filename: args.path,
665
+ plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
666
+ sourceMaps: false,
667
+ });
668
+ return {
669
+ contents: result?.code || source,
670
+ loader: 'js',
671
+ };
672
+ }
673
+ catch (e) {
674
+ return {
675
+ errors: [
676
+ {
677
+ text: `Transform error: ${e}`,
678
+ },
679
+ ],
680
+ };
681
+ }
682
+ });
683
+ },
684
+ };
685
+ }
686
+ // ============================================================================
687
+ // Exports
688
+ // ============================================================================
689
+ // ============================================================================
690
+ // "use server" — server functions vite plugin
691
+ // ============================================================================
692
+ export { transformServerFunctions } from './frontend/babel/server-functions.js';
693
+ import { transformServerFunctions as _txServerFns } from './frontend/babel/server-functions.js';
694
+ import { readdirSync, readFileSync } from 'node:fs';
695
+ import { join as _join } from 'node:path';
696
+ const SERVER_FNS_VMOD = 'virtual:fluixi-server-fns';
697
+ /** Recursively find source files containing the `"use server"` directive under `dir`. */
698
+ function findServerFnModules(dir, out = []) {
699
+ let entries;
700
+ try {
701
+ entries = readdirSync(dir, { withFileTypes: true });
702
+ }
703
+ catch {
704
+ return out;
705
+ }
706
+ for (const e of entries) {
707
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name.startsWith('.'))
708
+ continue;
709
+ const full = _join(dir, e.name);
710
+ if (e.isDirectory())
711
+ findServerFnModules(full, out);
712
+ else if (/\.[jt]sx?$/.test(e.name)) {
713
+ try {
714
+ if (readFileSync(full, 'utf8').includes('use server'))
715
+ out.push(full);
716
+ }
717
+ catch {
718
+ /* unreadable — skip */
719
+ }
720
+ }
721
+ }
722
+ return out;
723
+ }
724
+ /**
725
+ * Vite plugin for `"use server"`: applies the transform (client→RPC stub, server→register
726
+ * the body), and provides `virtual:fluixi-server-fns` — side-effect imports of every
727
+ * module that defines a server function. The server entry imports it so ALL server
728
+ * functions register at startup (including those in lazy routes, which would otherwise
729
+ * register only when their chunk loads → an RPC registry miss). The client never imports
730
+ * it. `enforce:'pre'` so it runs on the raw source before other transforms.
731
+ */
732
+ export function serverFunctionsVitePlugin() {
733
+ let root = process.cwd();
734
+ return {
735
+ name: 'fluixi-server-functions',
736
+ enforce: 'pre',
737
+ configResolved(config) {
738
+ root = config.root || root;
739
+ },
740
+ resolveId(id) {
741
+ return id === SERVER_FNS_VMOD ? '\0' + SERVER_FNS_VMOD : null;
742
+ },
743
+ load(id) {
744
+ if (id !== '\0' + SERVER_FNS_VMOD)
745
+ return null;
746
+ const mods = findServerFnModules(_join(root, 'src'));
747
+ // First wire the request-context bridge (server-only), then side-effect import each
748
+ // server-fn module so its top-level $$registerServerFn(...) runs, then re-export the
749
+ // dispatcher so the host can route /_server through THIS module graph (sharing the
750
+ // registry the imports populated) — apps don't need to re-export it from entry-server.
751
+ const setup = `import '@fluixi/start/server-fn-setup';\n`;
752
+ const imports = mods.map((m) => `import ${JSON.stringify(m)};`).join('\n');
753
+ const dispatcher = `\nexport { isServerFnRequest, handleServerFn } from '@fluixi/start/server-fn';\n`;
754
+ return setup + imports + dispatcher;
755
+ },
756
+ transform(code, id, opts) {
757
+ if (!/\.[jt]sx?$/.test(id))
758
+ return null;
759
+ if (!code.includes('use server'))
760
+ return null;
761
+ return _txServerFns(code, { ssr: !!opts?.ssr, filename: id, root });
762
+ },
763
+ };
764
+ }
765
+ export default {
766
+ // Analysis
767
+ analyzeProp,
768
+ analyzeProps,
769
+ shouldUseLitTemplate,
770
+ isLitTemplate,
771
+ // Code generation
772
+ generateCreateElement,
773
+ generatePropsCode,
774
+ generateChildrenCode,
775
+ generateLitHTMLTemplate,
776
+ // Optimization
777
+ optimizeStatic,
778
+ optimizeSpreads,
779
+ hoistStatic,
780
+ generateEventDelegation,
781
+ // Transform
782
+ transform,
783
+ // Plugin creators
784
+ createBabelPlugin,
785
+ createTypeScriptTransformer,
786
+ createVitePlugin,
787
+ createWebpackLoader,
788
+ createRollupPlugin,
789
+ createESBuildPlugin,
790
+ };