@tsrx/solid 0.0.23 → 0.0.24
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 +2 -2
- package/src/transform.js +296 -48
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Solid compiler built on @tsrx/core",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.24",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"esrap": "^2.1.0",
|
|
24
24
|
"zimmerframe": "^1.1.2",
|
|
25
|
-
"@tsrx/core": "0.0.
|
|
25
|
+
"@tsrx/core": "0.0.24"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"solid-js": ">=1.8 || >=2.0.0-beta"
|
package/src/transform.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
applyLazyTransforms as apply_lazy_transforms,
|
|
13
13
|
collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
|
|
14
14
|
replaceLazyParams as replace_lazy_params,
|
|
15
|
+
rewriteLoopContinuesToBareReturns as rewrite_loop_continues_to_bare_returns,
|
|
15
16
|
isInterleavedBody as is_interleaved_body_core,
|
|
16
17
|
isCapturableJsxChild as is_capturable_jsx_child,
|
|
17
18
|
captureJsxChild,
|
|
@@ -177,31 +178,35 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
177
178
|
|
|
178
179
|
const lazy_bindings = collect_lazy_bindings_from_component(params, body, transform_context);
|
|
179
180
|
|
|
180
|
-
// Detect top-level early-return
|
|
181
|
+
// Detect top-level early-return patterns such as `if (cond) { return; }`
|
|
182
|
+
// and `if (cond) { <p />; return; }`.
|
|
181
183
|
// Solid components run their body once at setup, so an early `return` would
|
|
182
184
|
// make subsequent statements and JSX permanently inert. To preserve
|
|
183
185
|
// React-like "stop rendering the rest when cond becomes true" semantics,
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// `<Show when={!cond}>` whose function-children re-runs when cond changes.
|
|
186
|
+
// keep JSX before the guard outside and lift the guarded/continuation JSX
|
|
187
|
+
// into `<Show>` branches whose function-children re-run when `cond` changes.
|
|
187
188
|
// Non-JSX statements on either side stay in the outer body so setup code
|
|
188
189
|
// (signal creation, resource declarations, etc.) runs exactly once at
|
|
189
190
|
// component setup — putting them inside the `<Show>` arrow would re-run
|
|
190
191
|
// them on every toggle, creating fresh signals and losing state.
|
|
191
192
|
//
|
|
192
193
|
// The `if` node itself is elided: its `test` expression lives on in the
|
|
193
|
-
// `<Show
|
|
194
|
-
//
|
|
194
|
+
// `<Show>` attribute and is evaluated reactively by Solid's runtime, so
|
|
195
|
+
// any side effects or reactive reads in `cond` are preserved.
|
|
195
196
|
// Non-JSX statements after the guard run unconditionally rather than being
|
|
196
197
|
// gated by it; this is an intentional divergence from imperative `return`
|
|
197
198
|
// semantics required by the setup-once component model.
|
|
198
|
-
const early_idx = body.findIndex(
|
|
199
|
+
const early_idx = body.findIndex((node) => get_returning_if_info(node) !== null);
|
|
199
200
|
/** @type {any[]} */
|
|
200
201
|
let effective_body = body;
|
|
201
202
|
if (early_idx !== -1) {
|
|
202
203
|
const early_if = /** @type {any} */ (body[early_idx]);
|
|
204
|
+
const early_info = /** @type {{ consequent_body: any[], return_index: number }} */ (
|
|
205
|
+
get_returning_if_info(early_if)
|
|
206
|
+
);
|
|
203
207
|
const before = body.slice(0, early_idx);
|
|
204
208
|
const after = body.slice(early_idx + 1);
|
|
209
|
+
const branch_has_content_before_return = early_info.return_index > 0;
|
|
205
210
|
|
|
206
211
|
// If mutations are interleaved with JSX children, the mutation and the
|
|
207
212
|
// JSX it affects can't both be hoisted out of order — that is the same
|
|
@@ -249,13 +254,21 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
249
254
|
collect(before, before_non_jsx, before_jsx);
|
|
250
255
|
collect(after, after_non_jsx, after_jsx);
|
|
251
256
|
|
|
252
|
-
const
|
|
253
|
-
|
|
257
|
+
const next_body = [...before_non_jsx, ...before_jsx, ...after_non_jsx];
|
|
258
|
+
|
|
259
|
+
if (branch_has_content_before_return) {
|
|
260
|
+
transform_context.needs_show = true;
|
|
261
|
+
const branch_body = body_to_jsx_child(early_info.consequent_body, transform_context);
|
|
262
|
+
const fallback_body =
|
|
263
|
+
after_jsx.length > 0 ? body_to_jsx_child(after_jsx, transform_context) : null;
|
|
264
|
+
next_body.push(build_show_element(early_if.test, branch_body, fallback_body));
|
|
265
|
+
} else if (after_jsx.length > 0) {
|
|
254
266
|
transform_context.needs_show = true;
|
|
255
|
-
const show_body = body_to_jsx_child(
|
|
256
|
-
|
|
257
|
-
effective_body = [...before_non_jsx, ...after_non_jsx, show_element];
|
|
267
|
+
const show_body = body_to_jsx_child(after_jsx, transform_context);
|
|
268
|
+
next_body.push(build_show_element(negate_expression(early_if.test), show_body, null));
|
|
258
269
|
}
|
|
270
|
+
|
|
271
|
+
effective_body = next_body;
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
const statements = [];
|
|
@@ -406,8 +419,23 @@ function body_to_jsx_child(body_nodes, transform_context) {
|
|
|
406
419
|
const statements = [];
|
|
407
420
|
/** @type {any[]} */
|
|
408
421
|
const children = [];
|
|
422
|
+
let has_bare_return = false;
|
|
409
423
|
let capture_index = 0;
|
|
410
424
|
for (const child of body_nodes) {
|
|
425
|
+
if (is_bare_return_statement(child)) {
|
|
426
|
+
statements.push({
|
|
427
|
+
type: 'ReturnStatement',
|
|
428
|
+
argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
|
|
429
|
+
metadata: { path: [] },
|
|
430
|
+
start: child.start,
|
|
431
|
+
end: child.end,
|
|
432
|
+
loc: child.loc,
|
|
433
|
+
});
|
|
434
|
+
children.length = 0;
|
|
435
|
+
has_bare_return = true;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
|
|
411
439
|
if (is_jsx_child(child)) {
|
|
412
440
|
const jsx = to_jsx_child(child, transform_context);
|
|
413
441
|
if (interleaved && is_capturable_jsx_child(jsx)) {
|
|
@@ -435,14 +463,16 @@ function body_to_jsx_child(body_nodes, transform_context) {
|
|
|
435
463
|
// Branch body has non-JSX statements: wrap everything in an arrow so the
|
|
436
464
|
// statements run when (and only when) the branch actually renders.
|
|
437
465
|
/** @type {any[]} */
|
|
438
|
-
const block_body = [
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
type
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
466
|
+
const block_body = [...statements];
|
|
467
|
+
if (children.length > 0 || !has_bare_return) {
|
|
468
|
+
block_body.push(
|
|
469
|
+
/** @type {any} */ ({
|
|
470
|
+
type: 'ReturnStatement',
|
|
471
|
+
argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
|
|
472
|
+
metadata: { path: [] },
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
446
476
|
|
|
447
477
|
return /** @type {any} */ ({
|
|
448
478
|
type: 'ArrowFunctionExpression',
|
|
@@ -459,6 +489,206 @@ function body_to_jsx_child(body_nodes, transform_context) {
|
|
|
459
489
|
});
|
|
460
490
|
}
|
|
461
491
|
|
|
492
|
+
/**
|
|
493
|
+
* @param {any} node
|
|
494
|
+
* @returns {boolean}
|
|
495
|
+
*/
|
|
496
|
+
function is_bare_return_statement(node) {
|
|
497
|
+
return node?.type === 'ReturnStatement' && node.argument == null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @param {any} node
|
|
502
|
+
* @returns {any[]}
|
|
503
|
+
*/
|
|
504
|
+
function get_if_consequent_body(node) {
|
|
505
|
+
return node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* @param {any[]} body_nodes
|
|
510
|
+
* @returns {boolean}
|
|
511
|
+
*/
|
|
512
|
+
function body_has_loop_skip(body_nodes) {
|
|
513
|
+
return body_nodes.some(
|
|
514
|
+
(node) => is_bare_return_statement(node) || get_returning_if_info(node) !== null,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* @param {any[]} body_nodes
|
|
520
|
+
* @param {TransformContext} transform_context
|
|
521
|
+
* @returns {any[]}
|
|
522
|
+
*/
|
|
523
|
+
function loop_body_to_callback_statements(body_nodes, transform_context) {
|
|
524
|
+
/** @type {any[]} */
|
|
525
|
+
const statements = [];
|
|
526
|
+
/** @type {any[]} */
|
|
527
|
+
const children = [];
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* @param {any} source_node
|
|
531
|
+
* @param {any[]} render_nodes
|
|
532
|
+
*/
|
|
533
|
+
const create_return_statement = (source_node, render_nodes) => {
|
|
534
|
+
const cloned = render_nodes.map((node) => clone_expression_node(node));
|
|
535
|
+
const argument = cloned.length > 0 ? build_return_expression(cloned) : create_null_literal();
|
|
536
|
+
return {
|
|
537
|
+
type: 'ReturnStatement',
|
|
538
|
+
argument,
|
|
539
|
+
metadata: { path: [] },
|
|
540
|
+
start: source_node?.start,
|
|
541
|
+
end: source_node?.end,
|
|
542
|
+
loc: source_node?.loc,
|
|
543
|
+
};
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
/** @param {any} source_node */
|
|
547
|
+
const flush_children_to_return = (source_node) => {
|
|
548
|
+
const statement = create_return_statement(source_node, children);
|
|
549
|
+
children.length = 0;
|
|
550
|
+
return statement;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
let has_terminal_return = false;
|
|
554
|
+
|
|
555
|
+
for (const child of body_nodes) {
|
|
556
|
+
if (is_bare_return_statement(child)) {
|
|
557
|
+
statements.push(flush_children_to_return(child));
|
|
558
|
+
has_terminal_return = true;
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const returning_if_info = get_returning_if_info(child);
|
|
563
|
+
if (returning_if_info !== null) {
|
|
564
|
+
const branch_statements = loop_body_to_callback_statements(
|
|
565
|
+
returning_if_info.consequent_body,
|
|
566
|
+
transform_context,
|
|
567
|
+
);
|
|
568
|
+
prepend_render_nodes_to_return_statements(branch_statements, children);
|
|
569
|
+
statements.push({
|
|
570
|
+
type: 'IfStatement',
|
|
571
|
+
test: child.test,
|
|
572
|
+
consequent: {
|
|
573
|
+
type: 'BlockStatement',
|
|
574
|
+
body: branch_statements,
|
|
575
|
+
metadata: { path: [] },
|
|
576
|
+
},
|
|
577
|
+
alternate: null,
|
|
578
|
+
metadata: { path: [] },
|
|
579
|
+
start: child.start,
|
|
580
|
+
end: child.end,
|
|
581
|
+
loc: child.loc,
|
|
582
|
+
});
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (is_jsx_child(child)) {
|
|
587
|
+
children.push(to_jsx_child(child, transform_context));
|
|
588
|
+
} else {
|
|
589
|
+
statements.push(child);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!has_terminal_return) {
|
|
594
|
+
statements.push(flush_children_to_return(body_nodes.at(-1)));
|
|
595
|
+
}
|
|
596
|
+
return statements;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @param {any[]} statements
|
|
601
|
+
* @param {any[]} render_nodes
|
|
602
|
+
* @returns {void}
|
|
603
|
+
*/
|
|
604
|
+
function prepend_render_nodes_to_return_statements(statements, render_nodes) {
|
|
605
|
+
if (render_nodes.length === 0) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
for (const statement of statements) {
|
|
610
|
+
prepend_render_nodes_to_return_statement(statement, render_nodes, false);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* @param {any} node
|
|
616
|
+
* @param {any[]} render_nodes
|
|
617
|
+
* @param {boolean} inside_nested_function
|
|
618
|
+
* @returns {void}
|
|
619
|
+
*/
|
|
620
|
+
function prepend_render_nodes_to_return_statement(node, render_nodes, inside_nested_function) {
|
|
621
|
+
if (!node || typeof node !== 'object') {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (
|
|
626
|
+
node.type === 'FunctionDeclaration' ||
|
|
627
|
+
node.type === 'FunctionExpression' ||
|
|
628
|
+
node.type === 'ArrowFunctionExpression'
|
|
629
|
+
) {
|
|
630
|
+
inside_nested_function = true;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!inside_nested_function && node.type === 'ReturnStatement') {
|
|
634
|
+
node.argument = combine_render_return_argument(render_nodes, node.argument);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (Array.isArray(node)) {
|
|
639
|
+
for (const child of node) {
|
|
640
|
+
prepend_render_nodes_to_return_statement(child, render_nodes, inside_nested_function);
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const key of Object.keys(node)) {
|
|
646
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
prepend_render_nodes_to_return_statement(node[key], render_nodes, inside_nested_function);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* @param {any[]} render_nodes
|
|
655
|
+
* @param {any} return_argument
|
|
656
|
+
* @returns {any}
|
|
657
|
+
*/
|
|
658
|
+
function combine_render_return_argument(render_nodes, return_argument) {
|
|
659
|
+
const combined = render_nodes.map((node) => clone_expression_node(node));
|
|
660
|
+
|
|
661
|
+
if (return_argument != null && !is_null_literal(return_argument)) {
|
|
662
|
+
combined.push(return_argument_to_render_node(return_argument));
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return build_return_expression(combined) || create_null_literal();
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* @param {any} argument
|
|
670
|
+
* @returns {any}
|
|
671
|
+
*/
|
|
672
|
+
function return_argument_to_render_node(argument) {
|
|
673
|
+
if (
|
|
674
|
+
argument?.type === 'JSXElement' ||
|
|
675
|
+
argument?.type === 'JSXFragment' ||
|
|
676
|
+
argument?.type === 'JSXExpressionContainer'
|
|
677
|
+
) {
|
|
678
|
+
return argument;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return to_jsx_expression_container(argument);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* @param {any} node
|
|
686
|
+
* @returns {boolean}
|
|
687
|
+
*/
|
|
688
|
+
function is_null_literal(node) {
|
|
689
|
+
return node?.type === 'Literal' && node.value == null;
|
|
690
|
+
}
|
|
691
|
+
|
|
462
692
|
/**
|
|
463
693
|
* Solid-specific binding of the core `isInterleavedBody` helper with this
|
|
464
694
|
* target's `is_jsx_child` predicate.
|
|
@@ -525,26 +755,34 @@ function merge_branch_body_into_arrow(outer_arrow, branch_body) {
|
|
|
525
755
|
}
|
|
526
756
|
|
|
527
757
|
/**
|
|
528
|
-
* Detect
|
|
529
|
-
* `if (cond) return;`) with no `else` branch.
|
|
758
|
+
* Detect a top-level `if` branch with a bare `return` and no `else` branch.
|
|
530
759
|
*
|
|
531
760
|
* @param {any} node
|
|
532
|
-
* @returns {
|
|
761
|
+
* @returns {{ consequent_body: any[], return_index: number } | null}
|
|
533
762
|
*/
|
|
534
|
-
function
|
|
535
|
-
if (!node || node.type !== 'IfStatement' || node.alternate) return
|
|
763
|
+
function get_returning_if_info(node) {
|
|
764
|
+
if (!node || node.type !== 'IfStatement' || node.alternate) return null;
|
|
536
765
|
const consequent = node.consequent;
|
|
537
|
-
if (!consequent) return
|
|
538
|
-
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
) {
|
|
545
|
-
return true;
|
|
766
|
+
if (!consequent) return null;
|
|
767
|
+
|
|
768
|
+
if (is_bare_return_statement(consequent)) {
|
|
769
|
+
return {
|
|
770
|
+
consequent_body: [consequent],
|
|
771
|
+
return_index: 0,
|
|
772
|
+
};
|
|
546
773
|
}
|
|
547
|
-
|
|
774
|
+
|
|
775
|
+
if (consequent.type === 'BlockStatement') {
|
|
776
|
+
const return_index = consequent.body.findIndex(is_bare_return_statement);
|
|
777
|
+
if (return_index !== -1) {
|
|
778
|
+
return {
|
|
779
|
+
consequent_body: consequent.body,
|
|
780
|
+
return_index,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return null;
|
|
548
786
|
}
|
|
549
787
|
|
|
550
788
|
/**
|
|
@@ -732,22 +970,32 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
732
970
|
transform_context.needs_for = true;
|
|
733
971
|
|
|
734
972
|
const loop_params = get_for_of_iteration_params(node.left, node.index);
|
|
735
|
-
const loop_body =
|
|
973
|
+
const loop_body = /** @type {any[]} */ (
|
|
974
|
+
rewrite_loop_continues_to_bare_returns(
|
|
975
|
+
node.body.type === 'BlockStatement' ? node.body.body : [node.body],
|
|
976
|
+
)
|
|
977
|
+
);
|
|
736
978
|
|
|
737
|
-
|
|
979
|
+
let arrow = /** @type {any} */ ({
|
|
980
|
+
type: 'ArrowFunctionExpression',
|
|
981
|
+
params: loop_params,
|
|
982
|
+
body: null,
|
|
983
|
+
async: false,
|
|
984
|
+
generator: false,
|
|
985
|
+
expression: true,
|
|
986
|
+
metadata: { path: [] },
|
|
987
|
+
});
|
|
738
988
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
type: '
|
|
742
|
-
|
|
743
|
-
body: null,
|
|
744
|
-
async: false,
|
|
745
|
-
generator: false,
|
|
746
|
-
expression: true,
|
|
989
|
+
if (body_has_loop_skip(loop_body)) {
|
|
990
|
+
arrow.body = {
|
|
991
|
+
type: 'BlockStatement',
|
|
992
|
+
body: loop_body_to_callback_statements(loop_body, transform_context),
|
|
747
993
|
metadata: { path: [] },
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
|
|
994
|
+
};
|
|
995
|
+
arrow.expression = false;
|
|
996
|
+
} else {
|
|
997
|
+
arrow = merge_branch_body_into_arrow(arrow, body_to_jsx_child(loop_body, transform_context));
|
|
998
|
+
}
|
|
751
999
|
|
|
752
1000
|
const attributes = [
|
|
753
1001
|
{
|