@fluixi/compiler 1.0.0-alpha.73 → 1.0.0-alpha.74

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 (109) hide show
  1. package/README.md +41 -16
  2. package/dist/analyze/expr-shape.cjs +1 -0
  3. package/dist/analyze/expr-shape.d.ts +15 -0
  4. package/dist/analyze/expr-shape.d.ts.map +1 -0
  5. package/dist/analyze/expr-shape.js +37 -0
  6. package/dist/analyze/expr-shape.mjs +1 -0
  7. package/dist/analyze/reactive-expr.cjs +1 -0
  8. package/dist/analyze/reactive-expr.d.ts +39 -0
  9. package/dist/analyze/reactive-expr.d.ts.map +1 -0
  10. package/dist/analyze/reactive-expr.js +31 -0
  11. package/dist/analyze/reactive-expr.mjs +1 -0
  12. package/dist/babel-GXO3KAVI.mjs +1 -0
  13. package/dist/chunk-3SAEGOMQ.mjs +1 -0
  14. package/dist/chunk-E2PW2OZU.mjs +1 -0
  15. package/dist/chunk-IBRM4W7I.mjs +10 -0
  16. package/dist/chunk-OHHZCYIQ.mjs +1 -0
  17. package/dist/chunk-TIE4XNAM.mjs +1 -0
  18. package/dist/frontend/babel/build-ir-lit.cjs +1 -1
  19. package/dist/frontend/babel/build-ir-lit.d.ts +1 -0
  20. package/dist/frontend/babel/build-ir-lit.d.ts.map +1 -1
  21. package/dist/frontend/babel/build-ir-lit.mjs +1 -1
  22. package/dist/frontend/babel/build-ir.cjs +1 -1
  23. package/dist/frontend/babel/build-ir.d.ts +13 -11
  24. package/dist/frontend/babel/build-ir.d.ts.map +1 -1
  25. package/dist/frontend/babel/build-ir.js +27 -217
  26. package/dist/frontend/babel/build-ir.mjs +1 -1
  27. package/dist/frontend/babel/index.cjs +1 -2
  28. package/dist/frontend/babel/index.mjs +1 -2
  29. package/dist/frontend/babel/lower-template.cjs +1 -1
  30. package/dist/frontend/babel/lower-template.d.ts +12 -9
  31. package/dist/frontend/babel/lower-template.d.ts.map +1 -1
  32. package/dist/frontend/babel/lower-template.js +33 -445
  33. package/dist/frontend/babel/lower-template.mjs +1 -1
  34. package/dist/frontend/babel/plugin.cjs +1 -2
  35. package/dist/frontend/babel/plugin.d.ts +24 -85
  36. package/dist/frontend/babel/plugin.d.ts.map +1 -1
  37. package/dist/frontend/babel/plugin.js +52 -1285
  38. package/dist/frontend/babel/plugin.mjs +1 -2
  39. package/dist/frontend/babel/server-functions.cjs +1 -1
  40. package/dist/frontend/babel/server-functions.d.ts.map +1 -1
  41. package/dist/frontend/babel/server-functions.js +2 -9
  42. package/dist/frontend/babel/server-functions.mjs +1 -1
  43. package/dist/index.cjs +8 -8
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +8 -0
  47. package/dist/index.mjs +9 -9
  48. package/dist/integrations.cjs +15 -19
  49. package/dist/integrations.d.ts +39 -151
  50. package/dist/integrations.d.ts.map +1 -1
  51. package/dist/integrations.js +79 -491
  52. package/dist/integrations.mjs +11 -17
  53. package/dist/lower/compile-template.cjs +1 -0
  54. package/dist/lower/compile-template.d.ts +57 -0
  55. package/dist/lower/compile-template.d.ts.map +1 -0
  56. package/dist/lower/compile-template.js +72 -0
  57. package/dist/lower/compile-template.mjs +1 -0
  58. package/dist/lower/jsx.cjs +1 -0
  59. package/dist/lower/jsx.d.ts +24 -0
  60. package/dist/lower/jsx.d.ts.map +1 -0
  61. package/dist/lower/jsx.js +193 -0
  62. package/dist/lower/jsx.mjs +1 -0
  63. package/dist/lower/template.cjs +1 -0
  64. package/dist/lower/template.d.ts +41 -0
  65. package/dist/lower/template.d.ts.map +1 -0
  66. package/dist/lower/template.js +445 -0
  67. package/dist/lower/template.mjs +1 -0
  68. package/dist/lower/text.cjs +1 -0
  69. package/dist/lower/text.d.ts +11 -0
  70. package/dist/lower/text.d.ts.map +1 -0
  71. package/dist/lower/text.js +31 -0
  72. package/dist/lower/text.mjs +1 -0
  73. package/dist/options.cjs +1 -1
  74. package/dist/options.d.ts +0 -2
  75. package/dist/options.d.ts.map +1 -1
  76. package/dist/options.js +0 -1
  77. package/dist/options.mjs +1 -1
  78. package/dist/server-fns-6QM243HC.mjs +2 -0
  79. package/dist/server-functions-E3LKCZ4R.mjs +1 -0
  80. package/dist/transform/bindings.cjs +1 -0
  81. package/dist/transform/bindings.d.ts +17 -0
  82. package/dist/transform/bindings.d.ts.map +1 -0
  83. package/dist/transform/bindings.js +69 -0
  84. package/dist/transform/bindings.mjs +1 -0
  85. package/dist/transform/index.cjs +3 -0
  86. package/dist/transform/index.d.ts +12 -0
  87. package/dist/transform/index.d.ts.map +1 -0
  88. package/dist/transform/index.js +10 -0
  89. package/dist/transform/index.mjs +12 -0
  90. package/dist/transform/server-fn-id.cjs +1 -0
  91. package/dist/transform/server-fn-id.d.ts +10 -0
  92. package/dist/transform/server-fn-id.d.ts.map +1 -0
  93. package/dist/transform/server-fn-id.js +18 -0
  94. package/dist/transform/server-fn-id.mjs +1 -0
  95. package/dist/transform/server-fns.cjs +2 -0
  96. package/dist/transform/server-fns.d.ts +13 -0
  97. package/dist/transform/server-fns.d.ts.map +1 -0
  98. package/dist/transform/server-fns.js +94 -0
  99. package/dist/transform/server-fns.mjs +11 -0
  100. package/dist/transform/templates.cjs +3 -0
  101. package/dist/transform/templates.d.ts +26 -0
  102. package/dist/transform/templates.d.ts.map +1 -0
  103. package/dist/transform/templates.js +347 -0
  104. package/dist/transform/templates.mjs +12 -0
  105. package/dist/transform-PJX2GNSV.mjs +3 -0
  106. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  107. package/package.json +29 -8
  108. package/dist/babel-E7EQLX36.mjs +0 -2
  109. package/dist/chunk-EF6V4GTD.mjs +0 -1
