@tsrx/core 0.1.6 → 0.1.8
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 +25 -10
- package/src/index.js +1 -2
- package/src/plugin.js +185 -104
- package/src/runtime/events.js +10 -0
- package/src/runtime/hash.js +1 -0
- package/src/runtime/html.js +3 -0
- package/src/transform/jsx/index.js +29 -47
- package/src/transform/lazy.js +289 -152
- package/src/utils/ast.js +13 -0
- package/src/utils/dom.js +158 -0
- package/src/utils.js +6 -159
- package/types/index.d.ts +1 -0
- package/types/jsx-platform.d.ts +4 -2
- package/types/runtime/events.d.ts +11 -0
- package/types/runtime/hash.d.ts +2 -0
- package/types/runtime/html.d.ts +4 -0
- package/types/runtime/language-helpers.d.ts +17 -0
- /package/src/runtime/{index.js → iterable.js} +0 -0
- /package/types/runtime/{index.d.ts → iterable.d.ts} +0 -0
package/src/transform/lazy.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
|
|
3
3
|
import * as b from '../utils/builders.js';
|
|
4
|
+
import { is_function_or_component_node } from '../utils/ast.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Lazy destructuring transform — framework-agnostic.
|
|
7
8
|
*
|
|
8
|
-
* Shared between `@tsrx/react
|
|
9
|
-
* references to names introduced by `&{ ... }` /
|
|
10
|
-
* patterns into member-expression accesses on a
|
|
9
|
+
* Shared between `@tsrx/react`, `@tsrx/preact`, `@tsrx/solid`, and `@tsrx/vue`.
|
|
10
|
+
* Walks an AST and rewrites references to names introduced by `&{ ... }` /
|
|
11
|
+
* `&[ ... ]` destructuring patterns into member-expression accesses on a
|
|
12
|
+
* generated source identifier.
|
|
11
13
|
*
|
|
12
14
|
* Usage:
|
|
13
15
|
* 1. Create a context with `createLazyContext()` (or provide any object with
|
|
14
16
|
* a `lazy_next_id: number` field).
|
|
15
17
|
* 2. Run `preallocateLazyIds(root, context)` once over the full program to
|
|
16
|
-
* assign stable `metadata.lazy_id` values to every lazy pattern
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* assign stable `metadata.lazy_id` values to every lazy pattern and to
|
|
19
|
+
* flag function-like nodes whose subtree contains any lazy pattern via
|
|
20
|
+
* `metadata.has_lazy_descendants`.
|
|
21
|
+
* 3. After converting components to functions, call `applyLazyTransforms(fn,
|
|
22
|
+
* new Map())` on each top-level function. The function-handler walks the
|
|
23
|
+
* whole subtree, collects param + body bindings, replaces lazy patterns
|
|
24
|
+
* with their generated identifiers, and rewrites every reference.
|
|
22
25
|
*
|
|
23
26
|
* The transform is purely AST-to-AST and has no framework-specific knowledge.
|
|
24
27
|
*/
|
|
@@ -196,33 +199,60 @@ function set_lazy_param_binding_mappings(lazy_id, pattern) {
|
|
|
196
199
|
* Collect lazy bindings from a destructuring pattern.
|
|
197
200
|
*
|
|
198
201
|
* For `&{ name, age }` on source `S`, maps `name` → `S.name`, `age` → `S.age`.
|
|
199
|
-
* For `&[a, b]` on source `S`, maps `a` → `S[0]`, `b` → `S[1]`.
|
|
200
|
-
* `
|
|
202
|
+
* For `&[a, b]` on source `S`, maps `a` → `S[0]`, `b` → `S[1]`. Recurses into
|
|
203
|
+
* nested `ObjectPattern` / `ArrayPattern` values so that `&{ outer: &{ inner } }`
|
|
204
|
+
* on source `S` maps `inner` → `S.outer.inner`, and `&{ pair: &[first, second] }`
|
|
205
|
+
* maps `first` → `S.pair[0]`. Handles `AssignmentPattern` (default values lost,
|
|
206
|
+
* but the binding still resolves to the member chain). Skips `RestElement`.
|
|
201
207
|
*
|
|
202
208
|
* @param {any} pattern
|
|
203
209
|
* @param {string} source_name
|
|
204
210
|
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
205
211
|
*/
|
|
206
212
|
export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
213
|
+
collect_lazy_bindings_at(
|
|
214
|
+
pattern,
|
|
215
|
+
source_name,
|
|
216
|
+
() => create_generated_identifier(source_name),
|
|
217
|
+
lazy_bindings,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Walk a destructure pattern and register a `LazyBinding` for each leaf
|
|
223
|
+
* `Identifier`, where `build_parent` produces the AST expression that reaches
|
|
224
|
+
* this pattern's value from the synthesized source identifier. Each nested
|
|
225
|
+
* level composes its accessor onto `build_parent`, so leaves get the full
|
|
226
|
+
* member chain (e.g. `source.outer.inner` for `&{ outer: &{ inner } }`).
|
|
227
|
+
*
|
|
228
|
+
* @param {any} pattern
|
|
229
|
+
* @param {string} source_name
|
|
230
|
+
* @param {(reference?: any) => any} build_parent
|
|
231
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
232
|
+
*/
|
|
233
|
+
function collect_lazy_bindings_at(pattern, source_name, build_parent, lazy_bindings) {
|
|
207
234
|
if (pattern.type === 'ObjectPattern') {
|
|
208
235
|
for (const prop of pattern.properties || []) {
|
|
209
236
|
if (prop.type === 'RestElement') continue;
|
|
210
237
|
const value = prop.value;
|
|
211
238
|
const actual = value.type === 'AssignmentPattern' ? value.left : value;
|
|
239
|
+
const key = prop.key;
|
|
240
|
+
const computed = prop.computed || key.type !== 'Identifier';
|
|
241
|
+
|
|
242
|
+
/** @type {(reference?: any) => any} */
|
|
243
|
+
const build_self = (reference) =>
|
|
244
|
+
b.member(
|
|
245
|
+
build_parent(),
|
|
246
|
+
computed || key.type !== 'Identifier'
|
|
247
|
+
? { ...key }
|
|
248
|
+
: create_generated_identifier(key.name, reference, reference?.name),
|
|
249
|
+
computed,
|
|
250
|
+
);
|
|
251
|
+
|
|
212
252
|
if (actual.type === 'Identifier') {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
source_name,
|
|
217
|
-
read: (reference) =>
|
|
218
|
-
b.member(
|
|
219
|
-
create_generated_identifier(source_name),
|
|
220
|
-
computed || key.type !== 'Identifier'
|
|
221
|
-
? { ...key }
|
|
222
|
-
: create_generated_identifier(key.name, reference, reference?.name),
|
|
223
|
-
computed,
|
|
224
|
-
),
|
|
225
|
-
});
|
|
253
|
+
lazy_bindings.set(actual.name, { source_name, read: build_self });
|
|
254
|
+
} else if (actual.type === 'ObjectPattern' || actual.type === 'ArrayPattern') {
|
|
255
|
+
collect_lazy_bindings_at(actual, source_name, build_self, lazy_bindings);
|
|
226
256
|
}
|
|
227
257
|
}
|
|
228
258
|
} else if (pattern.type === 'ArrayPattern') {
|
|
@@ -231,48 +261,18 @@ export function collect_lazy_bindings(pattern, source_name, lazy_bindings) {
|
|
|
231
261
|
if (!element) continue;
|
|
232
262
|
if (element.type === 'RestElement') continue;
|
|
233
263
|
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
234
|
-
|
|
235
|
-
const index = i;
|
|
236
|
-
lazy_bindings.set(actual.name, {
|
|
237
|
-
source_name,
|
|
238
|
-
read: () => b.member(create_generated_identifier(source_name), b.literal(index), true),
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
264
|
+
const index = i;
|
|
244
265
|
|
|
245
|
-
/**
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
* @returns {Map<string, LazyBinding>}
|
|
253
|
-
*/
|
|
254
|
-
export function collect_lazy_bindings_from_component(params, body, context) {
|
|
255
|
-
/** @type {Map<string, LazyBinding>} */
|
|
256
|
-
const lazy_bindings = new Map();
|
|
257
|
-
|
|
258
|
-
for (const param of params) {
|
|
259
|
-
const pattern = param.type === 'AssignmentPattern' ? param.left : param;
|
|
260
|
-
if ((pattern.type === 'ObjectPattern' || pattern.type === 'ArrayPattern') && pattern.lazy) {
|
|
261
|
-
const lazy_name = pattern.metadata?.lazy_id || generate_lazy_id(context);
|
|
262
|
-
if (!pattern.metadata?.lazy_id) {
|
|
263
|
-
pattern.metadata = { ...pattern.metadata, lazy_id: lazy_name };
|
|
266
|
+
/** @type {() => any} */
|
|
267
|
+
const build_self = () => b.member(build_parent(), b.literal(index), true);
|
|
268
|
+
|
|
269
|
+
if (actual.type === 'Identifier') {
|
|
270
|
+
lazy_bindings.set(actual.name, { source_name, read: build_self });
|
|
271
|
+
} else if (actual.type === 'ObjectPattern' || actual.type === 'ArrayPattern') {
|
|
272
|
+
collect_lazy_bindings_at(actual, source_name, build_self, lazy_bindings);
|
|
264
273
|
}
|
|
265
|
-
collect_lazy_bindings(pattern, lazy_name, lazy_bindings);
|
|
266
274
|
}
|
|
267
275
|
}
|
|
268
|
-
|
|
269
|
-
// VariableDeclaration lazy patterns already have their `lazy_id` assigned
|
|
270
|
-
// by `preallocate_lazy_ids` (run once over the whole program by the target
|
|
271
|
-
// transforms), so `collect_lazy_bindings_from_statements` handles them
|
|
272
|
-
// alongside the expression-statement assignment form.
|
|
273
|
-
collect_lazy_bindings_from_statements(body, lazy_bindings);
|
|
274
|
-
|
|
275
|
-
return lazy_bindings;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
@@ -288,61 +288,164 @@ export function collect_lazy_bindings_from_statements(statements, lazy_bindings)
|
|
|
288
288
|
for (const stmt of statements || []) {
|
|
289
289
|
if (stmt.type === 'VariableDeclaration') {
|
|
290
290
|
for (const declarator of stmt.declarations || []) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
(
|
|
294
|
-
|
|
295
|
-
pattern.metadata?.lazy_id &&
|
|
296
|
-
!lazy_bindings_contains(lazy_bindings, pattern)
|
|
297
|
-
) {
|
|
298
|
-
collect_lazy_bindings(pattern, pattern.metadata.lazy_id, lazy_bindings);
|
|
299
|
-
}
|
|
291
|
+
visit_topmost_lazy_patterns(declarator.id, (lazy) => {
|
|
292
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
293
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
|
|
294
|
+
});
|
|
300
295
|
}
|
|
301
296
|
} else if (
|
|
302
297
|
stmt.type === 'ExpressionStatement' &&
|
|
303
298
|
stmt.expression?.type === 'AssignmentExpression' &&
|
|
304
|
-
stmt.expression.operator === '='
|
|
305
|
-
(stmt.expression.left?.type === 'ObjectPattern' ||
|
|
306
|
-
stmt.expression.left?.type === 'ArrayPattern') &&
|
|
307
|
-
stmt.expression.left.lazy &&
|
|
308
|
-
stmt.expression.left.metadata?.lazy_id
|
|
299
|
+
stmt.expression.operator === '='
|
|
309
300
|
) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
301
|
+
visit_topmost_lazy_patterns(stmt.expression.left, (lazy) => {
|
|
302
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
303
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, lazy_bindings);
|
|
304
|
+
});
|
|
315
305
|
}
|
|
316
306
|
}
|
|
317
307
|
}
|
|
318
308
|
|
|
319
309
|
/**
|
|
320
|
-
*
|
|
310
|
+
* Walk a destructure pattern tree, calling `visit` on every *topmost-lazy*
|
|
311
|
+
* descendant — a lazy `ObjectPattern` / `ArrayPattern` with no lazy ancestor
|
|
312
|
+
* within the same pattern tree. Descends through `AssignmentPattern`,
|
|
313
|
+
* `RestElement`, and non-lazy `ObjectPattern` / `ArrayPattern`. Stops at lazy
|
|
314
|
+
* patterns: their inner leaves are reached via accessor chains rooted at the
|
|
315
|
+
* lazy pattern's synthesized id, not by further descent here.
|
|
316
|
+
*
|
|
321
317
|
* @param {any} pattern
|
|
322
|
-
* @
|
|
318
|
+
* @param {(node: any) => void} visit
|
|
323
319
|
*/
|
|
324
|
-
function
|
|
320
|
+
function visit_topmost_lazy_patterns(pattern, visit) {
|
|
321
|
+
if (!pattern || typeof pattern !== 'object') return;
|
|
322
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
323
|
+
visit_topmost_lazy_patterns(pattern.left, visit);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (pattern.type === 'RestElement') {
|
|
327
|
+
visit_topmost_lazy_patterns(pattern.argument, visit);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (pattern.type !== 'ObjectPattern' && pattern.type !== 'ArrayPattern') return;
|
|
331
|
+
|
|
332
|
+
if (pattern.lazy) {
|
|
333
|
+
visit(pattern);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
325
337
|
if (pattern.type === 'ObjectPattern') {
|
|
326
338
|
for (const prop of pattern.properties || []) {
|
|
327
|
-
if (prop.type === 'RestElement')
|
|
328
|
-
|
|
329
|
-
const actual = value?.type === 'AssignmentPattern' ? value.left : value;
|
|
330
|
-
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
339
|
+
if (prop.type === 'RestElement') visit_topmost_lazy_patterns(prop.argument, visit);
|
|
340
|
+
else visit_topmost_lazy_patterns(prop.value, visit);
|
|
331
341
|
}
|
|
332
|
-
} else
|
|
342
|
+
} else {
|
|
333
343
|
for (const element of pattern.elements || []) {
|
|
334
|
-
if (
|
|
335
|
-
const actual = element.type === 'AssignmentPattern' ? element.left : element;
|
|
336
|
-
if (actual?.type === 'Identifier' && lazy_bindings.has(actual.name)) return true;
|
|
344
|
+
if (element) visit_topmost_lazy_patterns(element, visit);
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
|
-
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Build the replacement identifier for a lazy pattern. When `is_top` is true
|
|
351
|
+
* (the pattern is itself a function parameter) we attach the original
|
|
352
|
+
* `typeAnnotation`, synthesize an object-shaped annotation for untyped object
|
|
353
|
+
* params so TypeScript sees prop names, and register source-mapping info.
|
|
354
|
+
* Nested replacements (inside a non-lazy outer destructure) can't carry an
|
|
355
|
+
* inline type annotation — that's not valid syntax — so they get a plain
|
|
356
|
+
* identifier with just source-range info.
|
|
357
|
+
*
|
|
358
|
+
* @param {any} pattern
|
|
359
|
+
* @param {boolean} is_top
|
|
360
|
+
* @returns {any}
|
|
361
|
+
*/
|
|
362
|
+
function build_lazy_id_for_pattern(pattern, is_top) {
|
|
363
|
+
const pattern_range = get_lazy_pattern_mapping_range(pattern);
|
|
364
|
+
const lazy_id = pattern_range
|
|
365
|
+
? create_generated_identifier(
|
|
366
|
+
pattern.metadata.lazy_id,
|
|
367
|
+
pattern_range,
|
|
368
|
+
undefined,
|
|
369
|
+
pattern_range.source_length,
|
|
370
|
+
)
|
|
371
|
+
: create_generated_identifier(pattern.metadata.lazy_id);
|
|
372
|
+
if (!is_top) return lazy_id;
|
|
373
|
+
if (pattern.typeAnnotation) {
|
|
374
|
+
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
375
|
+
} else {
|
|
376
|
+
const type_annotation = create_lazy_object_type_annotation(pattern);
|
|
377
|
+
if (type_annotation) lazy_id.typeAnnotation = type_annotation;
|
|
378
|
+
}
|
|
379
|
+
set_lazy_param_binding_mappings(lazy_id, pattern);
|
|
380
|
+
return lazy_id;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Walk a destructure pattern tree and replace every topmost-lazy pattern with
|
|
385
|
+
* its synthesized id identifier. Non-lazy outer patterns are preserved so a
|
|
386
|
+
* source like `{ pair: &[a, b] }` becomes `{ pair: __lazy0 }`. The `is_top`
|
|
387
|
+
* flag is true only when the caller is invoking on a position that itself
|
|
388
|
+
* binds a single param (so a directly-lazy pattern can carry param-level type
|
|
389
|
+
* info); recursive descent into child patterns passes `false`.
|
|
390
|
+
*
|
|
391
|
+
* @param {any} pattern
|
|
392
|
+
* @param {boolean} [is_top]
|
|
393
|
+
* @returns {any}
|
|
394
|
+
*/
|
|
395
|
+
function replace_lazy_in_pattern(pattern, is_top = true) {
|
|
396
|
+
if (!pattern || typeof pattern !== 'object') return pattern;
|
|
397
|
+
|
|
398
|
+
if (pattern.type === 'AssignmentPattern') {
|
|
399
|
+
const new_left = replace_lazy_in_pattern(pattern.left, is_top);
|
|
400
|
+
return new_left === pattern.left ? pattern : { ...pattern, left: new_left };
|
|
401
|
+
}
|
|
402
|
+
if (pattern.type === 'RestElement') {
|
|
403
|
+
const new_arg = replace_lazy_in_pattern(pattern.argument, false);
|
|
404
|
+
return new_arg === pattern.argument ? pattern : { ...pattern, argument: new_arg };
|
|
405
|
+
}
|
|
406
|
+
if (pattern.type !== 'ObjectPattern' && pattern.type !== 'ArrayPattern') return pattern;
|
|
407
|
+
|
|
408
|
+
if (pattern.lazy && pattern.metadata?.lazy_id) {
|
|
409
|
+
return build_lazy_id_for_pattern(pattern, is_top);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (pattern.type === 'ObjectPattern') {
|
|
413
|
+
let changed = false;
|
|
414
|
+
const new_properties = (pattern.properties || []).map((/** @type {any} */ prop) => {
|
|
415
|
+
if (prop.type === 'RestElement') {
|
|
416
|
+
const new_arg = replace_lazy_in_pattern(prop.argument, false);
|
|
417
|
+
if (new_arg === prop.argument) return prop;
|
|
418
|
+
changed = true;
|
|
419
|
+
return { ...prop, argument: new_arg };
|
|
420
|
+
}
|
|
421
|
+
const new_value = replace_lazy_in_pattern(prop.value, false);
|
|
422
|
+
if (new_value === prop.value) return prop;
|
|
423
|
+
changed = true;
|
|
424
|
+
return { ...prop, value: new_value };
|
|
425
|
+
});
|
|
426
|
+
return changed ? { ...pattern, properties: new_properties } : pattern;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let changed = false;
|
|
430
|
+
const new_elements = (pattern.elements || []).map((/** @type {any} */ element) => {
|
|
431
|
+
if (!element) return element;
|
|
432
|
+
const new_element = replace_lazy_in_pattern(element, false);
|
|
433
|
+
if (new_element !== element) changed = true;
|
|
434
|
+
return new_element;
|
|
435
|
+
});
|
|
436
|
+
return changed ? { ...pattern, elements: new_elements } : pattern;
|
|
340
437
|
}
|
|
341
438
|
|
|
342
439
|
/**
|
|
343
440
|
* Walk the AST and pre-allocate `lazy_id` metadata on every lazy destructuring
|
|
344
441
|
* pattern: function/component params, variable declarator ids, and statement-level
|
|
345
|
-
* assignment LHS.
|
|
442
|
+
* assignment LHS. Walks into non-lazy outer patterns to find nested lazy ones,
|
|
443
|
+
* e.g. `{ pair: &[a, b] }` allocates an id for the inner `&[a, b]`. Idempotent:
|
|
444
|
+
* skips patterns that already have a `lazy_id`.
|
|
445
|
+
*
|
|
446
|
+
* Also stamps `metadata.has_lazy_descendants = true` on every function-like
|
|
447
|
+
* node whose subtree contains any lazy pattern, so `apply_lazy_transforms`
|
|
448
|
+
* can take a constant-time early-return path for purely non-lazy functions.
|
|
346
449
|
*
|
|
347
450
|
* @param {any} root
|
|
348
451
|
* @param {LazyContext} context
|
|
@@ -350,32 +453,29 @@ function lazy_bindings_contains(lazy_bindings, pattern) {
|
|
|
350
453
|
export function preallocate_lazy_ids(root, context) {
|
|
351
454
|
/** @param {any} pattern */
|
|
352
455
|
const assign_id = (pattern) => {
|
|
353
|
-
|
|
354
|
-
(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
) {
|
|
358
|
-
pattern.metadata = {
|
|
359
|
-
...pattern.metadata,
|
|
360
|
-
lazy_id: generate_lazy_id(context),
|
|
361
|
-
};
|
|
362
|
-
}
|
|
456
|
+
visit_topmost_lazy_patterns(pattern, (lazy) => {
|
|
457
|
+
if (lazy.metadata?.lazy_id) return;
|
|
458
|
+
lazy.metadata = { ...lazy.metadata, lazy_id: generate_lazy_id(context) };
|
|
459
|
+
});
|
|
363
460
|
};
|
|
364
461
|
|
|
365
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* @param {any} node
|
|
464
|
+
* @returns {boolean} true if `node`'s subtree contains any lazy pattern.
|
|
465
|
+
*/
|
|
366
466
|
const visit = (node) => {
|
|
367
|
-
if (!node || typeof node !== 'object') return;
|
|
467
|
+
if (!node || typeof node !== 'object') return false;
|
|
368
468
|
if (Array.isArray(node)) {
|
|
369
|
-
|
|
370
|
-
|
|
469
|
+
let found = false;
|
|
470
|
+
for (const child of node) {
|
|
471
|
+
if (visit(child)) found = true;
|
|
472
|
+
}
|
|
473
|
+
return found;
|
|
371
474
|
}
|
|
372
475
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
node.type === 'ArrowFunctionExpression' ||
|
|
377
|
-
node.type === 'Component'
|
|
378
|
-
) {
|
|
476
|
+
const is_function_like = is_function_or_component_node(node);
|
|
477
|
+
|
|
478
|
+
if (is_function_like) {
|
|
379
479
|
for (const param of node.params || []) {
|
|
380
480
|
assign_id(param?.type === 'AssignmentPattern' ? param.left : param);
|
|
381
481
|
}
|
|
@@ -393,10 +493,19 @@ export function preallocate_lazy_ids(root, context) {
|
|
|
393
493
|
assign_id(node.expression.left);
|
|
394
494
|
}
|
|
395
495
|
|
|
496
|
+
let found =
|
|
497
|
+
(node.type === 'ObjectPattern' || node.type === 'ArrayPattern') && node.lazy === true;
|
|
498
|
+
|
|
396
499
|
for (const key of Object.keys(node)) {
|
|
397
500
|
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
|
|
398
|
-
visit(node[key]);
|
|
501
|
+
if (visit(node[key])) found = true;
|
|
399
502
|
}
|
|
503
|
+
|
|
504
|
+
if (is_function_like && found) {
|
|
505
|
+
node.metadata = { ...node.metadata, has_lazy_descendants: true };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return found;
|
|
400
509
|
};
|
|
401
510
|
|
|
402
511
|
visit(root);
|
|
@@ -439,15 +548,11 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
439
548
|
const own_bindings = new Map();
|
|
440
549
|
let had_lazy_param = false;
|
|
441
550
|
for (const param of node.params || []) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
(pattern?.type === 'ObjectPattern' || pattern?.type === 'ArrayPattern') &&
|
|
445
|
-
pattern.lazy &&
|
|
446
|
-
pattern.metadata?.lazy_id
|
|
447
|
-
) {
|
|
551
|
+
visit_topmost_lazy_patterns(param, (lazy) => {
|
|
552
|
+
if (!lazy.metadata?.lazy_id) return;
|
|
448
553
|
had_lazy_param = true;
|
|
449
|
-
collect_lazy_bindings(
|
|
450
|
-
}
|
|
554
|
+
collect_lazy_bindings(lazy, lazy.metadata.lazy_id, own_bindings);
|
|
555
|
+
});
|
|
451
556
|
}
|
|
452
557
|
|
|
453
558
|
// Own bindings override any outer binding with the same name.
|
|
@@ -456,10 +561,20 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
456
561
|
? new Map([...outer_minus_shadow, ...own_bindings])
|
|
457
562
|
: outer_minus_shadow;
|
|
458
563
|
|
|
459
|
-
if (
|
|
564
|
+
if (
|
|
565
|
+
inner_bindings.size === 0 &&
|
|
566
|
+
!params_changed &&
|
|
567
|
+
!had_lazy_param &&
|
|
568
|
+
!node.metadata?.has_lazy_descendants
|
|
569
|
+
) {
|
|
570
|
+
return node;
|
|
571
|
+
}
|
|
460
572
|
|
|
461
|
-
|
|
462
|
-
|
|
573
|
+
// Past the early-return: either we have active lazy bindings, lazy
|
|
574
|
+
// params to replace, defaults referencing outer lazy, or the body
|
|
575
|
+
// contains lazy descendants the BlockStatement handler will collect.
|
|
576
|
+
// In every case the body needs to be walked.
|
|
577
|
+
const new_body = apply_lazy_transforms(node.body, inner_bindings);
|
|
463
578
|
|
|
464
579
|
const final_params_src = params_changed ? new_params : node.params;
|
|
465
580
|
const final_params = had_lazy_param ? replace_lazy_params(final_params_src) : final_params_src;
|
|
@@ -599,6 +714,30 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
599
714
|
return b.const(lazy_id, init);
|
|
600
715
|
}
|
|
601
716
|
|
|
717
|
+
// Non-lazy outer assignment whose LHS contains nested lazy patterns:
|
|
718
|
+
// `{ pair: &[a, b] } = obj` → `{ pair: __lazy0 } = obj`. JS reference
|
|
719
|
+
// semantics carry writes from `__lazy0[0] = x` back into `obj.pair[0]`.
|
|
720
|
+
if (
|
|
721
|
+
node.type === 'ExpressionStatement' &&
|
|
722
|
+
node.expression?.type === 'AssignmentExpression' &&
|
|
723
|
+
node.expression.operator === '=' &&
|
|
724
|
+
(node.expression.left?.type === 'ObjectPattern' ||
|
|
725
|
+
node.expression.left?.type === 'ArrayPattern') &&
|
|
726
|
+
!node.expression.left.lazy
|
|
727
|
+
) {
|
|
728
|
+
const new_left = replace_lazy_in_pattern(node.expression.left);
|
|
729
|
+
if (new_left !== node.expression.left) {
|
|
730
|
+
return {
|
|
731
|
+
...node,
|
|
732
|
+
expression: {
|
|
733
|
+
...node.expression,
|
|
734
|
+
left: new_left,
|
|
735
|
+
right: apply_lazy_transforms(node.expression.right, lazy_bindings),
|
|
736
|
+
},
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
602
741
|
// AssignmentExpression / UpdateExpression whose target is a lazy identifier.
|
|
603
742
|
if (
|
|
604
743
|
node.type === 'AssignmentExpression' &&
|
|
@@ -633,6 +772,23 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
633
772
|
};
|
|
634
773
|
}
|
|
635
774
|
|
|
775
|
+
// Non-lazy outer declarator whose id contains nested lazy patterns:
|
|
776
|
+
// `let { pair: &[a, b] } = data` → `let { pair: __lazy0 } = data`.
|
|
777
|
+
if (
|
|
778
|
+
node.type === 'VariableDeclarator' &&
|
|
779
|
+
(node.id?.type === 'ObjectPattern' || node.id?.type === 'ArrayPattern') &&
|
|
780
|
+
!node.id.lazy
|
|
781
|
+
) {
|
|
782
|
+
const new_id = replace_lazy_in_pattern(node.id);
|
|
783
|
+
if (new_id !== node.id) {
|
|
784
|
+
return {
|
|
785
|
+
...node,
|
|
786
|
+
id: new_id,
|
|
787
|
+
init: apply_lazy_transforms(node.init, lazy_bindings),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
636
792
|
// Shorthand object properties `{ name }` → `{ name: __lazy0.name }`.
|
|
637
793
|
if (node.type === 'Property' && node.shorthand && node.value?.type === 'Identifier') {
|
|
638
794
|
const binding = lazy_bindings.get(node.value.name);
|
|
@@ -758,38 +914,19 @@ function remove_shadowed(lazy_bindings, shadowed) {
|
|
|
758
914
|
|
|
759
915
|
/**
|
|
760
916
|
* Replace any lazy `&{}` / `&[]` patterns in a parameter list with their
|
|
761
|
-
* generated lazy identifiers
|
|
917
|
+
* generated lazy identifiers, including patterns nested inside non-lazy outer
|
|
918
|
+
* patterns. For `({ pair: &[a, b] })` returns `({ pair: __lazy0 })`. Leaves
|
|
919
|
+
* params without any lazy descendants untouched.
|
|
762
920
|
*
|
|
763
921
|
* @param {any[]} params
|
|
764
922
|
* @returns {any[]}
|
|
765
923
|
*/
|
|
766
924
|
export function replace_lazy_params(params) {
|
|
767
925
|
return params.map((param) => {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
pattern.lazy &&
|
|
772
|
-
pattern.metadata?.lazy_id
|
|
773
|
-
) {
|
|
774
|
-
const pattern_range = get_lazy_pattern_mapping_range(pattern);
|
|
775
|
-
const lazy_id = pattern_range
|
|
776
|
-
? create_generated_identifier(
|
|
777
|
-
pattern.metadata.lazy_id,
|
|
778
|
-
pattern_range,
|
|
779
|
-
undefined,
|
|
780
|
-
pattern_range.source_length,
|
|
781
|
-
)
|
|
782
|
-
: create_generated_identifier(pattern.metadata.lazy_id);
|
|
783
|
-
if (pattern.typeAnnotation) {
|
|
784
|
-
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
785
|
-
} else {
|
|
786
|
-
const type_annotation = create_lazy_object_type_annotation(pattern);
|
|
787
|
-
if (type_annotation) lazy_id.typeAnnotation = type_annotation;
|
|
788
|
-
}
|
|
789
|
-
set_lazy_param_binding_mappings(lazy_id, pattern);
|
|
790
|
-
if (param.type === 'AssignmentPattern') return { ...param, left: lazy_id };
|
|
791
|
-
return lazy_id;
|
|
926
|
+
if (param.type === 'AssignmentPattern') {
|
|
927
|
+
const new_left = replace_lazy_in_pattern(param.left);
|
|
928
|
+
return new_left === param.left ? param : { ...param, left: new_left };
|
|
792
929
|
}
|
|
793
|
-
return param;
|
|
930
|
+
return replace_lazy_in_pattern(param);
|
|
794
931
|
});
|
|
795
932
|
}
|
package/src/utils/ast.js
CHANGED
|
@@ -32,6 +32,19 @@ export function is_function_node(node) {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {AST.Node} node
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function is_function_or_component_node(node) {
|
|
40
|
+
return (
|
|
41
|
+
node.type === 'FunctionDeclaration' ||
|
|
42
|
+
node.type === 'FunctionExpression' ||
|
|
43
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
44
|
+
node.type === 'Component'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
/**
|
|
36
49
|
* @param {AST.Node} node
|
|
37
50
|
* @returns {boolean}
|