@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
|
@@ -1,85 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Babel front-end for the Fluixi compiler.
|
|
3
3
|
* @module @fluixi/babel-plugin-jsx
|
|
4
4
|
*
|
|
5
|
-
* This
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* This is a host adapter, not the compiler. It parses with babel and, for each
|
|
6
|
+
* JSX element/fragment and each `` html`...` `` template, hands the node to the
|
|
7
|
+
* shared pipeline — build IR, run the imperative backend — then splices the
|
|
8
|
+
* emitted code back into the program. Everything that decides *what* the output
|
|
9
|
+
* means (directives, control flow, component resolution, load strategies,
|
|
10
|
+
* reactivity, templates) lives in ../../{ir,resolve,optimize,codegen}, which
|
|
11
|
+
* import no babel at all.
|
|
12
|
+
*
|
|
13
|
+
* What stays here is genuinely host work: dispatching the visitors, collecting
|
|
14
|
+
* the runtime symbols the backend reported so the imports can be injected, and
|
|
15
|
+
* hoisting static templates to module scope.
|
|
12
16
|
*/
|
|
13
17
|
import { declare } from '@babel/helper-plugin-utils';
|
|
14
18
|
import { createComponentResolver, } from '../../resolve/component-resolver.js';
|
|
15
19
|
import { resolveComponentSources } from '../../resolve/resolve-ir.js';
|
|
16
20
|
import { collapseStrategies } from '../../optimize/collapse-strategies.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { types as t, template
|
|
21
|
+
import { isDeferred } from '../../resolve/load-directive.js';
|
|
22
|
+
import { CORE_CONTROL_FLOW, ROUTER_COMPONENTS as ROUTER_COMPONENT_NAMES, } from '../../resolve/builtins.js';
|
|
23
|
+
import { types as t, template } from '@babel/core';
|
|
20
24
|
import { buildIR } from './build-ir.js';
|
|
21
25
|
import { buildIRFromLit } from './build-ir-lit.js';
|
|
22
26
|
import { imperativeBackend } from '../../codegen/backends/imperative.js';
|
|
23
27
|
// ============================================================================
|
|
24
28
|
// Constants
|
|
25
29
|
// ============================================================================
|
|
26
|
-
// The names come from `resolve/builtins.ts`, which publishes them for the TS plugin,
|
|
27
|
-
// the language server and codemods — tools that need to know where `<Show>` comes from
|
|
28
|
-
// but must not re-derive it. What stays here is the part that is genuinely codegen:
|
|
29
|
-
// which names get special lowering, reactive-getter props, and memo exemption.
|
|
30
|
-
const CONTROL_FLOW_COMPONENTS = new Set([...DOM_CONTROL_FLOW, ...CORE_CONTROL_FLOW]);
|
|
31
30
|
// Control-flow components that live in @fluixi/core, NOT @fluixi/dom — they must
|
|
32
31
|
// be auto-imported from core (dom doesn't export them).
|
|
33
32
|
const CORE_ONLY_CONTROL_FLOW = new Set(CORE_CONTROL_FLOW);
|
|
34
33
|
// Router components auto-imported (from routerModule) the same way control flow is:
|
|
35
34
|
// use `<Router>`/`<Outlet>`/… in a template and the import is injected if absent.
|
|
36
35
|
const ROUTER_COMPONENTS = new Set(ROUTER_COMPONENT_NAMES);
|
|
37
|
-
// Control-flow components read a single reactive *value* prop inside their own tracking
|
|
38
|
-
// scope. Emit that prop as a getter (like `children`) so reading it re-runs the caller's
|
|
39
|
-
// expression — otherwise `each={items()}` / `when={cond()}` snapshot once and never update.
|
|
40
|
-
// The runtime routes these through `resolveReactiveProp`, which also unwraps the accessor
|
|
41
|
-
// form (`each={items}`), so all three shapes work with no compiler-side ambiguity.
|
|
42
|
-
//
|
|
43
|
-
// Excluded on purpose: `Dynamic.component` (a component IS a function, so the value can't be
|
|
44
|
-
// told apart from an accessor — its idiomatic `component={signal}` form already reacts) and
|
|
45
|
-
// `Portal.mount` (read once at mount, not a reactive value).
|
|
46
|
-
const CONTROL_FLOW_VALUE_PROPS = {
|
|
47
|
-
Show: 'when',
|
|
48
|
-
For: 'each',
|
|
49
|
-
Index: 'each',
|
|
50
|
-
Match: 'when',
|
|
51
|
-
};
|
|
52
|
-
const REACTIVE_PATTERNS = {
|
|
53
|
-
signalCall: /\w+\(\)$/,
|
|
54
|
-
storeAccess: /\w+\.\w+/,
|
|
55
|
-
arrowFunction: /^\(\)\s*=>/,
|
|
56
|
-
functionCall: /^function\s*\(/,
|
|
57
|
-
};
|
|
58
|
-
const EVENT_PROPS = /^on[A-Z]/;
|
|
59
|
-
const DELEGATABLE_EVENTS = [
|
|
60
|
-
'click',
|
|
61
|
-
'dblclick',
|
|
62
|
-
'input',
|
|
63
|
-
'change',
|
|
64
|
-
'submit',
|
|
65
|
-
'focus',
|
|
66
|
-
'blur',
|
|
67
|
-
'keydown',
|
|
68
|
-
'keyup',
|
|
69
|
-
'keypress',
|
|
70
|
-
'mousedown',
|
|
71
|
-
'mouseup',
|
|
72
|
-
'mouseover',
|
|
73
|
-
'mouseout',
|
|
74
|
-
'mouseenter',
|
|
75
|
-
'mouseleave',
|
|
76
|
-
];
|
|
77
36
|
// ============================================================================
|
|
78
37
|
// Main Plugin
|
|
79
38
|
// ============================================================================
|
|
80
39
|
export default declare((api, options = {}) => {
|
|
81
40
|
api.assertVersion(7);
|
|
82
|
-
const {
|
|
41
|
+
const { delegateEvents = true, signalModule = '@fluixi/dom', controlFlowModule = '@fluixi/dom', routerModule = '@fluixi/core/router-next', reactiveModule = '@fluixi/reactive/signal', templateClone = true, partialTemplates = true,
|
|
83
42
|
// `both` by default: JSX and html`` are compiled out of the box, so
|
|
84
43
|
// `fluixi()` needs no `format` option. Injected imports are deduped, so a
|
|
85
44
|
// file mixing the two styles won't redeclare a binding.
|
|
@@ -89,8 +48,6 @@ export default declare((api, options = {}) => {
|
|
|
89
48
|
const componentResolver = createComponentResolver(resolveRules);
|
|
90
49
|
const compileLit = format === 'lit' || format === 'both';
|
|
91
50
|
const pluginState = {
|
|
92
|
-
backend,
|
|
93
|
-
codegen,
|
|
94
51
|
irImports: new Set(),
|
|
95
52
|
libModule: '@fluixi/core',
|
|
96
53
|
controlFlowModule,
|
|
@@ -98,9 +55,7 @@ export default declare((api, options = {}) => {
|
|
|
98
55
|
templateClone,
|
|
99
56
|
partialTemplates,
|
|
100
57
|
hasJSX: false,
|
|
101
|
-
hasReactivity: false,
|
|
102
58
|
hasLitHTML: false,
|
|
103
|
-
needsSignalImport: false,
|
|
104
59
|
needsControlFlowImport: false,
|
|
105
60
|
needsCreateMemo: false,
|
|
106
61
|
controlFlowComponents: new Set(),
|
|
@@ -108,7 +63,6 @@ export default declare((api, options = {}) => {
|
|
|
108
63
|
deferredComponents: new Map(),
|
|
109
64
|
componentResolver,
|
|
110
65
|
delegatedEvents: new Set(),
|
|
111
|
-
staticElements: new Map(),
|
|
112
66
|
staticTemplates: new Map(),
|
|
113
67
|
staticCounter: 0,
|
|
114
68
|
};
|
|
@@ -138,10 +92,8 @@ export default declare((api, options = {}) => {
|
|
|
138
92
|
// Reset state for new file
|
|
139
93
|
Object.assign(state, {
|
|
140
94
|
hasJSX: false,
|
|
141
|
-
hasReactivity: false,
|
|
142
95
|
hasLitHTML: false,
|
|
143
96
|
hasLitIR: false,
|
|
144
|
-
needsSignalImport: false,
|
|
145
97
|
needsControlFlowImport: false,
|
|
146
98
|
needsCreateMemo: false,
|
|
147
99
|
controlFlowComponents: new Set(),
|
|
@@ -149,17 +101,13 @@ export default declare((api, options = {}) => {
|
|
|
149
101
|
deferredComponents: new Map(),
|
|
150
102
|
componentResolver,
|
|
151
103
|
delegatedEvents: new Set(),
|
|
152
|
-
staticElements: new Map(),
|
|
153
104
|
staticTemplates: new Map(),
|
|
154
|
-
autoShow: autoShowTransform,
|
|
155
105
|
staticCounter: 0,
|
|
156
106
|
libModule: '@fluixi/core',
|
|
157
107
|
controlFlowModule,
|
|
158
108
|
routerModule,
|
|
159
109
|
templateClone,
|
|
160
110
|
partialTemplates,
|
|
161
|
-
backend,
|
|
162
|
-
codegen,
|
|
163
111
|
irImports: new Set(),
|
|
164
112
|
needsCreateNativeElement: false,
|
|
165
113
|
needsInsert: false,
|
|
@@ -170,19 +118,10 @@ export default declare((api, options = {}) => {
|
|
|
170
118
|
},
|
|
171
119
|
exit(path, state) {
|
|
172
120
|
const imports = [];
|
|
173
|
-
if (!state.hasJSX) {
|
|
174
|
-
// Add signal directive import if needed
|
|
175
|
-
if (state.needsSignalImport) {
|
|
176
|
-
imports.push(t.importDeclaration([
|
|
177
|
-
t.importSpecifier(t.identifier('signal'), t.identifier('signal')),
|
|
178
|
-
], t.stringLiteral(signalModule)));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
121
|
if (state.hasJSX || state.hasLitIR) {
|
|
182
122
|
if (state.irImports.size > 0) {
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// the control-flow module.
|
|
123
|
+
// Import exactly the symbols emit() reported, splitting control-flow
|
|
124
|
+
// components to the control-flow module.
|
|
186
125
|
const CF = new Set([
|
|
187
126
|
'Show', 'For', 'Index', 'Switch', 'Match',
|
|
188
127
|
'Dynamic', 'ErrorBoundary', 'Portal', 'Suspense',
|
|
@@ -208,48 +147,10 @@ export default declare((api, options = {}) => {
|
|
|
208
147
|
imports.push(t.importDeclaration(cf.map(spec), t.stringLiteral(controlFlowModule)));
|
|
209
148
|
}
|
|
210
149
|
}
|
|
211
|
-
if (state.hasJSX && backend === 'imperative' && codegen !== 'ir') {
|
|
212
|
-
// Compiled imperative output: import only the runtime primitives
|
|
213
|
-
// actually emitted, straight from the dom runtime module.
|
|
214
|
-
const want = [
|
|
215
|
-
[!!state.needsCreateNativeElement, 'createNativeElement'],
|
|
216
|
-
[!!state.needsSpread, 'spread'],
|
|
217
|
-
[!!state.needsInsert, 'insert'],
|
|
218
|
-
[!!state.needsCreateComponent, 'createComponent'],
|
|
219
|
-
[!!state.needsMergeProps, 'mergeProps'],
|
|
220
|
-
];
|
|
221
|
-
const specifiers = want
|
|
222
|
-
.filter(([need, name]) => need && !path.scope.hasBinding(name))
|
|
223
|
-
.map(([, name]) => t.importSpecifier(t.identifier(name), t.identifier(name)));
|
|
224
|
-
if (specifiers.length > 0) {
|
|
225
|
-
imports.push(t.importDeclaration(specifiers, t.stringLiteral(signalModule)));
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
else if (state.hasJSX && runtime === 'automatic' && backend !== 'imperative') {
|
|
229
|
-
// Not under the imperative backend: `codegen: 'ir'` lands here because the
|
|
230
|
-
// branch above excludes it, and emitted an unused jsx/jsxs/Fragment import.
|
|
231
|
-
// The IR path produces runtime calls, never JSX factory calls.
|
|
232
|
-
// Add JSX runtime imports
|
|
233
|
-
const specifiers = [
|
|
234
|
-
t.importSpecifier(t.identifier('jsx'), t.identifier('jsx')),
|
|
235
|
-
t.importSpecifier(t.identifier('jsxs'), t.identifier('jsxs')),
|
|
236
|
-
t.importSpecifier(t.identifier('Fragment'), t.identifier('Fragment')),
|
|
237
|
-
];
|
|
238
|
-
if (development) {
|
|
239
|
-
specifiers.push(t.importSpecifier(t.identifier('jsxDEV'), t.identifier('jsxDEV')));
|
|
240
|
-
}
|
|
241
|
-
// Add mergeProps import if spreads are used
|
|
242
|
-
if (state.needsMergeProps) {
|
|
243
|
-
specifiers.push(t.importSpecifier(t.identifier('mergeProps'), t.identifier('mergeProps')));
|
|
244
|
-
}
|
|
245
|
-
imports.push(t.importDeclaration(specifiers, t.stringLiteral(importSource)));
|
|
246
|
-
}
|
|
247
150
|
// createMemo (wraps conditionals/components) is a reactive primitive,
|
|
248
151
|
// so it comes from the reactive module, not dom.
|
|
249
152
|
if (state.needsCreateMemo && !path.scope.hasBinding('createMemo')) {
|
|
250
|
-
imports.push(t.importDeclaration([t.importSpecifier(t.identifier('createMemo'), t.identifier('createMemo'))], t.stringLiteral(
|
|
251
|
-
? reactiveModule
|
|
252
|
-
: state.libModule ?? '@fluixi/core')));
|
|
153
|
+
imports.push(t.importDeclaration([t.importSpecifier(t.identifier('createMemo'), t.identifier('createMemo'))], t.stringLiteral(reactiveModule)));
|
|
253
154
|
}
|
|
254
155
|
// Add control flow imports for auto-transformed components (Show, etc.)
|
|
255
156
|
// Only add components that aren't already bound in the file scope.
|
|
@@ -320,23 +221,13 @@ export default declare((api, options = {}) => {
|
|
|
320
221
|
if (delegateEvents && state.delegatedEvents.size > 0) {
|
|
321
222
|
imports.push(t.importDeclaration([
|
|
322
223
|
t.importSpecifier(t.identifier('delegateEvents'), t.identifier('delegateEvents')),
|
|
323
|
-
], t.stringLiteral(
|
|
224
|
+
], t.stringLiteral(signalModule)));
|
|
324
225
|
// Add delegateEvents call at top of program
|
|
325
226
|
const eventsArray = t.arrayExpression(Array.from(state.delegatedEvents).map((event) => t.stringLiteral(event)));
|
|
326
227
|
const delegateCall = t.expressionStatement(t.callExpression(t.identifier('delegateEvents'), [eventsArray]));
|
|
327
228
|
path.node.body.unshift(delegateCall);
|
|
328
229
|
}
|
|
329
|
-
//
|
|
330
|
-
if (hoistStatics && state.staticElements.size > 0) {
|
|
331
|
-
const hoisted = [];
|
|
332
|
-
state.staticElements.forEach((value, key) => {
|
|
333
|
-
hoisted.push(t.variableDeclaration('const', [
|
|
334
|
-
t.variableDeclarator(t.identifier(key), value),
|
|
335
|
-
]));
|
|
336
|
-
});
|
|
337
|
-
path.node.body.unshift(...hoisted);
|
|
338
|
-
}
|
|
339
|
-
// After the hoisted statics but before imports are unshifted, so the
|
|
230
|
+
// Before imports are unshifted, so the
|
|
340
231
|
// final order is: imports, deferred bindings, everything else.
|
|
341
232
|
if (deferredDecls.length > 0) {
|
|
342
233
|
path.node.body.unshift(...deferredDecls);
|
|
@@ -368,109 +259,24 @@ export default declare((api, options = {}) => {
|
|
|
368
259
|
}
|
|
369
260
|
},
|
|
370
261
|
},
|
|
262
|
+
// Both JSX forms take the one path: build IR, run the imperative backend,
|
|
263
|
+
// splice the emitted code back in. It handles the whole subtree, so nothing
|
|
264
|
+
// below this node needs its own visitor.
|
|
371
265
|
JSXElement(path, state) {
|
|
372
266
|
state.hasJSX = true;
|
|
373
|
-
|
|
374
|
-
// subtree, so we bypass the inline transform entirely.
|
|
375
|
-
if (backend === 'imperative' && codegen === 'ir') {
|
|
376
|
-
path.replaceWith(emitViaIR(path.node, state));
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
const element = path.node;
|
|
380
|
-
const openingElement = element.openingElement;
|
|
381
|
-
// Router components auto-import like control flow, but aren't optimized —
|
|
382
|
-
// just register the name so the import gets injected if it isn't bound. A
|
|
383
|
-
// configured rule takes precedence; registering anyway would inject the
|
|
384
|
-
// router's import alongside the resolved one and bind the name twice.
|
|
385
|
-
if (t.isJSXIdentifier(openingElement.name) &&
|
|
386
|
-
ROUTER_COMPONENTS.has(openingElement.name.name) &&
|
|
387
|
-
!state.componentResolver?.resolve(openingElement.name.name)) {
|
|
388
|
-
state.controlFlowComponents.add(openingElement.name.name);
|
|
389
|
-
state.needsControlFlowImport = true;
|
|
390
|
-
}
|
|
391
|
-
// Check if it's a control flow component
|
|
392
|
-
if (t.isJSXIdentifier(openingElement.name) &&
|
|
393
|
-
CONTROL_FLOW_COMPONENTS.has(openingElement.name.name)) {
|
|
394
|
-
state.controlFlowComponents.add(openingElement.name.name);
|
|
395
|
-
if (optimizeControlFlow) {
|
|
396
|
-
const optimized = optimizeControlFlowComponent(element, state);
|
|
397
|
-
if (optimized) {
|
|
398
|
-
path.replaceWith(optimized);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
// Try to hoist static elements.
|
|
404
|
-
// Not in imperative mode: hoisting a live DOM node would share one node
|
|
405
|
-
// across renders. Static hoisting there needs template-clone (contract v2).
|
|
406
|
-
if (hoistStatics && backend !== 'imperative' && isStaticElement(element, state)) {
|
|
407
|
-
const hoisted = hoistStaticElement(element, state);
|
|
408
|
-
if (hoisted) {
|
|
409
|
-
path.replaceWith(hoisted);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// Transform to JSX runtime calls
|
|
414
|
-
const transformed = transformJSXElement(element, state, {
|
|
415
|
-
runtime,
|
|
416
|
-
pragma,
|
|
417
|
-
development,
|
|
418
|
-
detectReactivity,
|
|
419
|
-
useLitHTML,
|
|
420
|
-
autoShow: autoShowTransform,
|
|
421
|
-
});
|
|
422
|
-
path.replaceWith(transformed);
|
|
267
|
+
path.replaceWith(emitViaIR(path.node, state));
|
|
423
268
|
},
|
|
424
269
|
JSXFragment(path, state) {
|
|
425
270
|
state.hasJSX = true;
|
|
426
|
-
|
|
427
|
-
path.replaceWith(emitViaIR(path.node, state));
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const transformed = transformJSXFragment(path.node, state, {
|
|
431
|
-
runtime,
|
|
432
|
-
pragmaFrag,
|
|
433
|
-
development,
|
|
434
|
-
});
|
|
435
|
-
path.replaceWith(transformed);
|
|
436
|
-
},
|
|
437
|
-
// Detect reactive patterns in code
|
|
438
|
-
CallExpression(path, state) {
|
|
439
|
-
if (!detectReactivity)
|
|
440
|
-
return;
|
|
441
|
-
const callee = path.node.callee;
|
|
442
|
-
if (callee.name === 'handleToggle') {
|
|
443
|
-
// console.log('call express', path);
|
|
444
|
-
}
|
|
445
|
-
// Detect signal creation
|
|
446
|
-
if (t.isIdentifier(callee) &&
|
|
447
|
-
(callee.name === 'createSignal' ||
|
|
448
|
-
callee.name === 'createStore' ||
|
|
449
|
-
callee.name === 'useContext' ||
|
|
450
|
-
callee.name === 'useLocation')) {
|
|
451
|
-
// console.log('call express', callee.name);
|
|
452
|
-
state.hasReactivity = true;
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
// NOTE: JSXExpressionContainer visitor is intentionally a no-op.
|
|
456
|
-
// JSXElement.enter fires BEFORE children are traversed, so by the time
|
|
457
|
-
// Babel would visit nested JSXExpressionContainers the JSXElement has
|
|
458
|
-
// already been replaced via path.replaceWith(). Attribute and children
|
|
459
|
-
// expressions are processed directly in transformAttributeValue /
|
|
460
|
-
// transformChildren above.
|
|
461
|
-
JSXExpressionContainer(_path) {
|
|
462
|
-
// no-op
|
|
271
|
+
path.replaceWith(emitViaIR(path.node, state));
|
|
463
272
|
},
|
|
464
273
|
TaggedTemplateExpression(path, state) {
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// same runtime calls.
|
|
274
|
+
// `html`...`` and `svg`...`` lower through the same IR and the same
|
|
275
|
+
// backend as JSX — one pipeline, two authoring syntaxes.
|
|
468
276
|
if (compileLit &&
|
|
469
|
-
backend === 'imperative' &&
|
|
470
277
|
t.isIdentifier(path.node.tag) &&
|
|
471
278
|
(path.node.tag.name === litTag || path.node.tag.name === 'svg')) {
|
|
472
279
|
path.replaceWith(emitLitViaIR(path.node, state));
|
|
473
|
-
return;
|
|
474
280
|
}
|
|
475
281
|
},
|
|
476
282
|
},
|
|
@@ -479,128 +285,34 @@ export default declare((api, options = {}) => {
|
|
|
479
285
|
// ============================================================================
|
|
480
286
|
// JSX Transformation
|
|
481
287
|
// ============================================================================
|
|
482
|
-
function transformJSXElement(element, state, options) {
|
|
483
|
-
const { runtime, pragma, development, autoShow = true } = options;
|
|
484
|
-
if (runtime === 'automatic') {
|
|
485
|
-
return transformAutomaticElement(element, state, development, autoShow);
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
return transformClassicElement(element, state, pragma);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
// ============================================================================
|
|
492
|
-
// Imperative backend emission
|
|
493
|
-
//
|
|
494
|
-
// Emits direct calls into the @fluixi/dom runtime instead of jsx()/jsxs():
|
|
495
|
-
// native element -> (() => { const _el = createNativeElement(tag);
|
|
496
|
-
// spread({ element: _el, props });
|
|
497
|
-
// insert(_el, children);
|
|
498
|
-
// return _el; })()
|
|
499
|
-
// component -> createMemo(() => createComponent(Comp, props))
|
|
500
|
-
// This matches @fluixi/jsx's component semantics exactly while removing
|
|
501
|
-
// the per-render jsx() dispatch for host elements. Targets RUNTIME_CONTRACT_V1.
|
|
502
|
-
// ============================================================================
|
|
503
|
-
// Build-time SVG detection, kept in lockstep with isSVGElement in the dom runtime.
|
|
504
|
-
const SVG_TAGS = new Set([
|
|
505
|
-
'svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline',
|
|
506
|
-
'ellipse', 'g', 'defs', 'clipPath', 'text',
|
|
507
|
-
]);
|
|
508
|
-
/** Build the props object, merging spreads via mergeProps (reactivity-preserving). */
|
|
509
|
-
function buildPropsObject(props, spreads, state) {
|
|
510
|
-
if (spreads.length > 0) {
|
|
511
|
-
state.needsMergeProps = true;
|
|
512
|
-
return t.callExpression(t.identifier('mergeProps'), [
|
|
513
|
-
...spreads,
|
|
514
|
-
t.objectExpression(props),
|
|
515
|
-
]);
|
|
516
|
-
}
|
|
517
|
-
return t.objectExpression(props);
|
|
518
|
-
}
|
|
519
|
-
/** Wrap children expressions into a single value for `insert` / `children` prop. */
|
|
520
|
-
function childrenValue(children) {
|
|
521
|
-
return children.length === 1 ? children[0] : t.arrayExpression(children);
|
|
522
|
-
}
|
|
523
|
-
function emitImperativeElement(tag, isNative, props, spreads, children, state) {
|
|
524
|
-
if (!isNative) {
|
|
525
|
-
// Component: children go into props as a LAZY getter, so child components are
|
|
526
|
-
// created inside the component's execution (owner nesting for context/Suspense),
|
|
527
|
-
// after the component runs (provider ordering), and lazily (ErrorBoundary catch).
|
|
528
|
-
const allProps = [...props];
|
|
529
|
-
if (children.length > 0) {
|
|
530
|
-
allProps.push(t.objectMethod('get', t.identifier('children'), [], t.blockStatement([t.returnStatement(childrenValue(children))])));
|
|
531
|
-
}
|
|
532
|
-
return emitComponentCall(tag, buildPropsObject(allProps, spreads, state), state);
|
|
533
|
-
}
|
|
534
|
-
// Native element: createNativeElement + spread (attrs only) + insert (children).
|
|
535
|
-
const tagName = tag.value;
|
|
536
|
-
const isSVG = SVG_TAGS.has(tagName);
|
|
537
|
-
const elId = t.identifier('_el$');
|
|
538
|
-
const stmts = [];
|
|
539
|
-
state.needsCreateNativeElement = true;
|
|
540
|
-
const createArgs = [tag];
|
|
541
|
-
if (isSVG)
|
|
542
|
-
createArgs.push(t.booleanLiteral(true));
|
|
543
|
-
stmts.push(t.variableDeclaration('const', [
|
|
544
|
-
t.variableDeclarator(elId, t.callExpression(t.identifier('createNativeElement'), createArgs)),
|
|
545
|
-
]));
|
|
546
|
-
if (props.length > 0 || spreads.length > 0) {
|
|
547
|
-
state.needsSpread = true;
|
|
548
|
-
const spreadProps = [
|
|
549
|
-
t.objectProperty(t.identifier('element'), elId),
|
|
550
|
-
t.objectProperty(t.identifier('props'), buildPropsObject(props, spreads, state)),
|
|
551
|
-
];
|
|
552
|
-
if (isSVG) {
|
|
553
|
-
spreadProps.push(t.objectProperty(t.identifier('isSVG'), t.booleanLiteral(true)));
|
|
554
|
-
}
|
|
555
|
-
stmts.push(t.expressionStatement(t.callExpression(t.identifier('spread'), [t.objectExpression(spreadProps)])));
|
|
556
|
-
}
|
|
557
|
-
// Insert each child individually (like the jsx runtime's applyProps), so a
|
|
558
|
-
// reactive/component child that isn't the only child still gets its own
|
|
559
|
-
// reactive binding. A single insert(el, [array]) does NOT bind function
|
|
560
|
-
// elements sitting inside the array.
|
|
561
|
-
if (children.length > 0) {
|
|
562
|
-
state.needsInsert = true;
|
|
563
|
-
for (const child of children) {
|
|
564
|
-
const isFn = t.isArrowFunctionExpression(child) ||
|
|
565
|
-
t.isFunctionExpression(child) ||
|
|
566
|
-
(t.isCallExpression(child) &&
|
|
567
|
-
t.isIdentifier(child.callee) &&
|
|
568
|
-
child.callee.name === 'createMemo');
|
|
569
|
-
const args = isFn
|
|
570
|
-
? [t.cloneNode(elId), child, t.nullLiteral()]
|
|
571
|
-
: [t.cloneNode(elId), child];
|
|
572
|
-
stmts.push(t.expressionStatement(t.callExpression(t.identifier('insert'), args)));
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
stmts.push(t.returnStatement(elId));
|
|
576
|
-
return t.callExpression(t.arrowFunctionExpression([], t.blockStatement(stmts)), []);
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Emit a component invocation. jsx backend: `jsx(Comp, props)`. Imperative:
|
|
580
|
-
* `createMemo(() => createComponent(Comp, props))` — same shape jsx-runtime
|
|
581
|
-
* produces for components, so it stays reactive when used as a child.
|
|
582
|
-
*/
|
|
583
|
-
/**
|
|
584
|
-
* Read a `load:` attribute's literal value. A `${…}` hole is rejected: the strategy
|
|
585
|
-
* decides how the module is emitted, so it has to be known at compile time.
|
|
586
|
-
*/
|
|
587
|
-
function readLoadDirective(name, attr) {
|
|
588
|
-
let value = null;
|
|
589
|
-
if (attr.value) {
|
|
590
|
-
if (t.isStringLiteral(attr.value))
|
|
591
|
-
value = attr.value.value;
|
|
592
|
-
else {
|
|
593
|
-
throw new LoadDirectiveError(`'${name}' needs a literal value, not an expression — the strategy is compile-time.`);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return parseLoadDirective(name, value);
|
|
597
|
-
}
|
|
598
288
|
/**
|
|
599
289
|
* Two usages of one component in the same file share a binding, so their strategies
|
|
600
290
|
* have to agree. The earlier-loading one wins: the module is in whichever chunk loads
|
|
601
291
|
* first regardless, so deferring the other buys nothing — the same reasoning as the
|
|
602
292
|
* collapse pass, applied within a file.
|
|
603
293
|
*/
|
|
294
|
+
/**
|
|
295
|
+
* What to do with JSX or a template found inside an expression: resolve its
|
|
296
|
+
* component references and record what the module needs, exactly as the
|
|
297
|
+
* top-level node gets. The IR itself is emitted and dropped by the walk, so
|
|
298
|
+
* anything not taken off it here is lost — which is how `{(b) => <Input/>}`
|
|
299
|
+
* came out referring to a component nothing imported.
|
|
300
|
+
*/
|
|
301
|
+
function nestedHook(state) {
|
|
302
|
+
return (ir) => {
|
|
303
|
+
resolveComponentSources(ir, {
|
|
304
|
+
resolver: state.componentResolver,
|
|
305
|
+
modules: {
|
|
306
|
+
controlFlowModule: state.controlFlowModule,
|
|
307
|
+
coreModule: state.libModule,
|
|
308
|
+
routerModule: state.routerModule,
|
|
309
|
+
},
|
|
310
|
+
strategyFor: (node) => node.load,
|
|
311
|
+
});
|
|
312
|
+
collapseStrategies(ir);
|
|
313
|
+
collectRuntimeNeeds(ir, state);
|
|
314
|
+
};
|
|
315
|
+
}
|
|
604
316
|
/** The strategy as an object literal the runtime can read. */
|
|
605
317
|
function strategyLiteral(strategy) {
|
|
606
318
|
const props = [
|
|
@@ -617,84 +329,6 @@ function strategyLiteral(strategy) {
|
|
|
617
329
|
}
|
|
618
330
|
return t.objectExpression(props);
|
|
619
331
|
}
|
|
620
|
-
function mergeStrategy(existing, incoming) {
|
|
621
|
-
if (!isDeferred(existing) || !isDeferred(incoming))
|
|
622
|
-
return EAGER;
|
|
623
|
-
return existing;
|
|
624
|
-
}
|
|
625
|
-
function emitComponentCall(comp, propsObj, state) {
|
|
626
|
-
// Configured tags register here rather than in the JSX element visitor: this is the
|
|
627
|
-
// chokepoint every component passes through, and the visitor missed children because
|
|
628
|
-
// a lowered parent is replaced before its subtree is walked.
|
|
629
|
-
//
|
|
630
|
-
// A configured rule wins over the router built-ins. Those are only an import — no
|
|
631
|
-
// special lowering, no reactive-getter props — so letting a project's own `Link` lose
|
|
632
|
-
// to them silently was the same failure `Switch` has: the tag renders the router's
|
|
633
|
-
// component and nothing reports it. Control flow still wins, because there the name
|
|
634
|
-
// selects codegen and a rule cannot supply that.
|
|
635
|
-
// Control flow needs its import injected too, and registering it here rather than in
|
|
636
|
-
// the JSX visitor is what makes nested usage work: a lowered parent is replaced before
|
|
637
|
-
// its subtree is walked, so `<div><Show/></div>` never reached the visitor and `Show`
|
|
638
|
-
// compiled to a reference to nothing. Every fixture imported Show by hand, so nothing
|
|
639
|
-
// caught it.
|
|
640
|
-
if (t.isIdentifier(comp) && CONTROL_FLOW_COMPONENTS.has(comp.name)) {
|
|
641
|
-
state.controlFlowComponents.add(comp.name);
|
|
642
|
-
state.needsControlFlowImport = true;
|
|
643
|
-
}
|
|
644
|
-
let resolvedByRule = false;
|
|
645
|
-
if (t.isIdentifier(comp) && !CONTROL_FLOW_COMPONENTS.has(comp.name) && state.componentResolver) {
|
|
646
|
-
const resolved = state.componentResolver.resolve(comp.name);
|
|
647
|
-
if (resolved) {
|
|
648
|
-
const strategy = state.pendingLoad ?? EAGER;
|
|
649
|
-
// An eager usage seen earlier in the file already put the module in the chunk,
|
|
650
|
-
// so a later deferred usage of the same name collapses to eager. Without this the
|
|
651
|
-
// file gets both a static import and a `const` for one name — a redeclaration.
|
|
652
|
-
const alreadyEager = state.resolvedComponents.has(comp.name);
|
|
653
|
-
if (isDeferred(strategy) && !alreadyEager) {
|
|
654
|
-
// A deferred component becomes a `deferred(() => import(...))` binding rather
|
|
655
|
-
// than a static import, so the bundler puts it in its own chunk.
|
|
656
|
-
const existing = state.deferredComponents.get(comp.name);
|
|
657
|
-
const merged = existing ? mergeStrategy(existing.strategy, strategy) : strategy;
|
|
658
|
-
if (isDeferred(merged)) {
|
|
659
|
-
state.deferredComponents.set(comp.name, { ...resolved, strategy: merged });
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
state.deferredComponents.delete(comp.name);
|
|
663
|
-
state.resolvedComponents.set(comp.name, resolved);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
// An eager usage anywhere wins: the module is in the chunk already.
|
|
668
|
-
state.deferredComponents.delete(comp.name);
|
|
669
|
-
state.resolvedComponents.set(comp.name, resolved);
|
|
670
|
-
}
|
|
671
|
-
resolvedByRule = true;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
state.pendingLoad = undefined;
|
|
675
|
-
// Router components auto-import like control flow — register the name here so
|
|
676
|
-
// `<Outlet/>` nested in markup is caught, not only a top-level router tag. Unbound
|
|
677
|
-
// names are injected at Program.exit.
|
|
678
|
-
if (!resolvedByRule && t.isIdentifier(comp) && ROUTER_COMPONENTS.has(comp.name)) {
|
|
679
|
-
state.controlFlowComponents.add(comp.name);
|
|
680
|
-
state.needsControlFlowImport = true;
|
|
681
|
-
}
|
|
682
|
-
if (state.backend === 'imperative') {
|
|
683
|
-
state.needsCreateComponent = true;
|
|
684
|
-
state.needsCreateMemo = true;
|
|
685
|
-
return t.callExpression(t.identifier('createMemo'), [
|
|
686
|
-
t.arrowFunctionExpression([], t.callExpression(t.identifier('createComponent'), [comp, propsObj])),
|
|
687
|
-
]);
|
|
688
|
-
}
|
|
689
|
-
return t.callExpression(t.identifier('jsx'), [comp, propsObj]);
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* IR-pipeline path (codegen: 'ir'): build host-agnostic IR from the JSX node, run
|
|
693
|
-
* the imperative backend's emit(), and reparse the emitted code into an AST
|
|
694
|
-
* expression. Records the runtime symbols used so Program.exit can import them.
|
|
695
|
-
* Produces the same runtime calls as the inline path — this is the route a future
|
|
696
|
-
* swc front-end reuses.
|
|
697
|
-
*/
|
|
698
332
|
/**
|
|
699
333
|
* Hoist the backend's static templates to module scope and rewrite the emitted code to
|
|
700
334
|
* reference them. Deduped by HTML, so a subtree repeated in a file is parsed once.
|
|
@@ -722,7 +356,7 @@ function emitViaIR(node, state) {
|
|
|
722
356
|
// `used` collects runtime symbols emitted for JSX nested inside expressions
|
|
723
357
|
// (render-props, ternaries, fallback={<p/>}, …) so we import them too.
|
|
724
358
|
const used = new Set();
|
|
725
|
-
const ir = buildIR(node, used);
|
|
359
|
+
const ir = buildIR(node, used, nestedHook(state));
|
|
726
360
|
// The same passes the lit path runs. Without them `codegen: 'ir'` silently skipped
|
|
727
361
|
// resolution entirely — a resolved tag compiled to a reference with no import.
|
|
728
362
|
resolveComponentSources(ir, {
|
|
@@ -785,7 +419,7 @@ function dedupeImports(imports) {
|
|
|
785
419
|
function emitLitViaIR(node, state) {
|
|
786
420
|
const used = new Set();
|
|
787
421
|
const svg = t.isIdentifier(node.tag) && node.tag.name === 'svg';
|
|
788
|
-
const ir = buildIRFromLit(node, used, { svg });
|
|
422
|
+
const ir = buildIRFromLit(node, used, { svg, onNested: nestedHook(state) });
|
|
789
423
|
// Resolve pass: annotate each component reference with where it comes from, before
|
|
790
424
|
// codegen. Bindings are not checked here — Program.exit knows the whole file's scope
|
|
791
425
|
// and drops any injected import whose name turns out to be bound.
|
|
@@ -897,870 +531,3 @@ function collectRuntimeNeeds(node, state) {
|
|
|
897
531
|
collectRuntimeNeeds(c, state);
|
|
898
532
|
}
|
|
899
533
|
}
|
|
900
|
-
//This method will convert props to lazy props like
|
|
901
|
-
function lazyProps(properties) {
|
|
902
|
-
return properties.map((prop) => {
|
|
903
|
-
// Only transform normal properties (e.g., { open: variable })
|
|
904
|
-
// Skip methods, spread elements, or already existing getters
|
|
905
|
-
if (t.isObjectProperty(prop)) {
|
|
906
|
-
// Handle potential computed properties (like {[someKey]: variable})
|
|
907
|
-
const key = prop.key;
|
|
908
|
-
const computed = prop.computed;
|
|
909
|
-
const value = prop.value;
|
|
910
|
-
// Create a getter method: get key() { return value; }
|
|
911
|
-
return t.objectMethod('get', // Kind of method
|
|
912
|
-
key, // Property key
|
|
913
|
-
[], // Getters take no arguments
|
|
914
|
-
t.blockStatement([
|
|
915
|
-
// Body: { return value; }
|
|
916
|
-
t.returnStatement(value),
|
|
917
|
-
]), computed // Preserve computed status if true
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
// Keep existing ObjectMethods (like get already there, or regular methods) as-is
|
|
921
|
-
return prop;
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
;
|
|
925
|
-
function transformAutomaticElement(element, state, development, autoShow = true) {
|
|
926
|
-
const openingElement = element.openingElement;
|
|
927
|
-
const children = element.children;
|
|
928
|
-
// Get element name/tag
|
|
929
|
-
const tag = getElementName(openingElement.name);
|
|
930
|
-
// Only auto-wrap reactive expressions for native HTML elements (lowercase tag).
|
|
931
|
-
// Component props must NOT be wrapped — the component receives the value
|
|
932
|
-
// as-is and is responsible for its own reactivity.
|
|
933
|
-
const isNativeElement = t.isJSXIdentifier(openingElement.name) &&
|
|
934
|
-
/^[a-z]/.test(openingElement.name.name);
|
|
935
|
-
// Transform attributes to props object
|
|
936
|
-
const attrs = transformAttributes(openingElement.attributes, state, isNativeElement, t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '');
|
|
937
|
-
let props = attrs.props;
|
|
938
|
-
const spreads = attrs.spreads;
|
|
939
|
-
// Component props become lazy getters so the component is created ONCE and reactivity flows
|
|
940
|
-
// through the getter (a reactive `prop={sig()}` read eagerly would re-create the whole
|
|
941
|
-
// component via the enclosing createMemo, so a <Portal> re-mounts and piles up). Static props
|
|
942
|
-
// as getters are harmless. Native-element attrs must NOT be wrapped: they already carry their
|
|
943
|
-
// own reactivity as `() => expr` thunks that `spread` consumes.
|
|
944
|
-
if (!isNativeElement)
|
|
945
|
-
props = lazyProps(props);
|
|
946
|
-
// Transform children
|
|
947
|
-
const transformedChildren = transformChildren(children, state, autoShow);
|
|
948
|
-
// Imperative backend: emit direct runtime calls instead of jsx()/jsxs().
|
|
949
|
-
if (state.backend === 'imperative') {
|
|
950
|
-
return emitImperativeElement(tag, isNativeElement, props, spreads, transformedChildren, state);
|
|
951
|
-
}
|
|
952
|
-
// Add children to props if any
|
|
953
|
-
if (transformedChildren.length > 0) {
|
|
954
|
-
const childrenProp = transformedChildren.length === 1
|
|
955
|
-
? transformedChildren[0]
|
|
956
|
-
: t.arrayExpression(transformedChildren);
|
|
957
|
-
props.push(t.objectProperty(t.identifier('children'), childrenProp));
|
|
958
|
-
}
|
|
959
|
-
// Build props object with spreads
|
|
960
|
-
let propsObj;
|
|
961
|
-
if (spreads.length > 0) {
|
|
962
|
-
// Use mergeProps helper to preserve reactivity for store proxies
|
|
963
|
-
const baseProps = props.length > 0 ? t.objectExpression(props) : t.objectExpression([]);
|
|
964
|
-
// Import mergeProps from jsx-runtime if spreads are used
|
|
965
|
-
state.needsMergeProps = true;
|
|
966
|
-
propsObj = t.callExpression(t.identifier('mergeProps'), [
|
|
967
|
-
...spreads,
|
|
968
|
-
baseProps,
|
|
969
|
-
]);
|
|
970
|
-
}
|
|
971
|
-
else {
|
|
972
|
-
propsObj = t.objectExpression(props);
|
|
973
|
-
}
|
|
974
|
-
// Determine function name
|
|
975
|
-
const functionName = development
|
|
976
|
-
? 'jsxDEV'
|
|
977
|
-
: transformedChildren.length > 1
|
|
978
|
-
? 'jsxs'
|
|
979
|
-
: 'jsx';
|
|
980
|
-
// Build jsx call
|
|
981
|
-
const args = [tag, propsObj];
|
|
982
|
-
if (development) {
|
|
983
|
-
// Add key, isStaticChildren, source, self for dev mode
|
|
984
|
-
args.push(t.identifier('undefined'), // key
|
|
985
|
-
t.booleanLiteral(false), // isStaticChildren
|
|
986
|
-
t.identifier('undefined'), // source
|
|
987
|
-
t.identifier('undefined') // self
|
|
988
|
-
);
|
|
989
|
-
}
|
|
990
|
-
return t.callExpression(t.identifier(functionName), args);
|
|
991
|
-
}
|
|
992
|
-
function transformClassicElement(element, state, pragma) {
|
|
993
|
-
const openingElement = element.openingElement;
|
|
994
|
-
const children = element.children;
|
|
995
|
-
const tag = getElementName(openingElement.name);
|
|
996
|
-
const isNativeElement = t.isJSXIdentifier(openingElement.name) &&
|
|
997
|
-
/^[a-z]/.test(openingElement.name.name);
|
|
998
|
-
const { props, spreads } = transformAttributes(openingElement.attributes, state, isNativeElement, t.isJSXIdentifier(openingElement.name) ? openingElement.name.name : '');
|
|
999
|
-
const transformedChildren = transformChildren(children, state, state.autoShow ?? true);
|
|
1000
|
-
let propsObj;
|
|
1001
|
-
if (spreads.length > 0) {
|
|
1002
|
-
// Use mergeProps helper to preserve reactivity for store proxies
|
|
1003
|
-
// mergeProps(...spreads, baseProps) instead of Object.assign
|
|
1004
|
-
const baseProps = props.length > 0 ? t.objectExpression(props) : t.objectExpression([]);
|
|
1005
|
-
// Import mergeProps from jsx-runtime if spreads are used
|
|
1006
|
-
state.needsMergeProps = true;
|
|
1007
|
-
propsObj = t.callExpression(t.identifier('mergeProps'), [
|
|
1008
|
-
...spreads,
|
|
1009
|
-
baseProps,
|
|
1010
|
-
]);
|
|
1011
|
-
}
|
|
1012
|
-
else {
|
|
1013
|
-
propsObj = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
|
|
1014
|
-
}
|
|
1015
|
-
const args = [tag, propsObj, ...transformedChildren];
|
|
1016
|
-
return t.callExpression(t.identifier(pragma), args);
|
|
1017
|
-
}
|
|
1018
|
-
function transformJSXFragment(fragment, state, options) {
|
|
1019
|
-
const { runtime, pragmaFrag, development } = options;
|
|
1020
|
-
const children = transformChildren(fragment.children, state, state.autoShow ?? true);
|
|
1021
|
-
// Imperative backend: a fragment is just its children; insert() flattens arrays.
|
|
1022
|
-
if (state.backend === 'imperative') {
|
|
1023
|
-
if (children.length === 0)
|
|
1024
|
-
return t.nullLiteral();
|
|
1025
|
-
return childrenValue(children);
|
|
1026
|
-
}
|
|
1027
|
-
if (runtime === 'automatic') {
|
|
1028
|
-
const functionName = development
|
|
1029
|
-
? 'jsxDEV'
|
|
1030
|
-
: children.length > 1
|
|
1031
|
-
? 'jsxs'
|
|
1032
|
-
: 'jsx';
|
|
1033
|
-
const props = t.objectExpression([
|
|
1034
|
-
t.objectProperty(t.identifier('children'), children.length === 1 ? children[0] : t.arrayExpression(children)),
|
|
1035
|
-
]);
|
|
1036
|
-
const args = [t.identifier('Fragment'), props];
|
|
1037
|
-
if (development) {
|
|
1038
|
-
args.push(t.identifier('undefined'), t.booleanLiteral(false), t.identifier('undefined'), t.identifier('undefined'));
|
|
1039
|
-
}
|
|
1040
|
-
return t.callExpression(t.identifier(functionName), args);
|
|
1041
|
-
}
|
|
1042
|
-
else {
|
|
1043
|
-
return t.callExpression(t.identifier(pragmaFrag), children);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
// ============================================================================
|
|
1047
|
-
// Element Name Transformation
|
|
1048
|
-
// ============================================================================
|
|
1049
|
-
function getElementName(name) {
|
|
1050
|
-
if (t.isJSXIdentifier(name)) {
|
|
1051
|
-
const nameStr = name.name;
|
|
1052
|
-
// Check if it's a native element (lowercase)
|
|
1053
|
-
if (nameStr[0] === nameStr[0].toLowerCase()) {
|
|
1054
|
-
return t.stringLiteral(nameStr);
|
|
1055
|
-
}
|
|
1056
|
-
// Component reference
|
|
1057
|
-
return t.identifier(nameStr);
|
|
1058
|
-
}
|
|
1059
|
-
if (t.isJSXMemberExpression(name)) {
|
|
1060
|
-
return transformJSXMemberExpression(name);
|
|
1061
|
-
}
|
|
1062
|
-
if (t.isJSXNamespacedName(name)) {
|
|
1063
|
-
return t.stringLiteral(`${name.namespace.name}:${name.name.name}`);
|
|
1064
|
-
}
|
|
1065
|
-
return t.stringLiteral('div');
|
|
1066
|
-
}
|
|
1067
|
-
function transformJSXMemberExpression(expr) {
|
|
1068
|
-
let object;
|
|
1069
|
-
if (t.isJSXIdentifier(expr.object)) {
|
|
1070
|
-
object = t.identifier(expr.object.name);
|
|
1071
|
-
}
|
|
1072
|
-
else {
|
|
1073
|
-
object = transformJSXMemberExpression(expr.object);
|
|
1074
|
-
}
|
|
1075
|
-
return t.memberExpression(object, t.identifier(expr.property.name));
|
|
1076
|
-
}
|
|
1077
|
-
// ============================================================================
|
|
1078
|
-
// Attribute Transformation
|
|
1079
|
-
// ============================================================================
|
|
1080
|
-
/** Returns true when `name` can be used as an unquoted JS identifier. */
|
|
1081
|
-
function isValidJSIdentifier(name) {
|
|
1082
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
1083
|
-
}
|
|
1084
|
-
function transformAttributes(attributes, state, isNativeElement = true, tagName = '') {
|
|
1085
|
-
const props = [];
|
|
1086
|
-
const spreads = [];
|
|
1087
|
-
const cfValueProp = CONTROL_FLOW_VALUE_PROPS[tagName];
|
|
1088
|
-
// `use:name` directives, collected into the single `use` prop the runtime dispatches on.
|
|
1089
|
-
const usePairs = [];
|
|
1090
|
-
// `load:` is a directive, not a prop — it says when this component's module is
|
|
1091
|
-
// fetched. Cleared per element so a strategy cannot leak onto the next tag.
|
|
1092
|
-
state.pendingLoad = undefined;
|
|
1093
|
-
for (const attr of attributes) {
|
|
1094
|
-
if (t.isJSXSpreadAttribute(attr)) {
|
|
1095
|
-
// Collect spread expressions separately
|
|
1096
|
-
spreads.push(attr.argument);
|
|
1097
|
-
}
|
|
1098
|
-
else if (t.isJSXAttribute(attr)) {
|
|
1099
|
-
const name = getAttributeName(attr.name);
|
|
1100
|
-
if (isLoadDirective(name)) {
|
|
1101
|
-
// Same parser the html`` lowering uses, so the two syntaxes cannot drift.
|
|
1102
|
-
state.pendingLoad = readLoadDirective(name, attr);
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
// use:name / use:name={opts} -> one `use` prop holding [directive, () => opts] pairs.
|
|
1106
|
-
// Without this the attribute reached the runtime as a prop literally named "use:name",
|
|
1107
|
-
// which nothing dispatches on — so the directive silently never ran. The IR path has
|
|
1108
|
-
// always normalised it; this is the same normalisation for the inline path.
|
|
1109
|
-
if (t.isJSXNamespacedName(attr.name) && attr.name.namespace.name === 'use') {
|
|
1110
|
-
const directive = t.identifier(attr.name.name.name);
|
|
1111
|
-
const opts = attr.value === null ? null : transformAttributeValue(attr.value, state, name, isNativeElement);
|
|
1112
|
-
usePairs.push(t.arrayExpression(
|
|
1113
|
-
// The options are wrapped in an arrow so the directive can track them, matching
|
|
1114
|
-
// what the runtime's applyUse expects of the second slot.
|
|
1115
|
-
opts ? [directive, t.arrowFunctionExpression([], opts)] : [directive]));
|
|
1116
|
-
continue;
|
|
1117
|
-
}
|
|
1118
|
-
const value = transformAttributeValue(attr.value, state, name, isNativeElement);
|
|
1119
|
-
// Detect event handlers for delegation
|
|
1120
|
-
if (EVENT_PROPS.test(name)) {
|
|
1121
|
-
const eventName = name.slice(2).toLowerCase();
|
|
1122
|
-
if (DELEGATABLE_EVENTS.includes(eventName)) {
|
|
1123
|
-
state.delegatedEvents.add(eventName);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
const key = isValidJSIdentifier(name)
|
|
1127
|
-
? t.identifier(name)
|
|
1128
|
-
: t.stringLiteral(name);
|
|
1129
|
-
// Inline JSX passed to a COMPONENT prop (e.g. <Suspense fallback={<div/>}>) becomes a
|
|
1130
|
-
// LAZY getter — same as children. Otherwise it's an eager IIFE built when the props object
|
|
1131
|
-
// is constructed; during hydration that walks the hydration cursor and adopts the real
|
|
1132
|
-
// server node (a fallback `<div>` matches the content `<div>` by tag), duplicating the
|
|
1133
|
-
// tree. As a getter the node is only built when the component actually reads the prop.
|
|
1134
|
-
const raw = attr.value;
|
|
1135
|
-
const isInlineJsx = t.isJSXElement(raw) ||
|
|
1136
|
-
t.isJSXFragment(raw) ||
|
|
1137
|
-
(t.isJSXExpressionContainer(raw) &&
|
|
1138
|
-
(t.isJSXElement(raw.expression) || t.isJSXFragment(raw.expression)));
|
|
1139
|
-
// Control-flow reactive value prop (each/when/component/mount): emit as a getter so
|
|
1140
|
-
// reading it re-runs the caller's expression inside the component's tracking scope.
|
|
1141
|
-
const isCfValueProp = !isNativeElement && name === cfValueProp;
|
|
1142
|
-
// A reactive component prop (`prop={sig()}`, `prop={store.x}`) must be a getter, not an
|
|
1143
|
-
// eager value: eager reads the signal inside the enclosing `createMemo(() =>
|
|
1144
|
-
// createComponent(Comp, props))`, so the memo re-creates the WHOLE component on every
|
|
1145
|
-
// change (a <Portal> then re-mounts and piles up). As a getter the component is created
|
|
1146
|
-
// once and reactivity flows through the getter into the component's own effects.
|
|
1147
|
-
const rawExpr = t.isJSXExpressionContainer(raw) && !t.isJSXEmptyExpression(raw.expression)
|
|
1148
|
-
? raw.expression
|
|
1149
|
-
: null;
|
|
1150
|
-
const isReactiveComponentProp = !isNativeElement && !!rawExpr && shouldAutoWrapAttrExpression(rawExpr, name);
|
|
1151
|
-
if ((!isNativeElement && isInlineJsx) || isCfValueProp || isReactiveComponentProp) {
|
|
1152
|
-
props.push(t.objectMethod('get', key, [], t.blockStatement([t.returnStatement(value)])));
|
|
1153
|
-
}
|
|
1154
|
-
else {
|
|
1155
|
-
props.push(t.objectProperty(key, value));
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
if (usePairs.length > 0) {
|
|
1160
|
-
props.push(t.objectProperty(t.identifier('use'), t.arrayExpression(usePairs)));
|
|
1161
|
-
}
|
|
1162
|
-
return { props, spreads };
|
|
1163
|
-
}
|
|
1164
|
-
function getAttributeName(name) {
|
|
1165
|
-
if (t.isJSXIdentifier(name)) {
|
|
1166
|
-
return name.name === 'class' ? 'className' : name.name;
|
|
1167
|
-
}
|
|
1168
|
-
if (t.isJSXNamespacedName(name)) {
|
|
1169
|
-
return `${name.namespace.name}:${name.name.name}`;
|
|
1170
|
-
}
|
|
1171
|
-
return 'unknown';
|
|
1172
|
-
}
|
|
1173
|
-
function transformAttributeValue(value, state, attrName = '', isNativeElement = true) {
|
|
1174
|
-
if (value === null) {
|
|
1175
|
-
return t.booleanLiteral(true);
|
|
1176
|
-
}
|
|
1177
|
-
if (t.isStringLiteral(value)) {
|
|
1178
|
-
// JSX allows string attribute values to span multiple lines, but JS string
|
|
1179
|
-
// literals cannot contain raw newlines. When multi-line, normalise: collapse
|
|
1180
|
-
// any run of whitespace (including newlines) to a single space and trim.
|
|
1181
|
-
// We create a *new* StringLiteral so Babel discards the original extra.raw
|
|
1182
|
-
// (which still contains the literal newlines) and generates a clean literal.
|
|
1183
|
-
if (value.value.includes('\n')) {
|
|
1184
|
-
return t.stringLiteral(value.value.replace(/\s+/g, ' ').trim());
|
|
1185
|
-
}
|
|
1186
|
-
return value;
|
|
1187
|
-
}
|
|
1188
|
-
if (t.isJSXExpressionContainer(value)) {
|
|
1189
|
-
if (t.isJSXEmptyExpression(value.expression)) {
|
|
1190
|
-
return t.booleanLiteral(true);
|
|
1191
|
-
}
|
|
1192
|
-
const expr = value.expression;
|
|
1193
|
-
// Auto-wrap reactive expressions so DOM attribute reads happen inside effects.
|
|
1194
|
-
// Only applies to native HTML elements — component props must NOT be wrapped
|
|
1195
|
-
// because the component receives the value directly and calling it would give
|
|
1196
|
-
// () => value instead of value.
|
|
1197
|
-
if (isNativeElement && shouldAutoWrapAttrExpression(expr, attrName)) {
|
|
1198
|
-
return t.arrowFunctionExpression([], expr);
|
|
1199
|
-
}
|
|
1200
|
-
return expr;
|
|
1201
|
-
}
|
|
1202
|
-
if (t.isJSXElement(value)) {
|
|
1203
|
-
return transformAutomaticElement(value, state, false, state.autoShow ?? true);
|
|
1204
|
-
}
|
|
1205
|
-
if (t.isJSXFragment(value)) {
|
|
1206
|
-
return transformJSXFragment(value, state, {
|
|
1207
|
-
runtime: 'automatic',
|
|
1208
|
-
pragmaFrag: 'Fragment',
|
|
1209
|
-
development: false,
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
return t.booleanLiteral(true);
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Decide whether a JSX attribute value expression should be auto-wrapped
|
|
1216
|
-
* in `() => expr` for fine-grained reactivity (SolidJS style).
|
|
1217
|
-
*/
|
|
1218
|
-
function shouldAutoWrapAttrExpression(expr, attrName) {
|
|
1219
|
-
// Event handlers must NOT be wrapped — they are called by the runtime.
|
|
1220
|
-
// Covers both camelCase (onClick) and namespaced (on:click) forms.
|
|
1221
|
-
if (EVENT_PROPS.test(attrName) || attrName.startsWith('on:')) {
|
|
1222
|
-
return false;
|
|
1223
|
-
}
|
|
1224
|
-
// ref receives the DOM element directly — never wrap.
|
|
1225
|
-
if (attrName === 'ref') {
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
// use:Directive — the directive itself decides how to handle the value.
|
|
1229
|
-
if (attrName.startsWith('use:')) {
|
|
1230
|
-
return false;
|
|
1231
|
-
}
|
|
1232
|
-
// Already an arrow function or function expression — already reactive.
|
|
1233
|
-
if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) {
|
|
1234
|
-
return false;
|
|
1235
|
-
}
|
|
1236
|
-
// Plain identifier — may itself be a signal accessor/function; let runtime handle it.
|
|
1237
|
-
if (t.isIdentifier(expr)) {
|
|
1238
|
-
return false;
|
|
1239
|
-
}
|
|
1240
|
-
// Static literals need no wrapping.
|
|
1241
|
-
if (t.isStringLiteral(expr) ||
|
|
1242
|
-
t.isNumericLiteral(expr) ||
|
|
1243
|
-
t.isBooleanLiteral(expr) ||
|
|
1244
|
-
t.isNullLiteral(expr)) {
|
|
1245
|
-
return false;
|
|
1246
|
-
}
|
|
1247
|
-
// Call expressions: signal(), count(), store.method()
|
|
1248
|
-
if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) {
|
|
1249
|
-
return true;
|
|
1250
|
-
}
|
|
1251
|
-
// Member expressions: store.prop, obj.value
|
|
1252
|
-
if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
|
|
1253
|
-
return true;
|
|
1254
|
-
}
|
|
1255
|
-
// Compound expressions that may contain reactive reads.
|
|
1256
|
-
if (t.isLogicalExpression(expr) ||
|
|
1257
|
-
t.isConditionalExpression(expr) ||
|
|
1258
|
-
t.isBinaryExpression(expr) ||
|
|
1259
|
-
t.isUnaryExpression(expr)) {
|
|
1260
|
-
return true;
|
|
1261
|
-
}
|
|
1262
|
-
// Template literals: `Hello ${name()}`
|
|
1263
|
-
if (t.isTemplateLiteral(expr)) {
|
|
1264
|
-
return true;
|
|
1265
|
-
}
|
|
1266
|
-
// Object expressions: classList={{ active: isActive() }}
|
|
1267
|
-
if (t.isObjectExpression(expr)) {
|
|
1268
|
-
return true;
|
|
1269
|
-
}
|
|
1270
|
-
// Array expressions: style={[baseStyle, dynamicStyle()]}
|
|
1271
|
-
if (t.isArrayExpression(expr)) {
|
|
1272
|
-
return true;
|
|
1273
|
-
}
|
|
1274
|
-
return false;
|
|
1275
|
-
}
|
|
1276
|
-
// ============================================================================
|
|
1277
|
-
// Children Transformation
|
|
1278
|
-
// ============================================================================
|
|
1279
|
-
/**
|
|
1280
|
-
* Canonical JSX text cleaning (matches babel/React/Solid): trim line-boundary
|
|
1281
|
-
* whitespace, collapse runs to single spaces, and drop text that is only
|
|
1282
|
-
* whitespace spanning a newline. Returns '' when the node is insignificant.
|
|
1283
|
-
*/
|
|
1284
|
-
function cleanJSXText(value) {
|
|
1285
|
-
const lines = value.split(/\r\n|\n|\r/);
|
|
1286
|
-
let lastNonEmpty = 0;
|
|
1287
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1288
|
-
if (/[^ \t]/.test(lines[i]))
|
|
1289
|
-
lastNonEmpty = i;
|
|
1290
|
-
}
|
|
1291
|
-
let out = '';
|
|
1292
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1293
|
-
let line = lines[i].replace(/\t/g, ' ');
|
|
1294
|
-
if (i !== 0)
|
|
1295
|
-
line = line.replace(/^ +/, '');
|
|
1296
|
-
if (i !== lines.length - 1)
|
|
1297
|
-
line = line.replace(/ +$/, '');
|
|
1298
|
-
if (!line)
|
|
1299
|
-
continue;
|
|
1300
|
-
if (i !== lastNonEmpty)
|
|
1301
|
-
line += ' ';
|
|
1302
|
-
out += line;
|
|
1303
|
-
}
|
|
1304
|
-
return out;
|
|
1305
|
-
}
|
|
1306
|
-
function transformChildren(children, state, autoShow = true) {
|
|
1307
|
-
const result = [];
|
|
1308
|
-
for (const child of children) {
|
|
1309
|
-
if (t.isJSXText(child)) {
|
|
1310
|
-
// Canonical JSX whitespace handling (as React/Solid): whitespace-only text
|
|
1311
|
-
// that spans a line break is insignificant and dropped; inline runs collapse
|
|
1312
|
-
// to a single space. This matters beyond cosmetics — keeping a `" "` text
|
|
1313
|
-
// node next to a child turns `props.children` into an ARRAY, and an array
|
|
1314
|
-
// insert doesn't reactively bind a memo/component sitting inside it (so a
|
|
1315
|
-
// nested lazy/<Suspense> never updates). Dropping the stray whitespace keeps
|
|
1316
|
-
// a lone child a lone child, so insert binds it reactively.
|
|
1317
|
-
const text = cleanJSXText(child.value);
|
|
1318
|
-
if (text)
|
|
1319
|
-
result.push(t.stringLiteral(text));
|
|
1320
|
-
}
|
|
1321
|
-
else if (t.isJSXExpressionContainer(child)) {
|
|
1322
|
-
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
1323
|
-
const expr = child.expression;
|
|
1324
|
-
// Auto-transform patterns into jsx(Show, ...) so JSX subtrees are
|
|
1325
|
-
// created once and never recreated on every signal change.
|
|
1326
|
-
//
|
|
1327
|
-
// Handled patterns:
|
|
1328
|
-
// () => cond && <JSX /> — arrow logical
|
|
1329
|
-
// () => cond ? <JSX /> : fallback — arrow ternary
|
|
1330
|
-
// cond && <JSX /> — bare logical
|
|
1331
|
-
// cond ? <JSX /> : fallback — bare ternary
|
|
1332
|
-
if (autoShow) {
|
|
1333
|
-
const showNode = tryTransformArrowToShow(expr, state) ??
|
|
1334
|
-
tryTransformRawToShow(expr, state);
|
|
1335
|
-
if (showNode) {
|
|
1336
|
-
result.push(showNode);
|
|
1337
|
-
continue;
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
else {
|
|
1341
|
-
// autoShow off: wrap JSX conditional expressions in createMemo(() => expr)
|
|
1342
|
-
// so the condition is still evaluated reactively without recreating the tree.
|
|
1343
|
-
const memoNode = tryWrapConditionalAsMemo(expr, state);
|
|
1344
|
-
if (memoNode) {
|
|
1345
|
-
result.push(memoNode);
|
|
1346
|
-
continue;
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
// Auto-wrap reactive expressions for SolidJS-style reactivity
|
|
1350
|
-
if (shouldAutoWrapExpression(expr)) {
|
|
1351
|
-
result.push(t.arrowFunctionExpression([], expr));
|
|
1352
|
-
}
|
|
1353
|
-
else {
|
|
1354
|
-
result.push(expr);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
else if (t.isJSXElement(child)) {
|
|
1359
|
-
result.push(transformAutomaticElement(child, state, false, state.autoShow ?? true));
|
|
1360
|
-
}
|
|
1361
|
-
else if (t.isJSXFragment(child)) {
|
|
1362
|
-
result.push(transformJSXFragment(child, state, {
|
|
1363
|
-
runtime: 'automatic',
|
|
1364
|
-
pragmaFrag: 'Fragment',
|
|
1365
|
-
development: false,
|
|
1366
|
-
}));
|
|
1367
|
-
}
|
|
1368
|
-
else if (t.isJSXSpreadChild(child)) {
|
|
1369
|
-
result.push(child.expression);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
return result;
|
|
1373
|
-
}
|
|
1374
|
-
// ============================================================================
|
|
1375
|
-
// Auto-Show Transform
|
|
1376
|
-
// ============================================================================
|
|
1377
|
-
/**
|
|
1378
|
-
* Detects the patterns:
|
|
1379
|
-
* () => condition && <JSX />
|
|
1380
|
-
* () => condition ? <JSX /> : null
|
|
1381
|
-
* () => condition ? <JSX /> : <Fallback />
|
|
1382
|
-
*
|
|
1383
|
-
* and rewrites them to:
|
|
1384
|
-
* jsx(Show, { when: () => condition, children: jsx(...), fallback? })
|
|
1385
|
-
*
|
|
1386
|
-
* This prevents the JSX subtree from being recreated on every reactive update.
|
|
1387
|
-
* Show's memo only re-evaluates when the boolean result of `condition` changes.
|
|
1388
|
-
*/
|
|
1389
|
-
function tryTransformArrowToShow(expr, state) {
|
|
1390
|
-
if (!t.isArrowFunctionExpression(expr) ||
|
|
1391
|
-
expr.params.length !== 0 ||
|
|
1392
|
-
expr.async) {
|
|
1393
|
-
return null;
|
|
1394
|
-
}
|
|
1395
|
-
const body = expr.body;
|
|
1396
|
-
// Pattern 1: () => condition && <JSX /> (or () => condition && (() => <JSX />))
|
|
1397
|
-
if (t.isLogicalExpression(body) &&
|
|
1398
|
-
body.operator === '&&') {
|
|
1399
|
-
const jsx = getJSXContent(body.right);
|
|
1400
|
-
if (jsx) {
|
|
1401
|
-
return buildShowCall(unwrapZeroArgArrow(body.left), jsx, null, state);
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
// Pattern 2: () => condition ? <JSX /> : null | undefined | false
|
|
1405
|
-
// Pattern 3: () => condition ? <JSX /> : <Fallback />
|
|
1406
|
-
// Pattern 4: () => condition ? <JSX /> : otherCond ? <JSX2 /> : <Fallback /> (nested ternary)
|
|
1407
|
-
if (t.isConditionalExpression(body)) {
|
|
1408
|
-
const jsx = getJSXContent(body.consequent);
|
|
1409
|
-
if (jsx) {
|
|
1410
|
-
const fallback = buildFallback(body.alternate, state);
|
|
1411
|
-
return buildShowCall(unwrapZeroArgArrow(body.test), jsx, fallback, state, true);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
return null;
|
|
1415
|
-
}
|
|
1416
|
-
/**
|
|
1417
|
-
* Handles bare (non-arrow-wrapped) ternary and logical JSX expressions:
|
|
1418
|
-
*
|
|
1419
|
-
* cond && <JSX />
|
|
1420
|
-
* cond ? <JSX /> : fallback
|
|
1421
|
-
*
|
|
1422
|
-
* These are transformed to `jsx(Show, { when: () => cond, ... })` exactly like
|
|
1423
|
-
* their arrow-wrapped counterparts, preventing JSX subtrees from being
|
|
1424
|
-
* recreated on every reactive update.
|
|
1425
|
-
*/
|
|
1426
|
-
function tryTransformRawToShow(expr, state) {
|
|
1427
|
-
// cond && <JSX /> (or cond && (() => <JSX />))
|
|
1428
|
-
if (t.isLogicalExpression(expr) && expr.operator === '&&') {
|
|
1429
|
-
const jsx = getJSXContent(expr.right);
|
|
1430
|
-
if (jsx) {
|
|
1431
|
-
return buildShowCall(unwrapZeroArgArrow(expr.left), jsx, null, state);
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
// cond ? <JSX /> : fallback (consequent may be () => <JSX />)
|
|
1435
|
-
// test may be (() => expr) — unwrap to avoid double arrow wrapping
|
|
1436
|
-
if (t.isConditionalExpression(expr)) {
|
|
1437
|
-
const jsx = getJSXContent(expr.consequent);
|
|
1438
|
-
if (jsx) {
|
|
1439
|
-
const fallback = buildFallback(expr.alternate, state);
|
|
1440
|
-
return buildShowCall(unwrapZeroArgArrow(expr.test), jsx, fallback, state);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
return null;
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* When autoShow is disabled, wraps ALL ternary and logical-AND expressions in
|
|
1447
|
-
* `createMemo(() => normalizedExpr)` so the condition evaluates reactively and
|
|
1448
|
-
* the result is cached (not re-evaluated on every render cycle).
|
|
1449
|
-
*
|
|
1450
|
-
* Normalization rules applied before wrapping:
|
|
1451
|
-
* - Outer zero-arg arrow unwrapped: `() => cond ? a : b` → body used directly
|
|
1452
|
-
* - Arrow-wrapped test unwrapped: `(() => cond) ? a : b` → `cond ? a : b`
|
|
1453
|
-
* - Arrow-wrapped JSX consequent: `() => <JSX>` → `<JSX>` (so Babel transforms it)
|
|
1454
|
-
* - Arrow-wrapped JSX alternate: `() => <JSX>` → `<JSX>` likewise
|
|
1455
|
-
* - Non-JSX branches left as-is: strings, identifiers, call results, etc.
|
|
1456
|
-
*
|
|
1457
|
-
* Examples:
|
|
1458
|
-
* {cond ? "a" : "b"} → createMemo(() => cond ? "a" : "b")
|
|
1459
|
-
* {cond ? <A/> : <B/>} → createMemo(() => cond ? <A/> : <B/>)
|
|
1460
|
-
* {(() => cond) ? ()=><A/> : <B/>} → createMemo(() => cond ? <A/> : <B/>)
|
|
1461
|
-
* {cond && <A/>} → createMemo(() => cond && <A/>)
|
|
1462
|
-
*/
|
|
1463
|
-
function tryWrapConditionalAsMemo(expr, state) {
|
|
1464
|
-
// Unwrap outer zero-arg arrow: () => <inner> → work on inner directly
|
|
1465
|
-
const inner = t.isArrowFunctionExpression(expr) &&
|
|
1466
|
-
expr.params.length === 0 &&
|
|
1467
|
-
!expr.async &&
|
|
1468
|
-
t.isExpression(expr.body)
|
|
1469
|
-
? expr.body
|
|
1470
|
-
: expr;
|
|
1471
|
-
if (t.isConditionalExpression(inner)) {
|
|
1472
|
-
// Unwrap arrow-wrapped test to prevent always-truthy condition
|
|
1473
|
-
const normalizedTest = unwrapZeroArgArrow(inner.test);
|
|
1474
|
-
// Unwrap arrow-wrapped JSX branches so Babel can transform them;
|
|
1475
|
-
// non-JSX branches (strings, calls, etc.) are kept as-is
|
|
1476
|
-
const consequentJsx = getJSXContent(inner.consequent);
|
|
1477
|
-
const normalizedConsequent = consequentJsx ?? inner.consequent;
|
|
1478
|
-
const altJsx = getJSXContent(inner.alternate);
|
|
1479
|
-
const normalizedAlternate = altJsx ?? inner.alternate;
|
|
1480
|
-
const normalized = t.conditionalExpression(normalizedTest, normalizedConsequent, normalizedAlternate);
|
|
1481
|
-
// If either branch is a control-flow component (For, Switch, Index, Show, …),
|
|
1482
|
-
// do NOT wrap in createMemo — return null so the caller falls through to the
|
|
1483
|
-
// plain `() => expr` path via shouldAutoWrapExpression.
|
|
1484
|
-
if (isControlFlowJSX(inner.consequent) ||
|
|
1485
|
-
isControlFlowJSX(inner.alternate)) {
|
|
1486
|
-
return null;
|
|
1487
|
-
}
|
|
1488
|
-
state.needsCreateMemo = true;
|
|
1489
|
-
return t.callExpression(t.identifier('createMemo'), [
|
|
1490
|
-
t.arrowFunctionExpression([], normalized),
|
|
1491
|
-
]);
|
|
1492
|
-
}
|
|
1493
|
-
if (t.isLogicalExpression(inner) && inner.operator === '&&') {
|
|
1494
|
-
const normalizedLeft = unwrapZeroArgArrow(inner.left);
|
|
1495
|
-
const rightJsx = getJSXContent(inner.right);
|
|
1496
|
-
const normalizedRight = rightJsx ?? inner.right;
|
|
1497
|
-
const normalized = t.logicalExpression('&&', normalizedLeft, normalizedRight);
|
|
1498
|
-
// Same guard for logical && (e.g. {show && <For ...>})
|
|
1499
|
-
if (isControlFlowJSX(inner.right)) {
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1502
|
-
state.needsCreateMemo = true;
|
|
1503
|
-
return t.callExpression(t.identifier('createMemo'), [
|
|
1504
|
-
t.arrowFunctionExpression([], normalized),
|
|
1505
|
-
]);
|
|
1506
|
-
}
|
|
1507
|
-
return null;
|
|
1508
|
-
}
|
|
1509
|
-
/**
|
|
1510
|
-
* Recursively transforms a fallback expression into a reactive value.
|
|
1511
|
-
* If the expression is a nested ternary `cond ? <JSX /> : rest`, it is
|
|
1512
|
-
* transformed into a nested `Show` call so the condition is tracked reactively.
|
|
1513
|
-
*/
|
|
1514
|
-
function buildFallback(expr, state) {
|
|
1515
|
-
if (isFalsyNode(expr))
|
|
1516
|
-
return null;
|
|
1517
|
-
// bare JSX or () => <JSX>
|
|
1518
|
-
const directJsx = getJSXContent(expr);
|
|
1519
|
-
if (directJsx) {
|
|
1520
|
-
return transformJSXNode(directJsx, state);
|
|
1521
|
-
}
|
|
1522
|
-
// Nested ternary whose truthy branch is JSX (or arrow-wrapped JSX)
|
|
1523
|
-
if (t.isConditionalExpression(expr)) {
|
|
1524
|
-
const inner = expr;
|
|
1525
|
-
const innerJsx = getJSXContent(inner.consequent);
|
|
1526
|
-
if (innerJsx) {
|
|
1527
|
-
const nestedFallback = buildFallback(inner.alternate, state);
|
|
1528
|
-
return buildShowCall(unwrapZeroArgArrow(inner.test), innerJsx, nestedFallback, state);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
return expr;
|
|
1532
|
-
}
|
|
1533
|
-
function isJSXNode(node) {
|
|
1534
|
-
return t.isJSXElement(node) || t.isJSXFragment(node);
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Returns true when `expr` (or a zero-arg arrow wrapping it) is a JSXElement
|
|
1538
|
-
* whose tag is one of the Fluixi control-flow components (For, Switch, Index,
|
|
1539
|
-
* Show, Portal, Dynamic, ErrorBoundary, Suspense). These must NOT be wrapped in
|
|
1540
|
-
* createMemo — a plain `() => expr` arrow is sufficient and avoids double-tracking.
|
|
1541
|
-
*/
|
|
1542
|
-
function isControlFlowJSX(expr) {
|
|
1543
|
-
const jsx = getJSXContent(expr);
|
|
1544
|
-
if (!jsx || !t.isJSXElement(jsx))
|
|
1545
|
-
return false;
|
|
1546
|
-
const name = jsx.openingElement.name;
|
|
1547
|
-
return t.isJSXIdentifier(name) && CONTROL_FLOW_COMPONENTS.has(name.name);
|
|
1548
|
-
}
|
|
1549
|
-
/**
|
|
1550
|
-
* Returns the JSX content from either bare `<JSX>` or a zero-arg arrow `() => <JSX>`.
|
|
1551
|
-
* This allows Show transformation to work regardless of whether the author wrapped the
|
|
1552
|
-
* consequent/alternate in an arrow function or not.
|
|
1553
|
-
*/
|
|
1554
|
-
function getJSXContent(expr) {
|
|
1555
|
-
if (isJSXNode(expr))
|
|
1556
|
-
return expr;
|
|
1557
|
-
if (t.isArrowFunctionExpression(expr) &&
|
|
1558
|
-
expr.params.length === 0 &&
|
|
1559
|
-
!expr.async &&
|
|
1560
|
-
isJSXNode(expr.body)) {
|
|
1561
|
-
return expr.body;
|
|
1562
|
-
}
|
|
1563
|
-
return null;
|
|
1564
|
-
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Unwraps a zero-argument arrow `() => expr` to `expr`.
|
|
1567
|
-
* Used to avoid double-wrapping when the test/condition is already `() => cond`.
|
|
1568
|
-
* `buildShowCall` always wraps the condition in `() =>`, so passing a bare expr
|
|
1569
|
-
* produces `() => cond`; passing `() => cond` without unwrapping produces `() => () => cond`
|
|
1570
|
-
* which is always truthy.
|
|
1571
|
-
*/
|
|
1572
|
-
function unwrapZeroArgArrow(expr) {
|
|
1573
|
-
if (t.isArrowFunctionExpression(expr) &&
|
|
1574
|
-
expr.params.length === 0 &&
|
|
1575
|
-
!expr.async &&
|
|
1576
|
-
t.isExpression(expr.body)) {
|
|
1577
|
-
return expr.body;
|
|
1578
|
-
}
|
|
1579
|
-
return expr;
|
|
1580
|
-
}
|
|
1581
|
-
function isFalsyNode(node) {
|
|
1582
|
-
return (t.isNullLiteral(node) ||
|
|
1583
|
-
(t.isIdentifier(node) && node.name === 'undefined') ||
|
|
1584
|
-
(t.isBooleanLiteral(node) && node.value === false));
|
|
1585
|
-
}
|
|
1586
|
-
function transformJSXNode(node, state) {
|
|
1587
|
-
if (t.isJSXElement(node)) {
|
|
1588
|
-
return transformAutomaticElement(node, state, false, state.autoShow ?? true);
|
|
1589
|
-
}
|
|
1590
|
-
return transformJSXFragment(node, state, {
|
|
1591
|
-
runtime: 'automatic',
|
|
1592
|
-
pragmaFrag: 'Fragment',
|
|
1593
|
-
development: false,
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
function buildShowCall(condition, children, fallback, state, fromArrow = false) {
|
|
1597
|
-
// Register Show so it gets auto-imported if needed
|
|
1598
|
-
state.controlFlowComponents.add('Show');
|
|
1599
|
-
state.needsControlFlowImport = true;
|
|
1600
|
-
const transformedChildren = transformJSXNode(children, state);
|
|
1601
|
-
// console.log('build show call', generate(condition).code);
|
|
1602
|
-
const props = [
|
|
1603
|
-
t.objectProperty(t.identifier('when'), t.arrowFunctionExpression([], condition)
|
|
1604
|
-
// fromArrow ? (condition) : t.arrowFunctionExpression([], condition),
|
|
1605
|
-
),
|
|
1606
|
-
t.objectProperty(t.identifier('children'), transformedChildren),
|
|
1607
|
-
];
|
|
1608
|
-
if (fallback) {
|
|
1609
|
-
props.push(t.objectProperty(t.identifier('fallback'), fallback));
|
|
1610
|
-
}
|
|
1611
|
-
return emitComponentCall(t.identifier('Show'), t.objectExpression(props), state);
|
|
1612
|
-
}
|
|
1613
|
-
// ============================================================================
|
|
1614
|
-
// Reactivity Detection
|
|
1615
|
-
// ============================================================================
|
|
1616
|
-
/**
|
|
1617
|
-
* Check if an expression should be auto-wrapped in an arrow function for reactivity
|
|
1618
|
-
* This enables SolidJS-style reactivity where you can write {store.property} or {signal()}
|
|
1619
|
-
* without manual wrapping
|
|
1620
|
-
*/
|
|
1621
|
-
function shouldAutoWrapExpression(expr) {
|
|
1622
|
-
// Member expressions: store.property, user.name, etc.
|
|
1623
|
-
// These need wrapping so property access happens inside an effect
|
|
1624
|
-
if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
|
|
1625
|
-
// `props.children` (any `.children`) is a stable subtree reference, not a
|
|
1626
|
-
// reactive value. Wrapping it in `() => props.children` adds an insert layer
|
|
1627
|
-
// that fully unwraps the children's Suspense/memo to a static node, severing
|
|
1628
|
-
// the reactive binding — so a nested lazy/<Suspense> that resolves later
|
|
1629
|
-
// never propagates up through the component-children hop. Pass it through
|
|
1630
|
-
// non-reactive (insert(el, props.children)) so insert binds to the child's
|
|
1631
|
-
// own memo, exactly like a directly-rendered <Outlet/>.
|
|
1632
|
-
if (!expr.computed && t.isIdentifier(expr.property, { name: 'children' })) {
|
|
1633
|
-
return false;
|
|
1634
|
-
}
|
|
1635
|
-
return true;
|
|
1636
|
-
}
|
|
1637
|
-
// Call expressions: signal(), count(), etc.
|
|
1638
|
-
// These are already function calls but need wrapping to re-run on updates
|
|
1639
|
-
if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) {
|
|
1640
|
-
return true;
|
|
1641
|
-
}
|
|
1642
|
-
// DON'T wrap plain identifiers - they might be signal accessors or functions
|
|
1643
|
-
// that should be passed as-is. Only wrap when accessing properties or calling.
|
|
1644
|
-
if (t.isIdentifier(expr)) {
|
|
1645
|
-
return false;
|
|
1646
|
-
}
|
|
1647
|
-
// Logical expressions: signal() && other, store.value || default
|
|
1648
|
-
if (t.isLogicalExpression(expr) || t.isConditionalExpression(expr)) {
|
|
1649
|
-
return true;
|
|
1650
|
-
}
|
|
1651
|
-
// Binary expressions: signal() + 1, store.count * 2
|
|
1652
|
-
if (t.isBinaryExpression(expr)) {
|
|
1653
|
-
return true;
|
|
1654
|
-
}
|
|
1655
|
-
// Unary expressions: !signal(), -store.count
|
|
1656
|
-
if (t.isUnaryExpression(expr)) {
|
|
1657
|
-
return true;
|
|
1658
|
-
}
|
|
1659
|
-
// Template literals: `Count: ${count()}`
|
|
1660
|
-
if (t.isTemplateLiteral(expr)) {
|
|
1661
|
-
return true;
|
|
1662
|
-
}
|
|
1663
|
-
// Don't wrap literals, strings, numbers, etc.
|
|
1664
|
-
return false;
|
|
1665
|
-
}
|
|
1666
|
-
// ============================================================================
|
|
1667
|
-
// Static Optimization
|
|
1668
|
-
// ============================================================================
|
|
1669
|
-
function shouldWrapReactive(expr, state) {
|
|
1670
|
-
// Call expressions ending with () - likely signal accessors
|
|
1671
|
-
if (t.isCallExpression(expr) && t.isIdentifier(expr.callee)) {
|
|
1672
|
-
return true;
|
|
1673
|
-
}
|
|
1674
|
-
// Arrow functions with no parameters - could be computed values
|
|
1675
|
-
if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
|
|
1676
|
-
return true;
|
|
1677
|
-
}
|
|
1678
|
-
// Function expressions with no parameters
|
|
1679
|
-
if (t.isFunctionExpression(expr) && expr.params.length === 0) {
|
|
1680
|
-
return true;
|
|
1681
|
-
}
|
|
1682
|
-
// Member expressions - could be store access
|
|
1683
|
-
if (t.isMemberExpression(expr)) {
|
|
1684
|
-
return true;
|
|
1685
|
-
}
|
|
1686
|
-
return false;
|
|
1687
|
-
}
|
|
1688
|
-
function wrapReactiveExpression(expr) {
|
|
1689
|
-
return t.callExpression(t.identifier(''), [
|
|
1690
|
-
t.arrowFunctionExpression([], expr),
|
|
1691
|
-
]);
|
|
1692
|
-
}
|
|
1693
|
-
// ============================================================================
|
|
1694
|
-
// Static Element Optimization
|
|
1695
|
-
// ============================================================================
|
|
1696
|
-
function isStaticElement(element, state) {
|
|
1697
|
-
const openingElement = element.openingElement;
|
|
1698
|
-
// Check if tag is static (string literal)
|
|
1699
|
-
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
1700
|
-
return false;
|
|
1701
|
-
}
|
|
1702
|
-
const tagName = openingElement.name.name;
|
|
1703
|
-
if (tagName[0] !== tagName[0].toLowerCase()) {
|
|
1704
|
-
// Component, not a native element
|
|
1705
|
-
return false;
|
|
1706
|
-
}
|
|
1707
|
-
// Check if all attributes are static
|
|
1708
|
-
for (const attr of openingElement.attributes) {
|
|
1709
|
-
if (t.isJSXSpreadAttribute(attr)) {
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
if (t.isJSXAttribute(attr)) {
|
|
1713
|
-
const value = attr.value;
|
|
1714
|
-
if (t.isJSXExpressionContainer(value)) {
|
|
1715
|
-
if (!t.isStringLiteral(value.expression) &&
|
|
1716
|
-
!t.isNumericLiteral(value.expression)) {
|
|
1717
|
-
return false;
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
// Check if all children are static
|
|
1723
|
-
for (const child of element.children) {
|
|
1724
|
-
if (t.isJSXElement(child) && !isStaticElement(child, state)) {
|
|
1725
|
-
return false;
|
|
1726
|
-
}
|
|
1727
|
-
if (t.isJSXExpressionContainer(child)) {
|
|
1728
|
-
return false;
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
return true;
|
|
1732
|
-
}
|
|
1733
|
-
function hoistStaticElement(element, state) {
|
|
1734
|
-
const varName = `_$static${state.staticCounter++}`;
|
|
1735
|
-
const transformed = transformAutomaticElement(element, state, false);
|
|
1736
|
-
state.staticElements.set(varName, transformed);
|
|
1737
|
-
return t.identifier(varName);
|
|
1738
|
-
}
|
|
1739
|
-
// ============================================================================
|
|
1740
|
-
// Control Flow Optimization
|
|
1741
|
-
// ============================================================================
|
|
1742
|
-
function optimizeControlFlowComponent(element, state) {
|
|
1743
|
-
const componentName = element.openingElement.name.name;
|
|
1744
|
-
// Optimize Show component
|
|
1745
|
-
if (componentName === 'Show') {
|
|
1746
|
-
return optimizeShowComponent(element, state);
|
|
1747
|
-
}
|
|
1748
|
-
// Optimize For component
|
|
1749
|
-
if (componentName === 'For') {
|
|
1750
|
-
return optimizeForComponent(element, state);
|
|
1751
|
-
}
|
|
1752
|
-
return null;
|
|
1753
|
-
}
|
|
1754
|
-
function optimizeShowComponent(element, state) {
|
|
1755
|
-
// Extract when, fallback, and children
|
|
1756
|
-
// Apply optimizations based on patterns
|
|
1757
|
-
return null; // Placeholder for optimization logic
|
|
1758
|
-
}
|
|
1759
|
-
function optimizeForComponent(element, state) {
|
|
1760
|
-
// Extract each and children
|
|
1761
|
-
// Apply keyed reconciliation optimizations
|
|
1762
|
-
return null; // Placeholder for optimization logic
|
|
1763
|
-
}
|
|
1764
|
-
// ============================================================================
|
|
1765
|
-
// Exports
|
|
1766
|
-
// ============================================================================
|