@@ -2,370 +2,18 @@
2
2
  * @fileoverview Bundler integrations for the Fluixi compiler.
3
3
  * @module @fluixi/compiler/integrations
4
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
5
+ * vite, webpack, rollup and esbuild adapters. Each is a few lines around
6
+ * `compileModule`, which rewrites the source in place JSX and `` html`` `` both
7
+ * lower to the shared IR and the imperative backend. @fluixi/core re-exports
8
8
  * createVitePlugin from here.
9
9
  *
10
- * Helpers:
11
- * - Detecting reactive expressions
12
- * - Wrapping signals for optimal updates
13
- * - Template optimization
14
- * - Static vs dynamic prop detection
10
+ * `babel: true` routes through the babel front-end instead, for a project whose
11
+ * own babel plugins have to see the output. It is the only thing that needs
12
+ * @babel/core, and it says so rather than degrading: source that was not compiled
13
+ * is source whose templates never became DOM calls, and a build that ships it
14
+ * looks successful until the page is opened.
15
15
  */
16
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
17
  // Babel Plugin Helpers
370
18
  // ============================================================================
371
19
  /**
@@ -387,26 +35,9 @@ export async function createBabelPlugin(options = {}) {
387
35
  // Provide options mapping
388
36
  getOptions() {
389
37
  return {
390
- runtime: 'automatic',
391
38
  importSource: options.importSource || '@fluixi/jsx',
392
- pragma: options.pragma || 'jsx',
393
- pragmaFrag: options.pragmaFrag || 'Fragment',
394
39
  development: options.development || false,
395
- detectReactivity: true,
396
- useLitHTML: options.useLitHTML !== false,
397
- hoistStatics: options.optimize !== false,
398
40
  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
41
  ...(options.format ? { format: options.format } : {}),
411
42
  // Forwarded explicitly: getOptions() is an allowlist, so an option missing
412
43
  // here is silently dropped rather than rejected.
@@ -499,26 +130,24 @@ export function createVitePlugin(options = {}) {
499
130
  console.warn(outcome.warning);
500
131
  },
501
132
  async transform(code, id) {
502
- // Load babel plugin dynamically on first transform
503
- const babelPlugin = await createBabelPlugin(options);
504
133
  // Only process JSX/TSX files
505
- // if (!/\.[jt]sx$/.test(id)) {
506
134
  if (!/\.(jsx|tsx|ts)$/i.test(id)) {
507
135
  return null;
508
136
  }
509
- // console.log('[createVitePlugin] Transforming file:', id);
137
+ if (!options.babel)
138
+ return compileModule(code, id, options);
139
+ const babelPlugin = await createBabelPlugin(options);
510
140
  try {
511
141
  // Use dynamic import instead of require for ESM compatibility
512
142
  const babel = await import('@babel/core').catch(() => null);
513
143
  if (!babel) {
514
- // console.log('[createVitePlugin] Babel not found, using fallback');
515
- // Fallback to simple transform
516
- return {
517
- code: transform(code, options).code,
518
- map: null,
519
- };
144
+ // No silent fallback. This front-end compiles through babel; without it
145
+ // there is no transform, and emitting the source unchanged would ship a
146
+ // build whose JSX and html`` templates were never compiled — a failure
147
+ // that looks exactly like success until the page is opened.
148
+ throw new Error(`[fluixi] @babel/core is required to compile ${id} but could not be loaded. ` +
149
+ `Install it alongside @fluixi/compiler.`);
520
150
  }
521
- // console.log('[createVitePlugin] Using babel transformation');
522
151
  const isTSX = /\.tsx$/.test(id);
523
152
  const isTS = /\.ts$/.test(id);
524
153
  const presets = [];
@@ -535,8 +164,8 @@ export function createVitePlugin(options = {}) {
535
164
  isTSX: isTSX,
536
165
  allExtensions: true,
537
166
  // Important: preserve JSX so our plugin can transform it
538
- jsxPragma: options.pragma || 'jsx',
539
- jsxPragmaFrag: options.pragmaFrag || 'Fragment',
167
+ jsxPragma: 'jsx',
168
+ jsxPragmaFrag: 'Fragment',
540
169
  onlyRemoveTypeImports: true,
541
170
  },
542
171
  ]);
@@ -569,22 +198,48 @@ export function createVitePlugin(options = {}) {
569
198
  };
570
199
  }
571
200
  catch (e) {
572
- console.error('Transform error:', e);
573
- // Return fallback transform on error
574
- try {
575
- return {
576
- code: transform(code, options).code,
577
- map: null,
578
- };
579
- }
580
- catch (fallbackError) {
581
- console.error('Fallback transform also failed:', fallbackError);
582
- return null;
583
- }
201
+ // Let it through to Vite. Swallowing a compile error here and returning
202
+ // half-transformed source turns a diagnosable failure into a mystery at
203
+ // runtime; the build should stop where the problem is.
204
+ throw e;
584
205
  }
585
206
  },
586
207
  };
587
208
  }
209
+ /** CompilerOptions as the transform wants them. Shared by every adapter. */
210
+ function transformOptions(options) {
211
+ return {
212
+ resolve: options.resolve,
213
+ litTag: options.litTag,
214
+ templateClone: options.templateClone,
215
+ partialTemplates: options.partialTemplates,
216
+ runtimeModule: options.signalModule,
217
+ controlFlowModule: options.controlFlowModule,
218
+ routerModule: options.routerModule,
219
+ reactiveModule: options.reactiveModule,
220
+ };
221
+ }
222
+ /** Compile one module, through babel only when asked for it. */
223
+ async function compileModule(code, id, options) {
224
+ if (!options.babel) {
225
+ const { transformTemplates } = await import('./transform/index.js');
226
+ return transformTemplates(code, id, transformOptions(options));
227
+ }
228
+ const babel = await import('@babel/core').catch(() => null);
229
+ const plugin = await createBabelPlugin(options);
230
+ if (!babel || !plugin.plugin) {
231
+ throw new Error(`[fluixi] babel: true needs @babel/core, which could not be loaded for ${id}. ` +
232
+ `Install it, or drop the option to use the built-in compiler.`);
233
+ }
234
+ const result = await babel.transformAsync(code, {
235
+ filename: id,
236
+ configFile: false,
237
+ babelrc: false,
238
+ plugins: [[plugin.plugin, plugin.getOptions()]],
239
+ sourceMaps: options.sourceMaps !== false,
240
+ });
241
+ return result?.code ? { code: result.code, map: result.map } : null;
242
+ }
588
243
  // ============================================================================
