@tsrx/react 0.0.2 → 0.0.3
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/package.json +1 -1
- package/src/transform.js +790 -35
package/package.json
CHANGED
package/src/transform.js
CHANGED
|
@@ -11,9 +11,16 @@ import { renderStylesheets, setLocation } from '@tsrx/core';
|
|
|
11
11
|
* local_statement_component_index: number,
|
|
12
12
|
* needs_error_boundary: boolean,
|
|
13
13
|
* needs_suspense: boolean,
|
|
14
|
+
* helper_state: { base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] } | null,
|
|
15
|
+
* available_bindings: Map<string, AST.Identifier>,
|
|
16
|
+
* lazy_next_id: number,
|
|
14
17
|
* }} TransformContext
|
|
15
18
|
*/
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{ source_name: string, read: () => any }} LazyBinding
|
|
22
|
+
*/
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Transform a parsed tsrx-react AST into a TSX/JSX module.
|
|
19
26
|
*
|
|
@@ -39,6 +46,9 @@ export function transform(ast, source, filename) {
|
|
|
39
46
|
local_statement_component_index: 0,
|
|
40
47
|
needs_error_boundary: false,
|
|
41
48
|
needs_suspense: false,
|
|
49
|
+
helper_state: null,
|
|
50
|
+
available_bindings: new Map(),
|
|
51
|
+
lazy_next_id: 0,
|
|
42
52
|
};
|
|
43
53
|
|
|
44
54
|
walk(/** @type {any} */ (ast), transform_context, {
|
|
@@ -56,8 +66,39 @@ export function transform(ast, source, filename) {
|
|
|
56
66
|
|
|
57
67
|
const transformed = walk(/** @type {any} */ (ast), transform_context, {
|
|
58
68
|
Component(node, { next, state }) {
|
|
69
|
+
const as_any = /** @type {any} */ (node);
|
|
70
|
+
|
|
71
|
+
// Set up helper_state and bindings BEFORE next() so that nested
|
|
72
|
+
// hook_safe_* calls (inside Element children) can register helpers
|
|
73
|
+
// and access available bindings during the bottom-up walk.
|
|
74
|
+
const helper_state = create_helper_state(as_any.id?.name || 'Component');
|
|
75
|
+
const saved_helper_state = state.helper_state;
|
|
76
|
+
const saved_bindings = state.available_bindings;
|
|
77
|
+
state.helper_state = helper_state;
|
|
78
|
+
|
|
79
|
+
// Pre-collect component body bindings (params + top-level statements)
|
|
80
|
+
// so that Element children processed during the bottom-up walk can see
|
|
81
|
+
// the full scope. Without this, hoisted helpers would miss body-level
|
|
82
|
+
// variables like `const [x] = useState(...)` and produce ReferenceErrors.
|
|
83
|
+
// Only collect up to the split point — bindings declared after a
|
|
84
|
+
// hook-safe split aren't in scope at the return statement and would
|
|
85
|
+
// cause ReferenceErrors if passed as helper props.
|
|
86
|
+
const body_bindings = collect_param_bindings(as_any.params || []);
|
|
87
|
+
const body = as_any.body || [];
|
|
88
|
+
const split_index = find_hook_safe_split_index(body);
|
|
89
|
+
const collect_end = split_index === -1 ? body.length : split_index;
|
|
90
|
+
for (let i = 0; i < collect_end; i += 1) {
|
|
91
|
+
collect_statement_bindings(body[i], body_bindings);
|
|
92
|
+
}
|
|
93
|
+
state.available_bindings = body_bindings;
|
|
94
|
+
|
|
59
95
|
const inner = /** @type {any} */ (next() ?? node);
|
|
60
|
-
|
|
96
|
+
|
|
97
|
+
// Restore context
|
|
98
|
+
state.helper_state = saved_helper_state;
|
|
99
|
+
state.available_bindings = saved_bindings;
|
|
100
|
+
|
|
101
|
+
return /** @type {any} */ (component_to_function_declaration(inner, state, helper_state));
|
|
61
102
|
},
|
|
62
103
|
|
|
63
104
|
Tsx(node, { next }) {
|
|
@@ -109,27 +150,558 @@ export function transform(ast, source, filename) {
|
|
|
109
150
|
return { ast: expanded, code: result.code, map: result.map, css };
|
|
110
151
|
}
|
|
111
152
|
|
|
153
|
+
// --- Lazy destructuring support ---
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate a unique lazy identifier name for a lazy destructuring pattern.
|
|
157
|
+
* @param {TransformContext} transform_context
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
function generate_lazy_id(transform_context) {
|
|
161
|
+
return `__lazy${transform_context.lazy_next_id++}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Collect lazy bindings from a destructuring pattern.
|
|
166
|
+
* For `&{name, age}`, maps `name` → `source.name`, `age` → `source.age`.
|
|
167
|
+
* For `&[a, b]`, maps `a` → `source[0]`, `b` → `source[1]`.
|
|
168
|
+
* Handles nested AssignmentPattern (default values) and RestElement.
|
|
169
|
+
*
|
|
170
|
+
* @param {any} pattern - The ObjectPattern or ArrayPattern with lazy: true
|
|
171
|
+
* @param {string} source_name - The generated identifier name for the source
|
|
172
|
+
* @param {Map<string, LazyBinding>} lazy_bindings - Map to populate
|
|
173
|
+
*/
|
|
174
|
+
function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
175
|
+
if (pattern.type === 'ObjectPattern') {
|
|
176
|
+
for (const prop of pattern.properties || []) {
|
|
177
|
+
if (prop.type === 'RestElement') {
|
|
178
|
+
// Rest element in object pattern — skip for now (complex to transform)
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const value = prop.value;
|
|
182
|
+
const actual = value.type === 'AssignmentPattern' ? value.left : value;
|
|
183
|
+
if (actual.type === 'Identifier') {
|
|
184
|
+
const key = prop.key;
|
|
185
|
+
const computed = prop.computed || key.type !== 'Identifier';
|
|
186
|
+
lazy_bindings.set(actual.name, {
|
|
187
|
+
source_name,
|
|
188
|
+
read: () => ({
|
|
189
|
+
type: 'MemberExpression',
|
|
190
|
+
object: create_generated_identifier(source_name),
|
|
191
|
+
property: computed
|
|
192
|
+
? { ...key }
|
|
193
|
+
: { type: 'Identifier', name: key.name, metadata: { path: [] } },
|
|
194
|
+
computed,
|
|
195
|
+
optional: false,
|
|
196
|
+
metadata: { path: [] },
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
202
|
+
for (let i = 0; i < (pattern.elements || []).length; i++) {
|
|
203
|
+
const element = pattern.elements[i];
|
|
204
|
+
if (!element) continue;
|
|
205
|
+
if (element.type === 'RestElement') {
|
|
206
|
+
// Rest element in array pattern — skip for now
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
210
|
+
if (actual.type === 'Identifier') {
|
|
211
|
+
const index = i;
|
|
212
|
+
lazy_bindings.set(actual.name, {
|
|
213
|
+
source_name,
|
|
214
|
+
read: () => ({
|
|
215
|
+
type: 'MemberExpression',
|
|
216
|
+
object: create_generated_identifier(source_name),
|
|
217
|
+
property: { type: 'Literal', value: index, raw: String(index), metadata: { path: [] } },
|
|
218
|
+
computed: true,
|
|
219
|
+
optional: false,
|
|
220
|
+
metadata: { path: [] },
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Collect lazy bindings from component params and body variable declarations
|
|
230
|
+
* WITHOUT modifying any AST nodes. Returns a map of binding name → accessor info.
|
|
231
|
+
* Stores the generated identifier name on the pattern's metadata for later replacement.
|
|
232
|
+
*
|
|
233
|
+
* @param {any[]} params - Component params (metadata annotated, not structurally mutated)
|
|
234
|
+
* @param {any[]} body - Component body (metadata annotated, not structurally mutated)
|
|
235
|
+
* @param {TransformContext} transform_context
|
|
236
|
+
* @returns {Map<string, LazyBinding>}
|
|
237
|
+
*/
|
|
238
|
+
function collect_lazy_bindings_from_component(params, body, transform_context) {
|
|
239
|
+
/** @type {Map<string, LazyBinding>} */
|
|
240
|
+
const lazy_bindings = new Map();
|
|
241
|
+
|
|
242
|
+
// Collect from lazy params
|
|
243
|
+
for (const param of params) {
|
|
244
|
+
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
245
|
+
|
|
246
|
+
if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
|
|
247
|
+
const lazy_name = generate_lazy_id(transform_context);
|
|
248
|
+
collect_lazy_bindings(pattern, lazy_name, lazy_bindings);
|
|
249
|
+
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_name };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Collect from lazy variable declarations in body
|
|
254
|
+
for (const statement of body) {
|
|
255
|
+
if (statement.type !== 'VariableDeclaration') continue;
|
|
256
|
+
|
|
257
|
+
for (const declarator of statement.declarations || []) {
|
|
258
|
+
const pattern = declarator.id;
|
|
259
|
+
if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
|
|
260
|
+
const lazy_name = generate_lazy_id(transform_context);
|
|
261
|
+
collect_lazy_bindings(pattern, lazy_name, lazy_bindings);
|
|
262
|
+
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_name };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return lazy_bindings;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Walk an AST node tree and replace identifier references that match lazy bindings
|
|
272
|
+
* with their corresponding member expressions (e.g., `name` → `__lazy0.name`).
|
|
273
|
+
* Also handles AssignmentExpression and UpdateExpression targets.
|
|
274
|
+
*
|
|
275
|
+
* @param {any} node - The AST node to walk
|
|
276
|
+
* @param {Map<string, LazyBinding>} lazy_bindings - Map of lazy binding names
|
|
277
|
+
* @returns {any}
|
|
278
|
+
*/
|
|
279
|
+
function apply_lazy_transforms(node, lazy_bindings) {
|
|
280
|
+
if (!node || typeof node !== 'object') return node;
|
|
281
|
+
if (Array.isArray(node)) return node.map((child) => apply_lazy_transforms(child, lazy_bindings));
|
|
282
|
+
|
|
283
|
+
// Don't recurse into nested function declarations (helper components have their own scope)
|
|
284
|
+
if (
|
|
285
|
+
node.type === 'FunctionDeclaration' ||
|
|
286
|
+
node.type === 'FunctionExpression' ||
|
|
287
|
+
node.type === 'ArrowFunctionExpression'
|
|
288
|
+
) {
|
|
289
|
+
// Transform default parameter values (e.g. (step = count) => ...) with the
|
|
290
|
+
// outer lazy_bindings, since defaults are evaluated in the outer scope.
|
|
291
|
+
let params_changed = false;
|
|
292
|
+
const new_params = (node.params || []).map((/** @type {any} */ param) => {
|
|
293
|
+
const transformed = transform_param_defaults(param, lazy_bindings);
|
|
294
|
+
if (transformed !== param) params_changed = true;
|
|
295
|
+
return transformed;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Check if any params shadow a lazy binding — if so, exclude those names
|
|
299
|
+
/** @type {Set<string>} */
|
|
300
|
+
const shadowed = new Set();
|
|
301
|
+
for (const param of node.params || []) {
|
|
302
|
+
collect_shadowed_names(param, lazy_bindings, shadowed);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const inner_bindings =
|
|
306
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
307
|
+
if (inner_bindings.size === 0 && !params_changed) return node;
|
|
308
|
+
|
|
309
|
+
const new_body =
|
|
310
|
+
inner_bindings.size > 0 ? apply_lazy_transforms(node.body, inner_bindings) : node.body;
|
|
311
|
+
|
|
312
|
+
if (new_body !== node.body || params_changed) {
|
|
313
|
+
return {
|
|
314
|
+
...node,
|
|
315
|
+
params: params_changed ? new_params : node.params,
|
|
316
|
+
body: new_body,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return node;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Handle block-scoped variable shadowing (const/let/var that shadows a lazy name)
|
|
323
|
+
if (node.type === 'BlockStatement' || node.type === 'Program') {
|
|
324
|
+
const block_bindings = collect_block_shadowed_names(node.body, lazy_bindings);
|
|
325
|
+
const effective_bindings =
|
|
326
|
+
block_bindings.size > 0 ? remove_shadowed(lazy_bindings, block_bindings) : lazy_bindings;
|
|
327
|
+
if (effective_bindings.size === 0 && block_bindings.size > 0) return node;
|
|
328
|
+
|
|
329
|
+
let changed = false;
|
|
330
|
+
const new_body = node.body.map((/** @type {any} */ stmt) => {
|
|
331
|
+
const transformed = apply_lazy_transforms(stmt, effective_bindings);
|
|
332
|
+
if (transformed !== stmt) changed = true;
|
|
333
|
+
return transformed;
|
|
334
|
+
});
|
|
335
|
+
return changed ? { ...node, body: new_body } : node;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Handle catch clause parameter shadowing
|
|
339
|
+
if (node.type === 'CatchClause') {
|
|
340
|
+
/** @type {Set<string>} */
|
|
341
|
+
const shadowed = new Set();
|
|
342
|
+
if (node.param) collect_shadowed_names(node.param, lazy_bindings, shadowed);
|
|
343
|
+
const effective_bindings =
|
|
344
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
345
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
346
|
+
if (new_body !== node.body) return { ...node, body: new_body };
|
|
347
|
+
return node;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Handle for-loop variable shadowing
|
|
351
|
+
if (node.type === 'ForStatement') {
|
|
352
|
+
/** @type {Set<string>} */
|
|
353
|
+
const shadowed = new Set();
|
|
354
|
+
if (node.init?.type === 'VariableDeclaration') {
|
|
355
|
+
for (const decl of node.init.declarations) {
|
|
356
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const effective_bindings =
|
|
360
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
361
|
+
let changed = false;
|
|
362
|
+
const new_init = apply_lazy_transforms(node.init, effective_bindings);
|
|
363
|
+
if (new_init !== node.init) changed = true;
|
|
364
|
+
const new_test = apply_lazy_transforms(node.test, effective_bindings);
|
|
365
|
+
if (new_test !== node.test) changed = true;
|
|
366
|
+
const new_update = apply_lazy_transforms(node.update, effective_bindings);
|
|
367
|
+
if (new_update !== node.update) changed = true;
|
|
368
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
369
|
+
if (new_body !== node.body) changed = true;
|
|
370
|
+
return changed
|
|
371
|
+
? { ...node, init: new_init, test: new_test, update: new_update, body: new_body }
|
|
372
|
+
: node;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
|
|
376
|
+
/** @type {Set<string>} */
|
|
377
|
+
const shadowed = new Set();
|
|
378
|
+
if (node.left?.type === 'VariableDeclaration') {
|
|
379
|
+
for (const decl of node.left.declarations) {
|
|
380
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const effective_bindings =
|
|
384
|
+
shadowed.size > 0 ? remove_shadowed(lazy_bindings, shadowed) : lazy_bindings;
|
|
385
|
+
let changed = false;
|
|
386
|
+
const new_right = apply_lazy_transforms(node.right, lazy_bindings);
|
|
387
|
+
if (new_right !== node.right) changed = true;
|
|
388
|
+
const new_body = apply_lazy_transforms(node.body, effective_bindings);
|
|
389
|
+
if (new_body !== node.body) changed = true;
|
|
390
|
+
return changed ? { ...node, right: new_right, body: new_body } : node;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Handle switch-case variable shadowing (const/let inside case consequent arrays)
|
|
394
|
+
if (node.type === 'SwitchStatement') {
|
|
395
|
+
let changed = false;
|
|
396
|
+
const new_discriminant = apply_lazy_transforms(node.discriminant, lazy_bindings);
|
|
397
|
+
if (new_discriminant !== node.discriminant) changed = true;
|
|
398
|
+
const new_cases = node.cases.map((/** @type {any} */ switch_case) => {
|
|
399
|
+
const case_bindings = collect_block_shadowed_names(switch_case.consequent, lazy_bindings);
|
|
400
|
+
const effective_bindings =
|
|
401
|
+
case_bindings.size > 0 ? remove_shadowed(lazy_bindings, case_bindings) : lazy_bindings;
|
|
402
|
+
let case_changed = false;
|
|
403
|
+
const new_test = switch_case.test
|
|
404
|
+
? apply_lazy_transforms(switch_case.test, lazy_bindings)
|
|
405
|
+
: null;
|
|
406
|
+
if (new_test !== switch_case.test) case_changed = true;
|
|
407
|
+
const new_consequent = switch_case.consequent.map((/** @type {any} */ stmt) => {
|
|
408
|
+
const transformed = apply_lazy_transforms(stmt, effective_bindings);
|
|
409
|
+
if (transformed !== stmt) case_changed = true;
|
|
410
|
+
return transformed;
|
|
411
|
+
});
|
|
412
|
+
if (case_changed) {
|
|
413
|
+
changed = true;
|
|
414
|
+
return { ...switch_case, test: new_test, consequent: new_consequent };
|
|
415
|
+
}
|
|
416
|
+
return switch_case;
|
|
417
|
+
});
|
|
418
|
+
return changed ? { ...node, discriminant: new_discriminant, cases: new_cases } : node;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Handle assignment: `name = value` → `__lazy0.name = value`
|
|
422
|
+
if (node.type === 'AssignmentExpression' && node.left.type === 'Identifier') {
|
|
423
|
+
const binding = lazy_bindings.get(node.left.name);
|
|
424
|
+
if (binding) {
|
|
425
|
+
return {
|
|
426
|
+
...node,
|
|
427
|
+
left: binding.read(),
|
|
428
|
+
right: apply_lazy_transforms(node.right, lazy_bindings),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Handle update: `count++` → `__lazy0[0]++`
|
|
434
|
+
if (node.type === 'UpdateExpression' && node.argument.type === 'Identifier') {
|
|
435
|
+
const binding = lazy_bindings.get(node.argument.name);
|
|
436
|
+
if (binding) {
|
|
437
|
+
return { ...node, argument: binding.read() };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Replace lazy variable declaration patterns with generated identifiers
|
|
442
|
+
if (node.type === 'VariableDeclarator' && node.id?.metadata?.lazy_id) {
|
|
443
|
+
const lazy_id = create_generated_identifier(node.id.metadata.lazy_id);
|
|
444
|
+
if (node.id.typeAnnotation) {
|
|
445
|
+
lazy_id.typeAnnotation = node.id.typeAnnotation;
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
...node,
|
|
449
|
+
id: lazy_id,
|
|
450
|
+
init: apply_lazy_transforms(node.init, lazy_bindings),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Handle identifier references in expression position
|
|
455
|
+
if (node.type === 'Identifier') {
|
|
456
|
+
const binding = lazy_bindings.get(node.name);
|
|
457
|
+
if (binding) {
|
|
458
|
+
return binding.read();
|
|
459
|
+
}
|
|
460
|
+
return node;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Skip JSXIdentifier (component/element names)
|
|
464
|
+
if (node.type === 'JSXIdentifier') return node;
|
|
465
|
+
|
|
466
|
+
// Handle shorthand properties: `{ name }` → `{ name: __lazy0.name }`
|
|
467
|
+
if (node.type === 'Property' && node.shorthand && node.value?.type === 'Identifier') {
|
|
468
|
+
const binding = lazy_bindings.get(node.value.name);
|
|
469
|
+
if (binding) {
|
|
470
|
+
return {
|
|
471
|
+
...node,
|
|
472
|
+
shorthand: false,
|
|
473
|
+
value: binding.read(),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Recurse into child nodes
|
|
479
|
+
let changed = false;
|
|
480
|
+
const result = /** @type {any} */ ({});
|
|
481
|
+
|
|
482
|
+
for (const key of Object.keys(node)) {
|
|
483
|
+
if (key === 'loc' || key === 'start' || key === 'end') {
|
|
484
|
+
result[key] = node[key];
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Skip non-computed property keys (they're labels, not references)
|
|
489
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) {
|
|
490
|
+
result[key] = node[key];
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Skip non-computed member expression property names
|
|
495
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) {
|
|
496
|
+
result[key] = node[key];
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Skip JSXAttribute name
|
|
501
|
+
if (key === 'name' && node.type === 'JSXAttribute') {
|
|
502
|
+
result[key] = node[key];
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Skip variable declaration id (the lazy declaration itself was already replaced)
|
|
507
|
+
if (key === 'id' && node.type === 'VariableDeclarator') {
|
|
508
|
+
result[key] = node[key];
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const child = node[key];
|
|
513
|
+
const transformed = apply_lazy_transforms(child, lazy_bindings);
|
|
514
|
+
result[key] = transformed;
|
|
515
|
+
if (transformed !== child) changed = true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return changed ? result : node;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Transform default values in function parameters without touching param names.
|
|
523
|
+
* E.g. `(step = count)` where `count` is a lazy binding → `(step = __lazy0[0])`.
|
|
524
|
+
*
|
|
525
|
+
* @param {any} param
|
|
526
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
527
|
+
* @returns {any}
|
|
528
|
+
*/
|
|
529
|
+
function transform_param_defaults(param, lazy_bindings) {
|
|
530
|
+
if (param?.type === 'AssignmentPattern') {
|
|
531
|
+
const new_right = apply_lazy_transforms(param.right, lazy_bindings);
|
|
532
|
+
if (new_right !== param.right) {
|
|
533
|
+
return { ...param, right: new_right };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return param;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Collect names from a pattern that shadow lazy bindings.
|
|
541
|
+
* @param {any} pattern
|
|
542
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
543
|
+
* @param {Set<string>} shadowed
|
|
544
|
+
*/
|
|
545
|
+
function collect_shadowed_names(pattern, lazy_bindings, shadowed) {
|
|
546
|
+
if (!pattern || typeof pattern !== 'object') return;
|
|
547
|
+
|
|
548
|
+
if (pattern.type === 'Identifier' && lazy_bindings.has(pattern.name)) {
|
|
549
|
+
shadowed.add(pattern.name);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
554
|
+
collect_shadowed_names(pattern.left, lazy_bindings, shadowed);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (pattern.type === 'RestElement') {
|
|
559
|
+
collect_shadowed_names(pattern.argument, lazy_bindings, shadowed);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (pattern.type === 'ObjectPattern') {
|
|
564
|
+
for (const prop of pattern.properties || []) {
|
|
565
|
+
if (prop.type === 'RestElement') {
|
|
566
|
+
collect_shadowed_names(prop.argument, lazy_bindings, shadowed);
|
|
567
|
+
} else {
|
|
568
|
+
collect_shadowed_names(prop.value, lazy_bindings, shadowed);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (pattern.type === 'ArrayPattern') {
|
|
575
|
+
for (const element of pattern.elements || []) {
|
|
576
|
+
if (element) collect_shadowed_names(element, lazy_bindings, shadowed);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Collect variable names declared in block-level statements that shadow lazy bindings.
|
|
583
|
+
* Scans VariableDeclarations (const/let/var) and FunctionDeclarations at the top level of a block.
|
|
584
|
+
*
|
|
585
|
+
* @param {any[]} statements
|
|
586
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
587
|
+
* @returns {Set<string>}
|
|
588
|
+
*/
|
|
589
|
+
function collect_block_shadowed_names(statements, lazy_bindings) {
|
|
590
|
+
/** @type {Set<string>} */
|
|
591
|
+
const shadowed = new Set();
|
|
592
|
+
for (const stmt of statements) {
|
|
593
|
+
if (stmt.type === 'VariableDeclaration') {
|
|
594
|
+
for (const decl of stmt.declarations) {
|
|
595
|
+
// Skip lazy destructuring patterns — they ARE the lazy bindings,
|
|
596
|
+
// not local declarations that shadow them.
|
|
597
|
+
if (decl.id?.metadata?.lazy_id) continue;
|
|
598
|
+
if (decl.id) collect_shadowed_names(decl.id, lazy_bindings, shadowed);
|
|
599
|
+
}
|
|
600
|
+
} else if (stmt.type === 'FunctionDeclaration' && stmt.id) {
|
|
601
|
+
if (lazy_bindings.has(stmt.id.name)) {
|
|
602
|
+
shadowed.add(stmt.id.name);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return shadowed;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Create a new lazy_bindings map with the shadowed names removed.
|
|
611
|
+
*
|
|
612
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
613
|
+
* @param {Set<string>} shadowed
|
|
614
|
+
* @returns {Map<string, LazyBinding>}
|
|
615
|
+
*/
|
|
616
|
+
function remove_shadowed(lazy_bindings, shadowed) {
|
|
617
|
+
const result = new Map(lazy_bindings);
|
|
618
|
+
for (const name of shadowed) {
|
|
619
|
+
result.delete(name);
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Replace lazy parameter patterns with their generated identifiers.
|
|
626
|
+
* A param `&{name, age}: Props` becomes `__lazy0: Props`.
|
|
627
|
+
*
|
|
628
|
+
* @param {any[]} params
|
|
629
|
+
* @returns {any[]}
|
|
630
|
+
*/
|
|
631
|
+
function replace_lazy_params(params) {
|
|
632
|
+
return params.map((param) => {
|
|
633
|
+
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
634
|
+
|
|
635
|
+
if (
|
|
636
|
+
(pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') &&
|
|
637
|
+
pattern.lazy &&
|
|
638
|
+
pattern.metadata?.lazy_id
|
|
639
|
+
) {
|
|
640
|
+
const lazy_id = create_generated_identifier(pattern.metadata.lazy_id);
|
|
641
|
+
if (pattern.typeAnnotation) {
|
|
642
|
+
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
643
|
+
}
|
|
644
|
+
if (param.type === 'AssignmentPattern') {
|
|
645
|
+
return { ...param, left: lazy_id };
|
|
646
|
+
}
|
|
647
|
+
return lazy_id;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return param;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
112
654
|
/**
|
|
113
655
|
* @param {any} component
|
|
114
656
|
* @param {TransformContext} transform_context
|
|
657
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} [walk_helper_state]
|
|
115
658
|
* @returns {AST.FunctionDeclaration}
|
|
116
659
|
*/
|
|
117
|
-
function component_to_function_declaration(component, transform_context) {
|
|
118
|
-
const helper_state = create_helper_state(component.id?.name || 'Component');
|
|
660
|
+
function component_to_function_declaration(component, transform_context, walk_helper_state) {
|
|
661
|
+
const helper_state = walk_helper_state || create_helper_state(component.id?.name || 'Component');
|
|
662
|
+
const params = component.params || [];
|
|
663
|
+
const body = /** @type {any[]} */ (component.body || []);
|
|
664
|
+
|
|
665
|
+
// Collect param bindings from original patterns (lazy patterns still intact).
|
|
666
|
+
const param_bindings = collect_param_bindings(params);
|
|
667
|
+
|
|
668
|
+
// Collect lazy binding info WITHOUT mutating patterns. Stores lazy_id on metadata
|
|
669
|
+
// for later replacement. Body bindings (count, setCount, etc.) are still in the
|
|
670
|
+
// original patterns, so collect_statement_bindings during build will find them.
|
|
671
|
+
const lazy_bindings = collect_lazy_bindings_from_component(params, body, transform_context);
|
|
672
|
+
|
|
673
|
+
// Save and set context for this component scope
|
|
674
|
+
const saved_helper_state = transform_context.helper_state;
|
|
675
|
+
const saved_bindings = transform_context.available_bindings;
|
|
676
|
+
transform_context.helper_state = helper_state;
|
|
677
|
+
transform_context.available_bindings = new Map(param_bindings);
|
|
678
|
+
|
|
679
|
+
const body_statements = build_component_statements(
|
|
680
|
+
body,
|
|
681
|
+
helper_state,
|
|
682
|
+
param_bindings,
|
|
683
|
+
transform_context,
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
// Replace lazy param patterns with generated identifiers
|
|
687
|
+
const final_params = lazy_bindings.size > 0 ? replace_lazy_params(params) : params;
|
|
688
|
+
|
|
689
|
+
// Wrap body_statements in a BlockStatement so that apply_lazy_transforms
|
|
690
|
+
// runs collect_block_shadowed_names and detects body-level declarations
|
|
691
|
+
// (e.g. `const name = ...`) that shadow lazy binding names.
|
|
692
|
+
const body_block = /** @type {any} */ ({
|
|
693
|
+
type: 'BlockStatement',
|
|
694
|
+
body: body_statements,
|
|
695
|
+
metadata: { path: [] },
|
|
696
|
+
});
|
|
697
|
+
const final_body =
|
|
698
|
+
lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
|
|
699
|
+
|
|
119
700
|
const fn = /** @type {any} */ ({
|
|
120
701
|
type: 'FunctionDeclaration',
|
|
121
702
|
id: component.id,
|
|
122
|
-
params:
|
|
123
|
-
body:
|
|
124
|
-
type: 'BlockStatement',
|
|
125
|
-
body: build_component_statements(
|
|
126
|
-
/** @type {any[]} */ (component.body),
|
|
127
|
-
helper_state,
|
|
128
|
-
collect_param_bindings(component.params || []),
|
|
129
|
-
transform_context,
|
|
130
|
-
),
|
|
131
|
-
metadata: { path: [] },
|
|
132
|
-
},
|
|
703
|
+
params: final_params,
|
|
704
|
+
body: final_body,
|
|
133
705
|
async: false,
|
|
134
706
|
generator: false,
|
|
135
707
|
metadata: {
|
|
@@ -138,7 +710,12 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
138
710
|
},
|
|
139
711
|
});
|
|
140
712
|
|
|
713
|
+
// Restore context
|
|
714
|
+
transform_context.helper_state = saved_helper_state;
|
|
715
|
+
transform_context.available_bindings = saved_bindings;
|
|
716
|
+
|
|
141
717
|
fn.metadata.generated_helpers = helper_state.helpers;
|
|
718
|
+
fn.metadata.generated_statics = helper_state.statics;
|
|
142
719
|
|
|
143
720
|
if (fn.id) {
|
|
144
721
|
fn.id.metadata = /** @type {AST.Identifier['metadata']} */ ({
|
|
@@ -153,7 +730,7 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
153
730
|
|
|
154
731
|
/**
|
|
155
732
|
* @param {any[]} body_nodes
|
|
156
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
733
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
157
734
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
158
735
|
* @param {TransformContext} transform_context
|
|
159
736
|
* @returns {any[]}
|
|
@@ -191,9 +768,12 @@ function build_component_statements(
|
|
|
191
768
|
} else {
|
|
192
769
|
statements.push(child);
|
|
193
770
|
collect_statement_bindings(child, bindings);
|
|
771
|
+
transform_context.available_bindings = bindings;
|
|
194
772
|
}
|
|
195
773
|
}
|
|
196
774
|
|
|
775
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
776
|
+
|
|
197
777
|
const split_node = body_nodes[split_index];
|
|
198
778
|
const consequent_body =
|
|
199
779
|
split_node.consequent.type === 'BlockStatement'
|
|
@@ -250,9 +830,15 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
250
830
|
const statements = [];
|
|
251
831
|
const render_nodes = [];
|
|
252
832
|
|
|
833
|
+
// Create a new bindings map so inner-scope bindings from
|
|
834
|
+
// collect_statement_bindings don't leak to the caller's scope.
|
|
835
|
+
const saved_bindings = transform_context.available_bindings;
|
|
836
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
837
|
+
|
|
253
838
|
for (const child of body_nodes) {
|
|
254
839
|
if (is_bare_return_statement(child)) {
|
|
255
840
|
statements.push(create_component_return_statement(render_nodes, child));
|
|
841
|
+
transform_context.available_bindings = saved_bindings;
|
|
256
842
|
return statements;
|
|
257
843
|
}
|
|
258
844
|
|
|
@@ -265,9 +851,12 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
265
851
|
render_nodes.push(to_jsx_child(child, transform_context));
|
|
266
852
|
} else {
|
|
267
853
|
statements.push(child);
|
|
854
|
+
collect_statement_bindings(child, transform_context.available_bindings);
|
|
268
855
|
}
|
|
269
856
|
}
|
|
270
857
|
|
|
858
|
+
hoist_static_render_nodes(render_nodes, transform_context);
|
|
859
|
+
|
|
271
860
|
const return_arg = build_return_expression(render_nodes);
|
|
272
861
|
if (return_arg || return_null_when_empty) {
|
|
273
862
|
statements.push({
|
|
@@ -276,6 +865,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
|
|
|
276
865
|
});
|
|
277
866
|
}
|
|
278
867
|
|
|
868
|
+
transform_context.available_bindings = saved_bindings;
|
|
279
869
|
return statements;
|
|
280
870
|
}
|
|
281
871
|
|
|
@@ -393,7 +983,7 @@ function is_hook_callee(callee) {
|
|
|
393
983
|
|
|
394
984
|
/**
|
|
395
985
|
* @param {any[]} body_nodes
|
|
396
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
986
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
397
987
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
398
988
|
* @param {any} source_node
|
|
399
989
|
* @param {string} suffix
|
|
@@ -433,7 +1023,7 @@ function create_helper_component_expression(
|
|
|
433
1023
|
/**
|
|
434
1024
|
* @param {AST.Identifier} helper_id
|
|
435
1025
|
* @param {any[]} body_nodes
|
|
436
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
1026
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
437
1027
|
* @param {Map<string, AST.Identifier>} available_bindings
|
|
438
1028
|
* @param {AST.Identifier[]} helper_bindings
|
|
439
1029
|
* @param {any} source_node
|
|
@@ -552,7 +1142,7 @@ function create_helper_component_element(helper_id, bindings, source_node) {
|
|
|
552
1142
|
}
|
|
553
1143
|
|
|
554
1144
|
/**
|
|
555
|
-
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }} helper_state
|
|
1145
|
+
* @param {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }} helper_state
|
|
556
1146
|
* @param {string} suffix
|
|
557
1147
|
* @returns {string}
|
|
558
1148
|
*/
|
|
@@ -563,13 +1153,14 @@ function create_helper_name(helper_state, suffix) {
|
|
|
563
1153
|
|
|
564
1154
|
/**
|
|
565
1155
|
* @param {string} base_name
|
|
566
|
-
* @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[] }}
|
|
1156
|
+
* @returns {{ base_name: string, next_id: number, helpers: AST.FunctionDeclaration[], statics: any[] }}
|
|
567
1157
|
*/
|
|
568
1158
|
function create_helper_state(base_name) {
|
|
569
1159
|
return {
|
|
570
1160
|
base_name,
|
|
571
1161
|
next_id: 0,
|
|
572
1162
|
helpers: [],
|
|
1163
|
+
statics: [],
|
|
573
1164
|
};
|
|
574
1165
|
}
|
|
575
1166
|
|
|
@@ -649,6 +1240,93 @@ function collect_pattern_bindings(pattern, bindings) {
|
|
|
649
1240
|
}
|
|
650
1241
|
}
|
|
651
1242
|
|
|
1243
|
+
/**
|
|
1244
|
+
* Check if a node references any of the given scope bindings.
|
|
1245
|
+
* Used to determine if a JSX element is static and can be hoisted to module level.
|
|
1246
|
+
*
|
|
1247
|
+
* @param {any} node
|
|
1248
|
+
* @param {Map<string, AST.Identifier>} scope_bindings
|
|
1249
|
+
* @returns {boolean}
|
|
1250
|
+
*/
|
|
1251
|
+
function references_scope_bindings(node, scope_bindings) {
|
|
1252
|
+
if (!node || typeof node !== 'object') return false;
|
|
1253
|
+
if (scope_bindings.size === 0) return false;
|
|
1254
|
+
|
|
1255
|
+
if (node.type === 'Identifier') {
|
|
1256
|
+
return scope_bindings.has(node.name);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// JSXIdentifier is a variable reference when capitalized (tag name like <MyComponent />)
|
|
1260
|
+
// or when it's the object of a JSXMemberExpression (e.g. ui in <ui.Button />)
|
|
1261
|
+
if (node.type === 'JSXIdentifier') {
|
|
1262
|
+
return scope_bindings.has(node.name);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (Array.isArray(node)) {
|
|
1266
|
+
return node.some((child) => references_scope_bindings(child, scope_bindings));
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
for (const key of Object.keys(node)) {
|
|
1270
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
1271
|
+
|
|
1272
|
+
// Skip non-computed, non-shorthand property keys (they are labels, not references)
|
|
1273
|
+
if (key === 'key' && node.type === 'Property' && !node.computed && !node.shorthand) continue;
|
|
1274
|
+
|
|
1275
|
+
// Skip non-computed member expression property access
|
|
1276
|
+
if (key === 'property' && node.type === 'MemberExpression' && !node.computed) continue;
|
|
1277
|
+
|
|
1278
|
+
// Skip JSXMemberExpression property (e.g. Button in <Icons.Button /> is a label, not a reference)
|
|
1279
|
+
if (key === 'property' && node.type === 'JSXMemberExpression') continue;
|
|
1280
|
+
|
|
1281
|
+
// Skip JSXAttribute names — they are attribute labels, not variable references
|
|
1282
|
+
if (key === 'name' && node.type === 'JSXAttribute') continue;
|
|
1283
|
+
|
|
1284
|
+
if (references_scope_bindings(node[key], scope_bindings)) return true;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Hoist static JSX elements from render_nodes to module level.
|
|
1292
|
+
* A JSX element is static if it doesn't reference any component-scope bindings.
|
|
1293
|
+
* Hoisting prevents React from recreating the element on every render, allowing
|
|
1294
|
+
* the reconciler to skip diffing when it sees the same element identity.
|
|
1295
|
+
*
|
|
1296
|
+
* @param {any[]} render_nodes
|
|
1297
|
+
* @param {TransformContext} transform_context
|
|
1298
|
+
*/
|
|
1299
|
+
function hoist_static_render_nodes(render_nodes, transform_context) {
|
|
1300
|
+
if (!transform_context.helper_state) return;
|
|
1301
|
+
|
|
1302
|
+
for (let i = 0; i < render_nodes.length; i++) {
|
|
1303
|
+
const node = render_nodes[i];
|
|
1304
|
+
if (node.type !== 'JSXElement') continue;
|
|
1305
|
+
if (references_scope_bindings(node, transform_context.available_bindings)) continue;
|
|
1306
|
+
|
|
1307
|
+
const name = create_helper_name(transform_context.helper_state, 'static');
|
|
1308
|
+
const id = create_generated_identifier(name);
|
|
1309
|
+
|
|
1310
|
+
transform_context.helper_state.statics.push(
|
|
1311
|
+
/** @type {any} */ ({
|
|
1312
|
+
type: 'VariableDeclaration',
|
|
1313
|
+
kind: 'const',
|
|
1314
|
+
declarations: [
|
|
1315
|
+
{
|
|
1316
|
+
type: 'VariableDeclarator',
|
|
1317
|
+
id,
|
|
1318
|
+
init: node,
|
|
1319
|
+
metadata: { path: [] },
|
|
1320
|
+
},
|
|
1321
|
+
],
|
|
1322
|
+
metadata: { path: [] },
|
|
1323
|
+
}),
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
render_nodes[i] = to_jsx_expression_container(clone_identifier(id), node);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
652
1330
|
/**
|
|
653
1331
|
* @param {AST.Identifier} identifier
|
|
654
1332
|
* @returns {AST.Identifier}
|
|
@@ -683,9 +1361,11 @@ function create_null_literal() {
|
|
|
683
1361
|
function expand_component_helpers(program) {
|
|
684
1362
|
program.body = program.body.flatMap((statement) => {
|
|
685
1363
|
if (statement.type === 'FunctionDeclaration') {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
1364
|
+
const meta = /** @type {any} */ (statement.metadata);
|
|
1365
|
+
const statics = meta?.generated_statics || [];
|
|
1366
|
+
const helpers = meta?.generated_helpers || [];
|
|
1367
|
+
if (statics.length || helpers.length) {
|
|
1368
|
+
return [...statics, ...helpers, statement];
|
|
689
1369
|
}
|
|
690
1370
|
}
|
|
691
1371
|
|
|
@@ -694,9 +1374,11 @@ function expand_component_helpers(program) {
|
|
|
694
1374
|
statement.type === 'ExportDefaultDeclaration') &&
|
|
695
1375
|
statement.declaration?.type === 'FunctionDeclaration'
|
|
696
1376
|
) {
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
1377
|
+
const meta = /** @type {any} */ (statement.declaration.metadata);
|
|
1378
|
+
const statics = meta?.generated_statics || [];
|
|
1379
|
+
const helpers = meta?.generated_helpers || [];
|
|
1380
|
+
if (statics.length || helpers.length) {
|
|
1381
|
+
return [...statics, ...helpers, statement];
|
|
700
1382
|
}
|
|
701
1383
|
}
|
|
702
1384
|
|
|
@@ -1144,11 +1826,17 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1144
1826
|
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1145
1827
|
source_node,
|
|
1146
1828
|
);
|
|
1829
|
+
const helper_bindings = Array.from(transform_context.available_bindings.values());
|
|
1830
|
+
|
|
1831
|
+
// Save and isolate bindings for the helper body
|
|
1832
|
+
const saved_bindings = transform_context.available_bindings;
|
|
1833
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
1834
|
+
|
|
1147
1835
|
const helper_fn = set_loc(
|
|
1148
1836
|
/** @type {any} */ ({
|
|
1149
1837
|
type: 'FunctionDeclaration',
|
|
1150
1838
|
id: helper_id,
|
|
1151
|
-
params: [],
|
|
1839
|
+
params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
|
|
1152
1840
|
body: {
|
|
1153
1841
|
type: 'BlockStatement',
|
|
1154
1842
|
body: build_render_statements(body_nodes, true, transform_context),
|
|
@@ -1165,6 +1853,19 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1165
1853
|
source_node,
|
|
1166
1854
|
);
|
|
1167
1855
|
|
|
1856
|
+
// Restore bindings
|
|
1857
|
+
transform_context.available_bindings = saved_bindings;
|
|
1858
|
+
|
|
1859
|
+
// Register helper for hoisting to module level
|
|
1860
|
+
if (transform_context.helper_state) {
|
|
1861
|
+
transform_context.helper_state.helpers.push(helper_fn);
|
|
1862
|
+
|
|
1863
|
+
return to_jsx_expression_container(
|
|
1864
|
+
/** @type {any} */ (create_helper_component_element(helper_id, helper_bindings, source_node)),
|
|
1865
|
+
source_node,
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1168
1869
|
return to_jsx_expression_container(
|
|
1169
1870
|
/** @type {any} */ ({
|
|
1170
1871
|
type: 'CallExpression',
|
|
@@ -1177,7 +1878,7 @@ function hook_safe_statement_body_to_jsx_child(body_nodes, transform_context) {
|
|
|
1177
1878
|
helper_fn,
|
|
1178
1879
|
{
|
|
1179
1880
|
type: 'ReturnStatement',
|
|
1180
|
-
argument: create_helper_component_element(helper_id,
|
|
1881
|
+
argument: create_helper_component_element(helper_id, helper_bindings, source_node),
|
|
1181
1882
|
metadata: { path: [] },
|
|
1182
1883
|
},
|
|
1183
1884
|
],
|
|
@@ -1206,8 +1907,10 @@ function create_local_statement_component_name(transform_context) {
|
|
|
1206
1907
|
}
|
|
1207
1908
|
|
|
1208
1909
|
/**
|
|
1209
|
-
* Wraps a list of body nodes into a
|
|
1210
|
-
* statements that
|
|
1910
|
+
* Wraps a list of body nodes into a component and returns
|
|
1911
|
+
* statements that return `<ComponentName prop1={prop1} ... />`.
|
|
1912
|
+
* The component is hoisted to module level via helper_state to avoid
|
|
1913
|
+
* recreating the component identity on every render.
|
|
1211
1914
|
* Used when a control flow branch contains hook calls that must be moved
|
|
1212
1915
|
* into their own component boundary to satisfy the Rules of Hooks.
|
|
1213
1916
|
*
|
|
@@ -1222,12 +1925,17 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1222
1925
|
create_generated_identifier(create_local_statement_component_name(transform_context)),
|
|
1223
1926
|
source_node,
|
|
1224
1927
|
);
|
|
1928
|
+
const helper_bindings = Array.from(transform_context.available_bindings.values());
|
|
1929
|
+
|
|
1930
|
+
// Save and isolate bindings for the helper body
|
|
1931
|
+
const saved_bindings = transform_context.available_bindings;
|
|
1932
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
1225
1933
|
|
|
1226
1934
|
const helper_fn = set_loc(
|
|
1227
1935
|
/** @type {any} */ ({
|
|
1228
1936
|
type: 'FunctionDeclaration',
|
|
1229
1937
|
id: helper_id,
|
|
1230
|
-
params: [],
|
|
1938
|
+
params: helper_bindings.length > 0 ? [create_helper_props_pattern(helper_bindings)] : [],
|
|
1231
1939
|
body: {
|
|
1232
1940
|
type: 'BlockStatement',
|
|
1233
1941
|
body: build_render_statements(body_nodes, true, transform_context),
|
|
@@ -1244,7 +1952,19 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1244
1952
|
source_node,
|
|
1245
1953
|
);
|
|
1246
1954
|
|
|
1247
|
-
|
|
1955
|
+
// Restore bindings
|
|
1956
|
+
transform_context.available_bindings = saved_bindings;
|
|
1957
|
+
|
|
1958
|
+
// Register helper for hoisting to module level
|
|
1959
|
+
if (transform_context.helper_state) {
|
|
1960
|
+
transform_context.helper_state.helpers.push(helper_fn);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
const component_element = create_helper_component_element(
|
|
1964
|
+
helper_id,
|
|
1965
|
+
helper_bindings,
|
|
1966
|
+
source_node,
|
|
1967
|
+
);
|
|
1248
1968
|
|
|
1249
1969
|
if (key_expression) {
|
|
1250
1970
|
component_element.openingElement.attributes.push(
|
|
@@ -1257,8 +1977,20 @@ function hook_safe_render_statements(body_nodes, key_expression, transform_conte
|
|
|
1257
1977
|
);
|
|
1258
1978
|
}
|
|
1259
1979
|
|
|
1980
|
+
// When helper_state is null (no enclosing component context), inline the
|
|
1981
|
+
// helper via an IIFE so the function declaration isn't silently dropped.
|
|
1982
|
+
if (!transform_context.helper_state) {
|
|
1983
|
+
return [
|
|
1984
|
+
helper_fn,
|
|
1985
|
+
{
|
|
1986
|
+
type: 'ReturnStatement',
|
|
1987
|
+
argument: component_element,
|
|
1988
|
+
metadata: { path: [] },
|
|
1989
|
+
},
|
|
1990
|
+
];
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1260
1993
|
return [
|
|
1261
|
-
helper_fn,
|
|
1262
1994
|
{
|
|
1263
1995
|
type: 'ReturnStatement',
|
|
1264
1996
|
argument: component_element,
|
|
@@ -1411,6 +2143,20 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1411
2143
|
const has_hooks = body_contains_top_level_hook_call(loop_body);
|
|
1412
2144
|
const key_expression = has_hooks ? find_key_expression_in_body(loop_body) : undefined;
|
|
1413
2145
|
|
|
2146
|
+
// Add loop params to available bindings so hoisted helpers receive them as props
|
|
2147
|
+
const saved_bindings = transform_context.available_bindings;
|
|
2148
|
+
transform_context.available_bindings = new Map(saved_bindings);
|
|
2149
|
+
for (const param of loop_params) {
|
|
2150
|
+
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
const body_statements = has_hooks
|
|
2154
|
+
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
2155
|
+
: build_render_statements(loop_body, true, transform_context);
|
|
2156
|
+
|
|
2157
|
+
// Restore bindings
|
|
2158
|
+
transform_context.available_bindings = saved_bindings;
|
|
2159
|
+
|
|
1414
2160
|
return to_jsx_expression_container(
|
|
1415
2161
|
/** @type {any} */ ({
|
|
1416
2162
|
type: 'CallExpression',
|
|
@@ -1428,9 +2174,7 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1428
2174
|
params: loop_params,
|
|
1429
2175
|
body: /** @type {any} */ ({
|
|
1430
2176
|
type: 'BlockStatement',
|
|
1431
|
-
body:
|
|
1432
|
-
? hook_safe_render_statements(loop_body, key_expression, transform_context)
|
|
1433
|
-
: build_render_statements(loop_body, true, transform_context),
|
|
2177
|
+
body: body_statements,
|
|
1434
2178
|
metadata: { path: [] },
|
|
1435
2179
|
}),
|
|
1436
2180
|
async: false,
|
|
@@ -1572,6 +2316,15 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
1572
2316
|
}
|
|
1573
2317
|
|
|
1574
2318
|
const catch_body_nodes = handler.body.body || [];
|
|
2319
|
+
|
|
2320
|
+
// Add catch params to available_bindings so static hoisting
|
|
2321
|
+
// correctly identifies references to err/reset as non-static
|
|
2322
|
+
const saved_catch_bindings = transform_context.available_bindings;
|
|
2323
|
+
transform_context.available_bindings = new Map(saved_catch_bindings);
|
|
2324
|
+
for (const param of catch_params) {
|
|
2325
|
+
collect_pattern_bindings(param, transform_context.available_bindings);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
1575
2328
|
const fallback_fn = {
|
|
1576
2329
|
type: 'ArrowFunctionExpression',
|
|
1577
2330
|
params: catch_params,
|
|
@@ -1586,6 +2339,8 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
1586
2339
|
metadata: { path: [] },
|
|
1587
2340
|
};
|
|
1588
2341
|
|
|
2342
|
+
transform_context.available_bindings = saved_catch_bindings;
|
|
2343
|
+
|
|
1589
2344
|
result = create_jsx_element(
|
|
1590
2345
|
'TsrxErrorBoundary',
|
|
1591
2346
|
[
|