@tsrx/core 0.1.22 → 0.1.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.
@@ -7,6 +7,7 @@ import { print } from 'esrap';
7
7
  import { error } from '../../errors.js';
8
8
  import { analyze_css } from '../../analyze/css-analyze.js';
9
9
  import { prune_css } from '../../analyze/prune.js';
10
+ import { create_scopes, ScopeRoot } from '../../scope.js';
10
11
  import {
11
12
  in_jsx_child_context,
12
13
  set_node_path_metadata,
@@ -25,7 +26,6 @@ import {
25
26
  is_bare_render_expression,
26
27
  is_component_jsx_name,
27
28
  is_jsx_child,
28
- jsx_name_to_expression,
29
29
  set_loc,
30
30
  to_text_expression,
31
31
  } from './ast-builders.js';
@@ -64,7 +64,7 @@ const HOOK_CALLBACK_OUTER_MUTATION_ERROR =
64
64
  const TEMPLATE_FRAGMENT_ERROR =
65
65
  'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only in expression position.';
66
66
  const TSRX_FOR_RETURN_ERROR =
67
- 'Return statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering or use an @for empty fallback for empty lists.';
67
+ 'Return statements are not allowed inside TSRX template for...of loops. Filter the iterable before rendering or use an @empty fallback for empty lists.';
68
68
  const TSRX_FOR_BREAK_ERROR =
69
69
  'Break statements are not allowed inside TSRX template for...of loops.';
70
70
  const TSRX_FOR_CONTINUE_ERROR =
@@ -223,6 +223,86 @@ function expand_child_code_blocks(node, seen = new Set()) {
223
223
  }
224
224
  }
225
225
 
226
+ /**
227
+ * A `@`-prefixed JSX control-flow expression (`@if`/`@for`/`@switch`/`@try`).
228
+ * These are the only control-flow nodes that can appear in expression position;
229
+ * the plain statement forms (`IfStatement`, `SwitchStatement`, …) never do.
230
+ * @param {any} node
231
+ * @returns {boolean}
232
+ */
233
+ function is_jsx_control_flow_expression(node) {
234
+ return (
235
+ node?.type === 'JSXIfExpression' ||
236
+ node?.type === 'JSXForExpression' ||
237
+ node?.type === 'JSXSwitchExpression' ||
238
+ node?.type === 'JSXTryExpression'
239
+ );
240
+ }
241
+
242
+ /**
243
+ * Wrap a render-output node in a native TSRX fragment so it flows through the
244
+ * same single-child render path as a `<> … </>` output.
245
+ * @param {any} node
246
+ * @returns {any}
247
+ */
248
+ function wrap_in_native_tsrx_fragment(node) {
249
+ const fragment = b.jsx_fragment([node]);
250
+ fragment.metadata = { ...(fragment.metadata || {}), native_tsrx: true };
251
+ return fragment;
252
+ }
253
+
254
+ /**
255
+ * Wrap a bare JSX control-flow directive that sits directly in an expression
256
+ * position — an expression-bodied arrow (`() => @switch (…) { … }`), a
257
+ * `return @switch (…) { … }`, an unused expression statement,
258
+ * assignment to a variable
259
+ * (`const x = @switch (…) { … }`, `x = @switch (…) { … }`), or a call/`new`
260
+ * argument (`render(@if (…) { … })`) — in a native TSRX fragment.
261
+ * @param {any} node
262
+ * @param {Set<any>} [seen]
263
+ * @returns {void}
264
+ */
265
+ function wrap_control_flow_expression_values(node, seen = new Set()) {
266
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
267
+ seen.add(node);
268
+
269
+ if (Array.isArray(node)) {
270
+ for (const item of node) wrap_control_flow_expression_values(item, seen);
271
+ return;
272
+ }
273
+
274
+ if (
275
+ node.type === 'ArrowFunctionExpression' &&
276
+ node.body?.type !== 'BlockStatement' &&
277
+ is_jsx_control_flow_expression(node.body)
278
+ ) {
279
+ node.body = wrap_in_native_tsrx_fragment(node.body);
280
+ } else if (node.type === 'ReturnStatement' && is_jsx_control_flow_expression(node.argument)) {
281
+ node.argument = wrap_in_native_tsrx_fragment(node.argument);
282
+ } else if (
283
+ node.type === 'ExpressionStatement' &&
284
+ is_jsx_control_flow_expression(node.expression)
285
+ ) {
286
+ node.expression = wrap_in_native_tsrx_fragment(node.expression);
287
+ } else if (node.type === 'VariableDeclarator' && is_jsx_control_flow_expression(node.init)) {
288
+ node.init = wrap_in_native_tsrx_fragment(node.init);
289
+ } else if (node.type === 'AssignmentExpression' && is_jsx_control_flow_expression(node.right)) {
290
+ node.right = wrap_in_native_tsrx_fragment(node.right);
291
+ } else if (
292
+ (node.type === 'CallExpression' || node.type === 'NewExpression') &&
293
+ Array.isArray(node.arguments)
294
+ ) {
295
+ node.arguments = node.arguments.map((/** @type {any} */ arg) =>
296
+ is_jsx_control_flow_expression(arg) ? wrap_in_native_tsrx_fragment(arg) : arg,
297
+ );
298
+ }
299
+
300
+ for (const key of Object.keys(node)) {
301
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
302
+ wrap_control_flow_expression_values(node[key], seen);
303
+ }
304
+ }
305
+
226
306
  /**
227
307
  * Build a `transform()` function for a specific JSX platform (React, Preact,
228
308
  * Solid). Given a `JsxPlatform` descriptor, returns a transform that lowers
@@ -273,6 +353,7 @@ export function createJsxTransform(platform) {
273
353
  hook_helpers_enabled: false,
274
354
  available_bindings: new Map(),
275
355
  lazy_next_id: 0,
356
+ runtime_dynamic_scopes: null,
276
357
  filename: filename ?? null,
277
358
  source,
278
359
  collect,
@@ -285,6 +366,11 @@ export function createJsxTransform(platform) {
285
366
  };
286
367
 
287
368
  expand_child_code_blocks(/** @type {any} */ (ast));