589
244
  // Webpack Loader
590
245
  // ============================================================================
@@ -594,20 +249,7 @@ export function createVitePlugin(options = {}) {
594
249
  export function createWebpackLoader(options = {}) {
595
250
  return async function (source) {
596
251
  const callback = this.async();
597
- const babel = tryRequire('@babel/core');
598
- const babelPlugin = await createBabelPlugin(options);
599
- if (!babel || !babelPlugin.plugin) {
600
- // Fallback to simple transform
601
- const result = transform(source, options);
602
- callback(null, result.code);
603
- return;
604
- }
605
- babel
606
- .transformAsync(source, {
607
- filename: this.resourcePath,
608
- plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
609
- sourceMaps: options.sourceMaps !== false,
610
- })
252
+ compileModule(source, this.resourcePath, options)
611
253
  .then((result) => {
612
254
  callback(null, result?.code, result?.map);
613
255
  })
@@ -626,33 +268,9 @@ export function createRollupPlugin(options = {}) {
626
268
  return {
627
269
  name: 'rollup-fluixi-jsx',
628
270
  async transform(code, id) {
629
- const babelPlugin = await createBabelPlugin(options);
630
- if (!/\.[jt]sx$/.test(id)) {
631
- return null;
632
- }
633
- try {
634
- const babel = tryRequire('@babel/core');
635
- if (!babel || !babelPlugin.plugin) {
636
- const result = transform(code, options);
637
- return {
638
- code: result.code,
639
- map: null,
640
- };
641
- }
642
- const result = await babel.transformAsync(code, {
643
- filename: id,
644
- plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
645
- sourceMaps: options.sourceMaps !== false,
646
- });
647
- return {
648
- code: result?.code || code,
649
- map: result?.map,
650
- };
651
- }
652
- catch (e) {
653
- console.error('Transform error:', e);
271
+ if (!/\.[jt]sx?$/.test(id))
654
272
  return null;
655
- }
273
+ return compileModule(code, id, options);
656
274
  },
657
275
  };
658
276
  }
@@ -666,27 +284,15 @@ export function createESBuildPlugin(options = {}) {
666
284
  return {
667
285
  name: 'esbuild-fluixi-jsx',
668
286
  setup(build) {
669
- build.onLoad({ filter: /\.[jt]sx$/ }, async (args) => {
670
- const babel = tryRequire('@babel/core');
671
- const babelPlugin = await createBabelPlugin(options);
672
- const fs = require('fs');
673
- const source = fs.readFileSync(args.path, 'utf8');
287
+ build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
288
+ const { readFileSync } = await import('node:fs');
289
+ const source = readFileSync(args.path, 'utf8');
674
290
  try {
675
- if (!babel || !babelPlugin.plugin) {
676
- const result = transform(source, options);
677
- return {
678
- contents: result.code,
679
- loader: 'js',
680
- };
681
- }
682
- const result = await babel.transformAsync(source, {
683
- filename: args.path,
684
- plugins: [[babelPlugin.plugin, babelPlugin.getOptions()]],
685
- sourceMaps: false,
686
- });
291
+ const result = await compileModule(source, args.path, options);
292
+ // esbuild still has to strip the types the transform leaves in place.
687
293
  return {
688
- contents: result?.code || source,
689
- loader: 'js',
294
+ contents: result?.code ?? source,
295
+ loader: args.path.endsWith('x') ? 'tsx' : 'ts',
690
296
  };
691
297
  }
692
298
  catch (e) {
@@ -702,14 +308,6 @@ export function createESBuildPlugin(options = {}) {
702
308
  },
703
309
  };
704
310
  }
705
- // ============================================================================
706
- // Exports
707
- // ============================================================================
708
- // ============================================================================
709
- // "use server" — server functions vite plugin
710
- // ============================================================================
711
- export { transformServerFunctions } from './frontend/babel/server-functions.js';
712
- import { transformServerFunctions as _txServerFns } from './frontend/babel/server-functions.js';
713
311
  import { writeComponentGlobals } from './resolve/write-globals.js';
714
312
  import { readdirSync, readFileSync } from 'node:fs';
715
313
  import { join as _join } from 'node:path';
@@ -749,7 +347,7 @@ function findServerFnModules(dir, out = []) {
749
347
  * register only when their chunk loads → an RPC registry miss). The client never imports
750
348
  * it. `enforce:'pre'` so it runs on the raw source before other transforms.
751
349
  */
752
- export function serverFunctionsVitePlugin() {
350
+ export function serverFunctionsVitePlugin(options = {}) {
753
351
  let root = process.cwd();
754
352
  return {
755
353
  name: 'fluixi-server-functions',
@@ -758,7 +356,9 @@ export function serverFunctionsVitePlugin() {
758
356
  root = config.root || root;
759
357
  },
760
358
  resolveId(id) {
761
- return id === SERVER_FNS_VMOD ? '\0' + SERVER_FNS_VMOD : null;
359
+ return id === SERVER_FNS_VMOD || id === '@fluixi/start/server-fns'
360
+ ? '\0' + SERVER_FNS_VMOD
361
+ : null;
762
362
  },
763
363
  load(id) {
764
364
  if (id !== '\0' + SERVER_FNS_VMOD)
@@ -773,34 +373,22 @@ export function serverFunctionsVitePlugin() {
773
373
  const dispatcher = `\nexport { isServerFnRequest, handleServerFn } from '@fluixi/start/server-fn';\n`;
774
374
  return setup + imports + dispatcher;
775
375
  },
776
- transform(code, id, opts) {
376
+ async transform(code, id, opts) {
777
377
  if (!/\.[jt]sx?$/.test(id))
778
378
  return null;
779
379
  if (!code.includes('use server'))
780
380
  return null;
781
- return _txServerFns(code, { ssr: !!opts?.ssr, filename: id, root });
381
+ const args = { ssr: !!opts?.ssr, filename: id, root };
382
+ if (options.babel) {
383
+ const { transformServerFunctions } = await import('./frontend/babel/server-functions.js');
384
+ return transformServerFunctions(code, args);
385
+ }
386
+ const { transformServerFns } = await import('./transform/server-fns.js');
387
+ return transformServerFns(code, args);
782
388
  },
783
389
  };
784
390
  }
785
391
  export default {
786
- // Analysis
787
- analyzeProp,
788
- analyzeProps,
789
- shouldUseLitTemplate,
790
- isLitTemplate,
791
- // Code generation
792
- generateCreateElement,
793
- generatePropsCode,
794
- generateChildrenCode,
795
- generateLitHTMLTemplate,
796
- // Optimization
797
- optimizeStatic,
798
- optimizeSpreads,
799
- hoistStatic,
800
- generateEventDelegation,
801
- // Transform
802
- transform,
803
- // Plugin creators
804
392
  createBabelPlugin,
805
393
  createTypeScriptTransformer,
806
394
  createVitePlugin,