@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.
- package/README.md +41 -16
- package/dist/analyze/expr-shape.cjs +1 -0
- package/dist/analyze/expr-shape.d.ts +15 -0
- package/dist/analyze/expr-shape.d.ts.map +1 -0
- package/dist/analyze/expr-shape.js +37 -0
- package/dist/analyze/expr-shape.mjs +1 -0
- package/dist/analyze/reactive-expr.cjs +1 -0
- package/dist/analyze/reactive-expr.d.ts +39 -0
- package/dist/analyze/reactive-expr.d.ts.map +1 -0
- package/dist/analyze/reactive-expr.js +31 -0
- package/dist/analyze/reactive-expr.mjs +1 -0
- package/dist/babel-GXO3KAVI.mjs +1 -0
- package/dist/chunk-3SAEGOMQ.mjs +1 -0
- package/dist/chunk-E2PW2OZU.mjs +1 -0
- package/dist/chunk-IBRM4W7I.mjs +10 -0
- package/dist/chunk-OHHZCYIQ.mjs +1 -0
- package/dist/chunk-TIE4XNAM.mjs +1 -0
- package/dist/frontend/babel/build-ir-lit.cjs +1 -1
- package/dist/frontend/babel/build-ir-lit.d.ts +1 -0
- package/dist/frontend/babel/build-ir-lit.d.ts.map +1 -1
- package/dist/frontend/babel/build-ir-lit.mjs +1 -1
- package/dist/frontend/babel/build-ir.cjs +1 -1
- package/dist/frontend/babel/build-ir.d.ts +13 -11
- package/dist/frontend/babel/build-ir.d.ts.map +1 -1
- package/dist/frontend/babel/build-ir.js +27 -217
- package/dist/frontend/babel/build-ir.mjs +1 -1
- package/dist/frontend/babel/index.cjs +1 -2
- package/dist/frontend/babel/index.mjs +1 -2
- package/dist/frontend/babel/lower-template.cjs +1 -1
- package/dist/frontend/babel/lower-template.d.ts +12 -9
- package/dist/frontend/babel/lower-template.d.ts.map +1 -1
- package/dist/frontend/babel/lower-template.js +33 -445
- package/dist/frontend/babel/lower-template.mjs +1 -1
- package/dist/frontend/babel/plugin.cjs +1 -2
- package/dist/frontend/babel/plugin.d.ts +24 -85
- package/dist/frontend/babel/plugin.d.ts.map +1 -1
- package/dist/frontend/babel/plugin.js +52 -1285
- package/dist/frontend/babel/plugin.mjs +1 -2
- package/dist/frontend/babel/server-functions.cjs +1 -1
- package/dist/frontend/babel/server-functions.d.ts.map +1 -1
- package/dist/frontend/babel/server-functions.js +2 -9
- package/dist/frontend/babel/server-functions.mjs +1 -1
- package/dist/index.cjs +8 -8
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.mjs +9 -9
- package/dist/integrations.cjs +15 -19
- package/dist/integrations.d.ts +39 -151
- package/dist/integrations.d.ts.map +1 -1
- package/dist/integrations.js +79 -491
- package/dist/integrations.mjs +11 -17
- package/dist/lower/compile-template.cjs +1 -0
- package/dist/lower/compile-template.d.ts +57 -0
- package/dist/lower/compile-template.d.ts.map +1 -0
- package/dist/lower/compile-template.js +72 -0
- package/dist/lower/compile-template.mjs +1 -0
- package/dist/lower/jsx.cjs +1 -0
- package/dist/lower/jsx.d.ts +24 -0
- package/dist/lower/jsx.d.ts.map +1 -0
- package/dist/lower/jsx.js +193 -0
- package/dist/lower/jsx.mjs +1 -0
- package/dist/lower/template.cjs +1 -0
- package/dist/lower/template.d.ts +41 -0
- package/dist/lower/template.d.ts.map +1 -0
- package/dist/lower/template.js +445 -0
- package/dist/lower/template.mjs +1 -0
- package/dist/lower/text.cjs +1 -0
- package/dist/lower/text.d.ts +11 -0
- package/dist/lower/text.d.ts.map +1 -0
- package/dist/lower/text.js +31 -0
- package/dist/lower/text.mjs +1 -0
- package/dist/options.cjs +1 -1
- package/dist/options.d.ts +0 -2
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +0 -1
- package/dist/options.mjs +1 -1
- package/dist/server-fns-6QM243HC.mjs +2 -0
- package/dist/server-functions-E3LKCZ4R.mjs +1 -0
- package/dist/transform/bindings.cjs +1 -0
- package/dist/transform/bindings.d.ts +17 -0
- package/dist/transform/bindings.d.ts.map +1 -0
- package/dist/transform/bindings.js +69 -0
- package/dist/transform/bindings.mjs +1 -0
- package/dist/transform/index.cjs +3 -0
- package/dist/transform/index.d.ts +12 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +10 -0
- package/dist/transform/index.mjs +12 -0
- package/dist/transform/server-fn-id.cjs +1 -0
- package/dist/transform/server-fn-id.d.ts +10 -0
- package/dist/transform/server-fn-id.d.ts.map +1 -0
- package/dist/transform/server-fn-id.js +18 -0
- package/dist/transform/server-fn-id.mjs +1 -0
- package/dist/transform/server-fns.cjs +2 -0
- package/dist/transform/server-fns.d.ts +13 -0
- package/dist/transform/server-fns.d.ts.map +1 -0
- package/dist/transform/server-fns.js +94 -0
- package/dist/transform/server-fns.mjs +11 -0
- package/dist/transform/templates.cjs +3 -0
- package/dist/transform/templates.d.ts +26 -0
- package/dist/transform/templates.d.ts.map +1 -0
- package/dist/transform/templates.js +347 -0
- package/dist/transform/templates.mjs +12 -0
- package/dist/transform-PJX2GNSV.mjs +3 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +29 -8
- package/dist/babel-E7EQLX36.mjs +0 -2
- package/dist/chunk-EF6V4GTD.mjs +0 -1
package/dist/integrations.js
CHANGED
|
@@ -2,370 +2,18 @@
|
|
|
2
2
|
* @fileoverview Bundler integrations for the Fluixi compiler.
|
|
3
3
|
* @module @fluixi/compiler/integrations
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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:
|
|
539
|
-
jsxPragmaFrag:
|
|
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
|
-
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
670
|
-
const
|
|
671
|
-
const
|
|
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
|
-
|
|
676
|
-
|
|
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
|
|
689
|
-
loader: '
|
|
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
|
|
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
|
-
|
|
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,
|