369
+ wrap_control_flow_expression_values(/** @type {any} */ (ast));
370
+ transform_context.runtime_dynamic_scopes = create_runtime_dynamic_scopes(
371
+ /** @type {any} */ (ast),
372
+ transform_context,
373
+ );
288
374
 
289
375
  if (!transform_context.typeOnly) {
290
376
  preallocate_lazy_ids(/** @type {any} */ (ast), transform_context);
@@ -321,10 +407,6 @@ export function createJsxTransform(platform) {
321
407
  },
322
408
 
323
409
  JSXElement(node, { next, path, state }) {
324
- if (!node.metadata?.native_tsrx && is_dynamic_jsx_element(node)) {
325
- return dynamic_element_to_jsx(node, state, in_jsx_child_context(path));
326
- }
327
-
328
410
  if (!node.metadata?.native_tsrx) {
329
411
  return next() ?? node;
330
412
  }
@@ -464,16 +546,26 @@ export function createJsxTransform(platform) {
464
546
  *
465
547
  * @param {any} component
466
548
  * @param {any} css
549
+ * @param {TransformContext} transform_context
467
550
  * @param {boolean} [export_top_scoped_classes]
468
551
  * @returns {void}
469
552
  */
470
- function apply_css_definition_metadata(component, css, export_top_scoped_classes = false) {
553
+ function apply_css_definition_metadata(
554
+ component,
555
+ css,
556
+ transform_context,
557
+ export_top_scoped_classes = false,
558
+ ) {
471
559
  analyze_css(css);
472
560
 
473
561
  const metadata = component.metadata || (component.metadata = { path: [] });
474
562
  const style_classes = metadata.styleClasses || (metadata.styleClasses = new Map());
475
563
  const top_scoped_classes = metadata.topScopedClasses || new Map();
476
- const elements = collect_css_prunable_elements(component.body || component.children || []);
564
+ const elements = collect_css_prunable_elements(
565
+ component.body || component.children || [],
566
+ [],
567
+ transform_context,
568
+ );
477
569
 
478
570
  const prune = () => {
479
571
  for (const element of elements) {
@@ -498,16 +590,17 @@ function apply_css_definition_metadata(component, css, export_top_scoped_classes
498
590
  /**
499
591
  * @param {any} value
500
592
  * @param {any[]} [elements]
593
+ * @param {TransformContext | null} [transform_context]
501
594
  * @returns {any[]}
502
595
  */
503
- function collect_css_prunable_elements(value, elements = []) {
596
+ function collect_css_prunable_elements(value, elements = [], transform_context = null) {
504
597
  if (!value || typeof value !== 'object') {
505
598
  return elements;
506
599
  }
507
600
 
508
601
  if (Array.isArray(value)) {
509
602
  for (const child of value) {
510
- collect_css_prunable_elements(child, elements);
603
+ collect_css_prunable_elements(child, elements, transform_context);
511
604
  }
512
605
  return elements;
513
606
  }
@@ -521,6 +614,7 @@ function collect_css_prunable_elements(value, elements = []) {
521
614
  }
522
615
 
523
616
  if (value.type === 'JSXElement' && value.metadata?.native_tsrx) {
617
+ mark_runtime_dynamic_element(value, transform_context);
524
618
  if (!is_style_element(value)) {
525
619
  elements.push(value);
526
620
  }
@@ -530,12 +624,247 @@ function collect_css_prunable_elements(value, elements = []) {
530
624
  if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata' || key === 'css') {
531
625
  continue;
532
626
  }
533
- collect_css_prunable_elements(value[key], elements);
627
+ collect_css_prunable_elements(value[key], elements, transform_context);
534
628
  }
535
629
 
536
630
  return elements;
537
631
  }
538
632
 
633
+ /**
634
+ * @param {AST.Program} ast
635
+ * @param {TransformContext} transform_context
636
+ * @returns {Map<any, any> | null}
637
+ */
638
+ function create_runtime_dynamic_scopes(ast, transform_context) {
639
+ const dynamic_source = transform_context.platform.imports.dynamic;
640
+ if (!dynamic_source) {
641
+ return null;
642
+ }
643
+ if (!has_runtime_dynamic_import(ast, dynamic_source)) {
644
+ return null;
645
+ }
646
+
647
+ const { scopes } = create_scopes(ast, new ScopeRoot(), null, {
648
+ collect: true,
649
+ errors: [],
650
+ filename: transform_context.filename ?? '',
651
+ comments: transform_context.comments,
652
+ });
653
+
654
+ return scopes;
655
+ }
656
+
657
+ /**
658
+ * @param {any} node
659
+ * @param {TransformContext | null} transform_context
660
+ * @returns {void}
661
+ */
662
+ function mark_runtime_dynamic_element(node, transform_context) {
663
+ const dynamic_source = transform_context?.platform.imports.dynamic;
664
+ const scopes = transform_context?.runtime_dynamic_scopes;
665
+ if (
666
+ !dynamic_source ||
667
+ !scopes ||
668
+ node.metadata?.runtime_dynamic_element === true ||
669
+ !has_jsx_attribute(node, 'is') ||
670
+ !is_runtime_dynamic_jsx_name(node.openingElement?.name, scopes.get(node), dynamic_source)
671
+ ) {
672
+ return;
673
+ }
674
+
675
+ node.metadata.runtime_dynamic_element = true;
676
+ }
677
+
678
+ /**
679
+ * @param {AST.Program} ast
680
+ * @param {string} dynamic_source
681
+ * @returns {boolean}
682
+ */
683
+ function has_runtime_dynamic_import(ast, dynamic_source) {
684
+ return ast.body.some(
685
+ (/** @type {any} */ node) =>
686
+ node.type === 'ImportDeclaration' &&
687
+ node.importKind !== 'type' &&
688
+ node.source?.type === 'Literal' &&
689
+ node.source.value === dynamic_source &&
690
+ node.specifiers.some(
691
+ (/** @type {any} */ specifier) =>
692
+ is_runtime_dynamic_import_specifier(specifier, 'component') ||
693
+ is_runtime_dynamic_import_specifier(specifier, 'namespace'),
694
+ ),
695
+ );
696
+ }
697
+
698
+ /**
699
+ * @param {any} node
700
+ * @param {string} name
701
+ * @returns {boolean}
702
+ */
703
+ function has_jsx_attribute(node, name) {
704
+ return (node.openingElement?.attributes ?? []).some(
705
+ (/** @type {any} */ attr) =>
706
+ attr.type === 'JSXAttribute' &&
707
+ attr.name?.type === 'JSXIdentifier' &&
708
+ attr.name.name === name,
709
+ );
710
+ }
711
+
712
+ /**
713
+ * @param {any} name
714
+ * @param {any} scope
715
+ * @param {string} dynamic_source
716
+ * @returns {boolean}
717
+ */
718
+ function is_runtime_dynamic_jsx_name(name, scope, dynamic_source) {
719
+ if (!scope || !name) {
720
+ return false;
721
+ }
722
+
723
+ if (name.type === 'JSXIdentifier') {
724
+ return is_runtime_dynamic_binding(scope.get(name.name), dynamic_source, 'component', new Set());
725
+ }
726
+
727
+ if (
728
+ name.type === 'JSXMemberExpression' &&
729
+ name.object?.type === 'JSXIdentifier' &&
730
+ name.property?.type === 'JSXIdentifier' &&
731
+ name.property.name === 'Dynamic'
732
+ ) {
733
+ return is_runtime_dynamic_binding(
734
+ scope.get(name.object.name),
735
+ dynamic_source,
736
+ 'namespace',
737
+ new Set(),
738
+ );
739
+ }
740
+
741
+ return false;
742
+ }
743
+
744
+ /**
745
+ * @param {any} binding
746
+ * @param {string} dynamic_source
747
+ * @param {'component' | 'namespace'} kind
748
+ * @param {Set<any>} seen
749
+ * @returns {boolean}
750
+ */
751
+ function is_runtime_dynamic_binding(binding, dynamic_source, kind, seen) {
752
+ if (!binding || seen.has(binding)) {
753
+ return false;
754
+ }
755
+ seen.add(binding);
756
+
757
+ if (is_runtime_dynamic_import_binding(binding, dynamic_source, kind)) {
758
+ return true;
759
+ }
760
+
761
+ if (binding.reassigned) {
762
+ return false;
763
+ }
764
+
765
+ const initial = unwrap_reference_expression(binding.initial);
766
+ if (!initial) {
767
+ return false;
768
+ }
769
+
770
+ if (initial.type === 'Identifier') {
771
+ return is_runtime_dynamic_binding(binding.scope.get(initial.name), dynamic_source, kind, seen);
772
+ }
773
+
774
+ if (
775
+ kind === 'component' &&
776
+ initial.type === 'MemberExpression' &&
777
+ !initial.computed &&
778
+ initial.object?.type === 'Identifier' &&
779
+ initial.property?.type === 'Identifier' &&
780
+ initial.property.name === 'Dynamic'
781
+ ) {
782
+ return is_runtime_dynamic_binding(
783
+ binding.scope.get(initial.object.name),
784
+ dynamic_source,
785
+ 'namespace',
786
+ new Set(),
787
+ );
788
+ }
789
+
790
+ return false;
791
+ }
792
+
793
+ /**
794
+ * @param {any} binding
795
+ * @param {string} dynamic_source
796
+ * @param {'component' | 'namespace'} kind
797
+ * @returns {boolean}
798
+ */
799
+ function is_runtime_dynamic_import_binding(binding, dynamic_source, kind) {
800
+ const declaration = binding?.initial;
801
+ if (
802
+ binding?.declaration_kind !== 'import' ||
803
+ declaration?.type !== 'ImportDeclaration' ||
804
+ declaration.importKind === 'type' ||
805
+ declaration.source?.type !== 'Literal' ||
806
+ declaration.source.value !== dynamic_source
807
+ ) {
808
+ return false;
809
+ }
810
+
811
+ return declaration.specifiers.some(
812
+ (/** @type {any} */ specifier) =>
813
+ specifier.local?.name === binding.node?.name &&
814
+ is_runtime_dynamic_import_specifier(specifier, kind),
815
+ );
816
+ }
817
+
818
+ /**
819
+ * @param {any} specifier
820
+ * @param {'component' | 'namespace'} kind
821
+ * @returns {boolean}
822
+ */
823
+ function is_runtime_dynamic_import_specifier(specifier, kind) {
824
+ if (kind === 'namespace') {
825
+ return specifier.type === 'ImportNamespaceSpecifier';
826
+ }
827
+ return (
828
+ specifier.type === 'ImportSpecifier' &&
829
+ specifier.importKind !== 'type' &&
830
+ get_imported_name(specifier) === 'Dynamic'
831
+ );
832
+ }
833
+
834
+ /**
835
+ * @param {any} specifier
836
+ * @returns {string | null}
837
+ */
838
+ function get_imported_name(specifier) {
839
+ const imported = specifier.imported;
840
+ if (imported?.type === 'Identifier') {
841
+ return imported.name;
842
+ }
843
+ if (imported?.type === 'Literal') {
844
+ return String(imported.value);
845
+ }
846
+ return null;
847
+ }
848
+
849
+ /**
850
+ * @param {any} expression
851
+ * @returns {any}
852
+ */
853
+ function unwrap_reference_expression(expression) {
854
+ let node = expression;
855
+ while (
856
+ node &&
857
+ (node.type === 'TSAsExpression' ||
858
+ node.type === 'TSTypeAssertion' ||
859
+ node.type === 'TSNonNullExpression' ||
860
+ node.type === 'ParenthesizedExpression' ||
861
+ node.type === 'ChainExpression')
862
+ ) {
863
+ node = node.expression;
864
+ }
865
+ return node;
866
+ }
867
+
539
868
  /**
540
869
  * @param {any[]} body_nodes
541
870
  * @param {TransformContext} transform_context
@@ -598,7 +927,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
598
927
  (node) =>
599
928
  !is_loop_skip_return_statement(node) &&
600
929
  !is_loop_skip_if_statement(node) &&
601
- !is_jsx_child(node),
930
+ !is_render_child_node(node),
602
931
  );
603
932
 
604
933
  if (!continuation_has_setup_statements) {
@@ -640,7 +969,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
640
969
  }
641
970
 
642
971
  if (
643
- is_for_of_control_node(child) &&
972
+ is_template_for_of_node(child) &&
644
973
  !child.await &&
645
974
  should_extract_hook_helpers(transform_context) &&
646
975
  !transform_context.platform.hooks?.isTopLevelSetupCall &&
@@ -668,7 +997,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
668
997
  }
669
998
  }
670
999
 
671
- if (is_jsx_child(child)) {
1000
+ if (is_render_child_node(child)) {
672
1001
  const jsx = to_jsx_child(child, transform_context);
673
1002
  statements.push(...extract_jsx_setup_declarations(jsx));
674
1003
  if (interleaved && is_capturable_jsx_child(jsx)) {
@@ -705,7 +1034,7 @@ function build_render_statements(body_nodes, return_null_when_empty, transform_c
705
1034
  * @returns {boolean}
706
1035
  */
707
1036
  function is_interleaved_body(body_nodes) {
708
- return is_interleaved_body_core(body_nodes, is_jsx_child);
1037
+ return is_interleaved_body_core(body_nodes, is_render_child_node);
709
1038
  }
710
1039
 
711
1040
  /**
@@ -714,10 +1043,12 @@ function is_interleaved_body(body_nodes) {
714
1043
  * @returns {boolean}
715
1044
  */
716
1045
  function needs_hook_split(node, transform_context) {
1046
+ const body = node.body?.body || [];
717
1047
  return (
718
1048
  transform_context.platform.hooks?.componentBodyHookHelpers === true &&
719
1049
  node.body?.type === 'BlockStatement' &&
720
- find_hook_split_index(node.body.body || [], transform_context) !== -1
1050
+ (find_hook_split_index(body, transform_context) !== -1 ||
1051
+ body_contains_component_body_branch_hook_return(body, transform_context))
721
1052
  );
722
1053
  }
723
1054
 
@@ -735,29 +1066,35 @@ function create_hook_split_block(node, transform_context) {
735
1066
  return null;
736
1067
  }
737
1068
 
738
- const body = node.body.body || [];
1069
+ const source_body = node.body.body || [];
1070
+ const branch_rewrite = rewrite_component_body_branch_hook_returns(source_body, transform_context);
1071
+ const body = branch_rewrite.body;
739
1072
  const split_index = find_hook_split_index(body, transform_context);
740
- if (split_index === -1) {
1073
+ if (split_index === -1 && !branch_rewrite.changed) {
741
1074
  return null;
742
1075
  }
743
1076
 
744
- const split_statement = body[split_index];
745
- const continuation_body = body.slice(split_index + 1);
746
- const helper = create_hook_safe_helper(
747
- expand_native_tsrx_return_statement_list(continuation_body, transform_context),
748
- undefined,
749
- get_body_source_node(continuation_body) || split_statement,
750
- transform_context,
751
- );
1077
+ let block_body;
1078
+ if (split_index === -1) {
1079
+ block_body = expand_native_tsrx_return_statement_list(body, transform_context);
1080
+ } else {
1081
+ const split_statement = body[split_index];
1082
+ const continuation_body = body.slice(split_index + 1);
1083
+ const helper = create_hook_safe_helper(
1084
+ expand_native_tsrx_return_statement_list(continuation_body, transform_context),
1085
+ undefined,
1086
+ get_body_source_node(continuation_body) || split_statement,
1087
+ transform_context,
1088
+ );
752
1089
 
753
- const block = b.block(
754
- [
1090
+ block_body = [
755
1091
  ...body.slice(0, split_index + 1),
756
1092
  ...helper.setup_statements,
757
1093
  set_loc(b.return(helper.component_element), split_statement),
758
- ],
759
- node.body,
760
- );
1094
+ ];
1095
+ }
1096
+
1097
+ const block = b.block(block_body, node.body);
761
1098
  block.metadata = {
762
1099
  ...(block.metadata || {}),
763
1100
  hook_split_block: true,
@@ -765,6 +1102,228 @@ function create_hook_split_block(node, transform_context) {
765
1102
  return block;
766
1103
  }
767
1104
 
1105
+ /**
1106
+ * @param {any[]} body_nodes
1107
+ * @param {TransformContext} transform_context
1108
+ * @returns {boolean}
1109
+ */
1110
+ function body_contains_component_body_branch_hook_return(body_nodes, transform_context) {
1111
+ return body_nodes.some((node) =>
1112
+ statement_contains_component_body_branch_hook_return(node, transform_context),
1113
+ );
1114
+ }
1115
+
1116
+ /**
1117
+ * @param {any} node
1118
+ * @param {TransformContext} transform_context
1119
+ * @returns {boolean}
1120
+ */
1121
+ function statement_contains_component_body_branch_hook_return(node, transform_context) {
1122
+ if (!node || typeof node !== 'object') {
1123
+ return false;
1124
+ }
1125
+
1126
+ if (Array.isArray(node)) {
1127
+ return body_contains_component_body_branch_hook_return(node, transform_context);
1128
+ }
1129
+
1130
+ if (is_function_or_class_boundary(node)) {
1131
+ return false;
1132
+ }
1133
+
1134
+ if (is_plain_if_statement(node)) {
1135
+ return (
1136
+ branch_needs_component_body_hook_helper(node.consequent, transform_context) ||
1137
+ statement_contains_component_body_branch_hook_return(node.consequent, transform_context) ||
1138
+ branch_needs_component_body_hook_helper(node.alternate, transform_context) ||
1139
+ statement_contains_component_body_branch_hook_return(node.alternate, transform_context)
1140
+ );
1141
+ }
1142
+
1143
+ if (node.type === 'BlockStatement') {
1144
+ return body_contains_component_body_branch_hook_return(node.body || [], transform_context);
1145
+ }
1146
+
1147
+ return false;
1148
+ }
1149
+
1150
+ /**
1151
+ * @param {any[]} body_nodes
1152
+ * @param {TransformContext} transform_context
1153
+ * @returns {{ body: any[], changed: boolean }}
1154
+ */
1155
+ function rewrite_component_body_branch_hook_returns(body_nodes, transform_context) {
1156
+ let changed = false;
1157
+ const body = body_nodes.map((node) => {
1158
+ const next_node = rewrite_component_body_branch_hook_return_statement(node, transform_context);
1159
+ if (next_node !== node) {
1160
+ changed = true;
1161
+ }
1162
+ return next_node;
1163
+ });
1164
+ return changed ? { body, changed } : { body: body_nodes, changed: false };
1165
+ }
1166
+
1167
+ /**
1168
+ * @param {any} node
1169
+ * @param {TransformContext} transform_context
1170
+ * @returns {any}
1171
+ */
1172
+ function rewrite_component_body_branch_hook_return_statement(node, transform_context) {
1173
+ if (!node || typeof node !== 'object' || is_function_or_class_boundary(node)) {
1174
+ return node;
1175
+ }
1176
+
1177
+ if (is_plain_if_statement(node)) {
1178
+ const consequent = rewrite_component_body_hook_return_branch(
1179
+ node.consequent,
1180
+ transform_context,
1181
+ );
1182
+ const alternate = node.alternate
1183
+ ? rewrite_component_body_hook_return_branch(node.alternate, transform_context)
1184
+ : { node: node.alternate, changed: false };
1185
+
1186
+ if (!consequent.changed && !alternate.changed) {
1187
+ return node;
1188
+ }
1189
+ return set_loc(b.if(node.test, consequent.node, alternate.node), node);
1190
+ }
1191
+
1192
+ if (node.type === 'BlockStatement') {
1193
+ const rewritten = rewrite_component_body_branch_hook_returns(
1194
+ node.body || [],
1195
+ transform_context,
1196
+ );
1197
+ return rewritten.changed ? set_loc(b.block(rewritten.body, node), node) : node;
1198
+ }
1199
+
1200
+ return node;
1201
+ }
1202
+
1203
+ /**
1204
+ * @param {any} branch
1205
+ * @param {TransformContext} transform_context
1206
+ * @returns {{ node: any, changed: boolean }}
1207
+ */
1208
+ function rewrite_component_body_hook_return_branch(branch, transform_context) {
1209
+ if (!branch || typeof branch !== 'object') {
1210
+ return { node: branch, changed: false };
1211
+ }
1212
+
1213
+ if (is_plain_if_statement(branch)) {
1214
+ const next_node = rewrite_component_body_branch_hook_return_statement(
1215
+ branch,
1216
+ transform_context,
1217
+ );
1218
+ return { node: next_node, changed: next_node !== branch };
1219
+ }
1220
+
1221
+ const branch_body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1222
+ const rewritten = rewrite_component_body_branch_hook_returns(branch_body, transform_context);
1223
+ const body = rewritten.body;
1224
+ const needs_helper = branch_needs_component_body_hook_helper_body(body, transform_context);
1225
+
1226
+ if (!needs_helper) {
1227
+ if (!rewritten.changed) {
1228
+ return { node: branch, changed: false };
1229
+ }
1230
+ const node =
1231
+ branch.type === 'BlockStatement'
1232
+ ? set_loc(b.block(body, branch), branch)
1233
+ : (body[0] ?? branch);
1234
+ return { node, changed: true };
1235
+ }
1236
+
1237
+ const helper_body = expand_native_tsrx_return_statement_list(body, transform_context);
1238
+ const helper = create_hook_safe_helper(
1239
+ helper_body,
1240
+ undefined,
1241
+ get_body_source_node(body) || branch,
1242
+ transform_context,
1243
+ );
1244
+ const node = set_loc(
1245
+ b.block([...helper.setup_statements, set_loc(b.return(helper.component_element), branch)]),
1246
+ branch,
1247
+ );
1248
+ return { node, changed: true };
1249
+ }
1250
+
1251
+ /**
1252
+ * @param {any} branch
1253
+ * @param {TransformContext} transform_context
1254
+ * @returns {boolean}
1255
+ */
1256
+ function branch_needs_component_body_hook_helper(branch, transform_context) {
1257
+ if (!branch || typeof branch !== 'object' || is_plain_if_statement(branch)) {
1258
+ return false;
1259
+ }
1260
+ const body = branch.type === 'BlockStatement' ? branch.body || [] : [branch];
1261
+ return branch_needs_component_body_hook_helper_body(body, transform_context);
1262
+ }
1263
+
1264
+ /**
1265
+ * @param {any[]} body
1266
+ * @param {TransformContext} transform_context
1267
+ * @returns {boolean}
1268
+ */
1269
+ function branch_needs_component_body_hook_helper_body(body, transform_context) {
1270
+ return (
1271
+ body_has_top_level_component_body_return(body) &&
1272
+ body_contains_direct_top_level_hook_call(body, transform_context, true)
1273
+ );
1274
+ }
1275
+
1276
+ /**
1277
+ * @param {any[]} body
1278
+ * @returns {boolean}
1279
+ */
1280
+ function body_has_top_level_component_body_return(body) {
1281
+ return body.some((node) => node?.type === 'ReturnStatement');
1282
+ }
1283
+
1284
+ /**
1285
+ * @param {any[]} body
1286
+ * @param {TransformContext} transform_context
1287
+ * @param {boolean} include_platform_setup
1288
+ * @returns {boolean}
1289
+ */
1290
+ function body_contains_direct_top_level_hook_call(body, transform_context, include_platform_setup) {
1291
+ return body.some((node) =>
1292
+ statement_contains_direct_top_level_hook_call(node, transform_context, include_platform_setup),
1293
+ );
1294
+ }
1295
+
1296
+ /**
1297
+ * @param {any} node
1298
+ * @param {TransformContext} transform_context
1299
+ * @param {boolean} include_platform_setup
1300
+ * @returns {boolean}
1301
+ */
1302
+ function statement_contains_direct_top_level_hook_call(
1303
+ node,
1304
+ transform_context,
1305
+ include_platform_setup,
1306
+ ) {
1307
+ if (!node || typeof node !== 'object') {
1308
+ return false;
1309
+ }
1310
+
1311
+ if (is_function_or_class_boundary(node)) {
1312
+ return false;
1313
+ }
1314
+
1315
+ if (
1316
+ is_plain_if_statement(node) ||
1317
+ is_switch_control_node(node) ||
1318
+ is_try_control_node(node) ||
1319
+ is_for_of_control_node(node)
1320
+ ) {
1321
+ return false;
1322
+ }
1323
+
1324
+ return statement_contains_top_level_hook_call(node, transform_context, include_platform_setup);
1325
+ }
1326
+
768
1327
  /**
769
1328
  * @param {any[]} body_nodes
770
1329
  * @param {TransformContext} transform_context
@@ -1114,7 +1673,7 @@ function transform_block_statement(node, { next, visit, state, path }) {
1114
1673
  }
1115
1674
  }
1116
1675
 
1117
- if (get_active_native_tsrx_function(path)) {
1676
+ if (get_active_native_tsrx_function(path)?.metadata?.native_tsrx_body) {
1118
1677
  const block = create_native_tsrx_statement_list_block(node, state);
1119
1678
  if (block) {
1120
1679
  return visit(block, state);
@@ -1130,7 +1689,22 @@ function transform_block_statement(node, { next, visit, state, path }) {
1130
1689
  * @returns {any}
1131
1690
  */
1132
1691
  function transform_return_statement(node, { next, visit, state, path }) {
1133
- if (get_active_native_tsrx_function(path) && is_native_tsrx_node(node.argument)) {
1692
+ const active_native_tsrx_function = get_active_native_tsrx_function(path);
1693
+ if (active_native_tsrx_function && is_native_tsrx_node(node.argument)) {
1694
+ if (!active_native_tsrx_function.metadata?.native_tsrx_body) {
1695
+ const statements = mark_native_pretransformed_jsx(
1696
+ create_native_tsrx_render_statements(node.argument, state),
1697
+ );
1698
+ if (statements.length === 1) {
1699
+ return visit(statements[0], state);
1700
+ }
1701
+ const block = b.block(statements, node.argument);
1702
+ block.metadata = {
1703
+ ...(block.metadata || {}),
1704
+ native_return_block: true,
1705
+ };
1706
+ return visit(block, state);
1707
+ }
1134
1708
  return visit(create_native_tsrx_render_block(node.argument, state), state);
1135
1709
  }
1136
1710
 
@@ -1190,10 +1764,17 @@ function transform_function(node, context) {
1190
1764
  // render output. The parser already marks the render JSX as native_tsrx, so
1191
1765
  // from here it flows through the existing native-component machinery exactly
1192
1766
  // like the older fenced `{ return <> … </> }` shape.
1767
+ const has_jsx_code_block_body = node.body?.type === 'JSXCodeBlock';
1193
1768
  lower_jsx_code_block_function_body(node);
1194
1769
 
1195
- if (node.metadata?.native_tsrx_function || function_has_native_tsrx_return(node)) {
1196
- return transform_native_tsrx_function(node, context);
1770
+ if (
1771
+ has_jsx_code_block_body ||
1772
+ node.metadata?.native_tsrx_function ||
1773
+ function_has_native_tsrx_return(node)
1774
+ ) {
1775
+ return transform_native_tsrx_function(node, context, {
1776
+ nativeBody: has_jsx_code_block_body || !!node.metadata?.native_tsrx_function,
1777
+ });
1197
1778
  }
1198
1779
 
1199
1780
  return transform_function_with_hook_helpers(node, context);
@@ -1230,9 +1811,10 @@ function lower_jsx_code_block_function_body(node) {
1230
1811
  /**
1231
1812
  * @param {any} node
1232
1813
  * @param {{ next: () => any, state: TransformContext }} context
1814
+ * @param {{ nativeBody?: boolean }} [options]
1233
1815
  * @returns {any}
1234
1816
  */
1235
- function transform_native_tsrx_function(node, { next, state }) {
1817
+ function transform_native_tsrx_function(node, { next, state }, { nativeBody = false } = {}) {
1236
1818
  const helper_state =
1237
1819
  state.helper_state || create_helper_state(get_function_helper_base_name(node));
1238
1820
  const saved_helper_state = state.helper_state;
@@ -1244,7 +1826,8 @@ function transform_native_tsrx_function(node, { next, state }) {
1244
1826
  node.metadata = {
1245
1827
  ...(node.metadata || {}),
1246
1828
  native_tsrx: true,
1247
- ...(needs_hook_split(node, state) ? { hook_split: true } : {}),
1829
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1830
+ ...(nativeBody && needs_hook_split(node, state) ? { hook_split: true } : {}),
1248
1831
  };
1249
1832
  state.available_bindings = merge_binding_maps(
1250
1833
  saved_bindings,
@@ -1270,6 +1853,7 @@ function transform_native_tsrx_function(node, { next, state }) {
1270
1853
  inner.metadata = {
1271
1854
  ...strip_function_transform_metadata(inner.metadata),
1272
1855
  native_tsrx_function: true,
1856
+ ...(nativeBody ? { native_tsrx_body: true } : {}),
1273
1857
  ...(!saved_helper_state ? create_generated_helper_metadata(helper_state) || {} : {}),
1274
1858
  };
1275
1859
 
@@ -1396,12 +1980,7 @@ function find_native_await_in_statement(statement) {
1396
1980
  */
1397
1981
  function transform_function_with_hook_helpers(node, { next, state }) {
1398
1982
  const has_hook_bearing_tsrx = function_contains_hook_bearing_tsrx(node, state);
1399
- const has_hook_split = needs_hook_split(node, state);
1400
- if (
1401
- state.helper_state ||
1402
- !is_uppercase_function_like(node) ||
1403
- (!has_hook_bearing_tsrx && !has_hook_split)
1404
- ) {
1983
+ if (state.helper_state || !is_uppercase_function_like(node) || !has_hook_bearing_tsrx) {
1405
1984
  return next() ?? node;
1406
1985
  }
1407
1986
 
@@ -1412,12 +1991,6 @@ function transform_function_with_hook_helpers(node, { next, state }) {
1412
1991
 
1413
1992
  state.helper_state = helper_state;
1414
1993
  state.hook_helpers_enabled = true;
1415
- if (has_hook_split) {
1416
- node.metadata = {
1417
- ...(node.metadata || {}),
1418
- hook_split: true,
1419
- };
1420
- }
1421
1994
  state.available_bindings = collect_function_scope_bindings(node);
1422
1995
 
1423
1996
  const inner = /** @type {any} */ (next() ?? node);
@@ -1681,7 +2254,7 @@ function prepare_tsrx_fragment_styles(node, transform_context) {
1681
2254
  if (!css) return null;
1682
2255
 
1683
2256
  const style_refs = collect_style_ref_attributes(node);
1684
- apply_css_definition_metadata(node, css, style_refs.length > 0);
2257
+ apply_css_definition_metadata(node, css, transform_context, style_refs.length > 0);
1685
2258
  transform_context.stylesheets.push(css);
1686
2259
  const fragment = annotate_tsrx_with_hash(
1687
2260
  node,
@@ -3056,7 +3629,7 @@ function to_jsx_element(
3056
3629
  raw_children = node.children || [],
3057
3630
  in_jsx_child = false,
3058
3631
  ) {
3059
- if (node.type === 'JSXElement' && !node.metadata?.native_tsrx && !is_dynamic_jsx_element(node)) {
3632
+ if (node.type === 'JSXElement' && !node.metadata?.native_tsrx) {
3060
3633
  return node;
3061
3634
  }
3062
3635
 
@@ -3066,10 +3639,6 @@ function to_jsx_element(
3066
3639
  report_jsx_fragment_in_tsrx_error(node, transform_context);
3067
3640
  return set_loc(b.jsx_fragment(), node);
3068
3641
  }
3069
- if (is_dynamic_jsx_element(node)) {
3070
- return dynamic_element_to_jsx(node, transform_context, in_jsx_child);
3071
- }
3072
-
3073
3642
  const name = clone_jsx_name(source_name);
3074
3643
  const attributes = transform_element_attributes_dispatch(
3075
3644
  source_opening.attributes || [],
@@ -3205,7 +3774,7 @@ function child_contains_return_semantics(node) {
3205
3774
  * @returns {boolean}
3206
3775
  */
3207
3776
  function is_inline_element_child(node) {
3208
- return node && is_jsx_child(node);
3777
+ return node && is_render_child_node(node);
3209
3778
  }
3210
3779
 
3211
3780
  /**
@@ -4314,31 +4883,46 @@ function is_native_tsrx_node(node) {
4314
4883
  * @param {any} node
4315
4884
  * @returns {boolean}
4316
4885
  */
4317
- function is_dynamic_jsx_element(node) {
4318
- return !!(
4319
- node?.type === 'JSXElement' &&
4320
- (node.dynamic === true ||
4321
- node.openingElement?.dynamic === true ||
4322
- is_dynamic_jsx_name(node.openingElement?.name))
4323
- );
4886
+ function is_if_control_node(node) {
4887
+ return node?.type === 'IfStatement' || node?.type === 'JSXIfExpression';
4324
4888
  }
4325
4889
 
4326
4890
  /**
4327
- * @param {any} name
4891
+ * @param {any} node
4328
4892
  * @returns {boolean}
4329
4893
  */
4330
- function is_dynamic_jsx_name(name) {
4331
- if (!name || typeof name !== 'object') return false;
4332
- if (name.dynamic === true) return true;
4333
- return name.type === 'JSXMemberExpression' && is_dynamic_jsx_name(name.object);
4894
+ function is_plain_if_statement(node) {
4895
+ return node?.type === 'IfStatement' && !is_template_if_node(node);
4334
4896
  }
4335
4897
 
4336
4898
  /**
4337
4899
  * @param {any} node
4338
4900
  * @returns {boolean}
4339
4901
  */
4340
- function is_if_control_node(node) {
4341
- return node?.type === 'IfStatement' || node?.type === 'JSXIfExpression';
4902
+ function is_render_child_node(node) {
4903
+ if (!node) return false;
4904
+
4905
+ switch (node.type) {
4906
+ case 'JSXElement':
4907
+ case 'JSXFragment':
4908
+ case 'JSXExpressionContainer':
4909
+ case 'JSXText':
4910
+ case 'JSXIfExpression':
4911
+ case 'JSXForExpression':
4912
+ case 'JSXSwitchExpression':
4913
+ case 'JSXTryExpression':
4914
+ return true;
4915
+ case 'IfStatement':
4916
+ return is_template_if_node(node);
4917
+ case 'ForOfStatement':
4918
+ return is_template_for_of_node(node);
4919
+ case 'SwitchStatement':
4920
+ return is_template_switch_node(node);
4921
+ case 'TryStatement':
4922
+ return is_template_try_node(node);
4923
+ default:
4924
+ return false;
4925
+ }
4342
4926
  }
4343
4927
 
4344
4928
  /**
@@ -4380,9 +4964,6 @@ function to_jsx_child(node, transform_context) {
4380
4964
  if (is_native_tsrx_node(node)) {
4381
4965
  return to_jsx_element(node, transform_context, node.children || [], true);
4382
4966
  }
4383
- if (is_dynamic_jsx_element(node)) {
4384
- return dynamic_element_to_jsx(node, transform_context, true);
4385
- }
4386
4967
  return node;
4387
4968
  case 'JSXFragment':
4388
4969
  if (is_native_tsrx_node(node)) {
@@ -4391,6 +4972,9 @@ function to_jsx_child(node, transform_context) {
4391
4972
  return node;
4392
4973
  case 'JSXIfExpression':
4393
4974
  case 'IfStatement':
4975
+ if (node.type === 'IfStatement' && !is_template_if_node(node)) {
4976
+ return node;
4977
+ }
4394
4978
  if (node.metadata?.generated_loop_skip_if) {
4395
4979
  return node;
4396
4980
  }
@@ -4412,17 +4996,26 @@ function to_jsx_child(node, transform_context) {
4412
4996
  transform_context.platform.hooks?.controlFlow?.forOf ?? for_of_statement_to_jsx_child
4413
4997
  )(jsx_control_expression_to_statement(node), transform_context);
4414
4998
  case 'ForOfStatement':
4999
+ if (!is_template_for_of_node(node)) {
5000
+ return node;
5001
+ }
4415
5002
  return (
4416
5003
  transform_context.platform.hooks?.controlFlow?.forOf ?? for_of_statement_to_jsx_child
4417
5004
  )(node, transform_context);
4418
5005
  case 'JSXSwitchExpression':
4419
5006
  case 'SwitchStatement':
5007
+ if (node.type === 'SwitchStatement' && !is_template_switch_node(node)) {
5008
+ return node;
5009
+ }
4420
5010
  return (
4421
5011
  transform_context.platform.hooks?.controlFlow?.switchStatement ??
4422
5012
  switch_statement_to_jsx_child
4423
5013
  )(jsx_control_expression_to_statement(node), transform_context);
4424
5014
  case 'JSXTryExpression':
4425
5015
  case 'TryStatement':
5016
+ if (node.type === 'TryStatement' && !is_template_try_node(node)) {
5017
+ return node;
5018
+ }
4426
5019
  return (
4427
5020
  transform_context.platform.hooks?.controlFlow?.tryStatement ?? try_statement_to_jsx_child
4428
5021
  )(jsx_control_expression_to_statement(node), transform_context);
@@ -4910,6 +5503,42 @@ function is_template_if_node(node) {
4910
5503
  );
4911
5504
  }
4912
5505
 
5506
+ /**
5507
+ * @param {any} node
5508
+ * @returns {boolean}
5509
+ */
5510
+ function is_template_for_of_node(node) {
5511
+ return (
5512
+ node?.type === 'JSXForExpression' ||
5513
+ node?.metadata?.tsrxDirective === 'for' ||
5514
+ (node?.type === 'ForOfStatement' && node?.statementType === 'ForOfStatement')
5515
+ );
5516
+ }
5517
+
5518
+ /**
5519
+ * @param {any} node
5520
+ * @returns {boolean}
5521
+ */
5522
+ function is_template_switch_node(node) {
5523
+ return (
5524
+ node?.type === 'JSXSwitchExpression' ||
5525
+ node?.metadata?.tsrxDirective === 'switch' ||
5526
+ (node?.type === 'SwitchStatement' && node?.statementType === 'SwitchStatement')
5527
+ );
5528
+ }
5529
+
5530
+ /**
5531
+ * @param {any} node
5532
+ * @returns {boolean}
5533
+ */
5534
+ function is_template_try_node(node) {
5535
+ return (
5536
+ node?.type === 'JSXTryExpression' ||
5537
+ node?.metadata?.tsrxDirective === 'try' ||
5538
+ (node?.type === 'TryStatement' && node?.statementType === 'TryStatement')
5539
+ );
5540
+ }
5541
+
4913
5542
  /**
4914
5543
  * @param {any} node
4915
5544
  * @returns {boolean}
@@ -5707,7 +6336,7 @@ function build_switch_with_lift(switch_node, transform_context) {
5707
6336
  has_terminal = true;
5708
6337
  break;
5709
6338
  }
5710
- if (is_jsx_child(child)) {
6339
+ if (is_render_child_node(child)) {
5711
6340
  render_nodes.push(to_jsx_child(child, transform_context));
5712
6341
  } else if (is_bare_render_expression(child)) {
5713
6342
  render_nodes.push(to_jsx_expression_container(child, child));
@@ -6266,57 +6895,6 @@ function value_has_unmappable_jsx_loc(value) {
6266
6895
  );
6267
6896
  }
6268
6897
 
6269
- /**
6270
- * @param {any} node
6271
- * @param {TransformContext} transform_context
6272
- * @param {boolean} in_jsx_child
6273
- * @returns {any}
6274
- */
6275
- function dynamic_element_to_jsx(node, transform_context, in_jsx_child) {
6276
- const source_name = node.openingElement?.name;
6277
- const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), source_name || node);
6278
- const alias_declaration = set_loc(
6279
- b.const(dynamic_id, jsx_name_to_expression(source_name)),
6280
- source_name || node,
6281
- );
6282
- const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
6283
-
6284
- const expression = b.call(
6285
- b.arrow(
6286
- [],
6287
- b.block([
6288
- alias_declaration,
6289
- b.return(b.conditional(clone_identifier(dynamic_id), jsx_element, create_null_literal())),
6290
- ]),
6291
- ),
6292
- );
6293
-
6294
- return in_jsx_child ? to_jsx_expression_container(expression, node) : set_loc(expression, node);
6295
- }
6296
-
6297
- /**
6298
- * @param {AST.Identifier} dynamic_id
6299
- * @param {any} node
6300
- * @param {TransformContext} transform_context
6301
- * @returns {ESTreeJSX.JSXElement}
6302
- */
6303
- function create_dynamic_jsx_element(dynamic_id, node, transform_context) {
6304
- const attributes = transform_element_attributes_dispatch(
6305
- node.openingElement?.attributes || [],
6306
- transform_context,
6307
- node,
6308
- );
6309
- const selfClosing = !!node.openingElement?.selfClosing;
6310
- const children = create_element_children(node.children || [], transform_context);
6311
- const name = identifier_to_jsx_name(clone_identifier(dynamic_id));
6312
-
6313
- return b.jsx_element_fresh(
6314
- b.jsx_opening_element(name, attributes, selfClosing),
6315
- selfClosing ? null : b.jsx_closing_element(clone_jsx_name(name)),
6316
- children,
6317
- );
6318
- }
6319
-
6320
6898
  /**
6321
6899
  * @param {any[]} render_nodes
6322
6900
  * @returns {any}