ripple 0.2.208 → 0.2.211

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +2 -1
  3. package/package.json +2 -6
  4. package/shims/rollup-estree-types.d.ts +1 -1
  5. package/src/compiler/index.d.ts +1 -0
  6. package/src/compiler/index.js +7 -1
  7. package/src/compiler/phases/1-parse/index.js +24 -7
  8. package/src/compiler/phases/2-analyze/css-analyze.js +100 -104
  9. package/src/compiler/phases/2-analyze/index.js +215 -2
  10. package/src/compiler/phases/3-transform/client/index.js +388 -50
  11. package/src/compiler/phases/3-transform/segments.js +123 -39
  12. package/src/compiler/phases/3-transform/server/index.js +266 -13
  13. package/src/compiler/types/index.d.ts +15 -3
  14. package/src/compiler/utils.js +1 -15
  15. package/src/constants.js +0 -2
  16. package/src/helpers.d.ts +4 -0
  17. package/src/html-tree-validation.js +211 -0
  18. package/src/jsx-runtime.d.ts +260 -259
  19. package/src/jsx-runtime.js +12 -12
  20. package/src/runtime/array.js +17 -17
  21. package/src/runtime/create-subscriber.js +1 -1
  22. package/src/runtime/index-client.js +1 -5
  23. package/src/runtime/index-server.js +15 -0
  24. package/src/runtime/internal/client/compat.js +3 -3
  25. package/src/runtime/internal/client/composite.js +6 -1
  26. package/src/runtime/internal/client/head.js +50 -4
  27. package/src/runtime/internal/client/html.js +73 -12
  28. package/src/runtime/internal/client/hydration.js +12 -0
  29. package/src/runtime/internal/client/index.js +1 -1
  30. package/src/runtime/internal/client/portal.js +54 -29
  31. package/src/runtime/internal/client/rpc.js +3 -1
  32. package/src/runtime/internal/client/switch.js +5 -0
  33. package/src/runtime/internal/client/template.js +119 -12
  34. package/src/runtime/internal/client/try.js +1 -0
  35. package/src/runtime/internal/server/index.js +113 -1
  36. package/src/runtime/internal/server/rpc.js +4 -4
  37. package/src/runtime/map.js +2 -2
  38. package/src/runtime/object.js +6 -6
  39. package/src/runtime/proxy.js +12 -11
  40. package/src/runtime/reactive-value.js +9 -1
  41. package/src/runtime/set.js +12 -7
  42. package/src/runtime/url-search-params.js +0 -1
  43. package/src/server/index.js +4 -0
  44. package/src/utils/hashing.js +15 -0
  45. package/src/utils/normalize_css_property_name.js +1 -1
  46. package/tests/client/array/array.mutations.test.ripple +8 -8
  47. package/tests/client/basic/basic.errors.test.ripple +28 -0
  48. package/tests/client/basic/basic.events.test.ripple +6 -3
  49. package/tests/client/basic/basic.utilities.test.ripple +1 -1
  50. package/tests/client/compiler/compiler.regex.test.ripple +10 -8
  51. package/tests/client/composite/composite.generics.test.ripple +5 -2
  52. package/tests/client/dynamic-elements.test.ripple +30 -1
  53. package/tests/client/function-overload-import.ripple +6 -7
  54. package/tests/client/html.test.ripple +0 -1
  55. package/tests/client/object.test.ripple +2 -2
  56. package/tests/client/portal.test.ripple +3 -3
  57. package/tests/client/return.test.ripple +2500 -0
  58. package/tests/client/try.test.ripple +69 -0
  59. package/tests/client/typescript-generics.test.ripple +1 -1
  60. package/tests/client/url/url.derived.test.ripple +1 -1
  61. package/tests/client/url/url.parsing.test.ripple +3 -3
  62. package/tests/client/url/url.partial-removal.test.ripple +7 -7
  63. package/tests/client/url/url.reactivity.test.ripple +15 -15
  64. package/tests/client/url/url.serialization.test.ripple +2 -2
  65. package/tests/hydration/basic.test.js +23 -0
  66. package/tests/hydration/build-components.js +10 -4
  67. package/tests/hydration/compiled/client/basic.js +165 -3
  68. package/tests/hydration/compiled/client/composite.js +139 -0
  69. package/tests/hydration/compiled/client/for.js +1140 -23
  70. package/tests/hydration/compiled/client/head.js +234 -0
  71. package/tests/hydration/compiled/client/html.js +135 -0
  72. package/tests/hydration/compiled/client/portal.js +172 -0
  73. package/tests/hydration/compiled/client/reactivity.js +3 -1
  74. package/tests/hydration/compiled/client/return.js +1976 -0
  75. package/tests/hydration/compiled/client/switch.js +162 -0
  76. package/tests/hydration/compiled/server/basic.js +249 -0
  77. package/tests/hydration/compiled/server/composite.js +176 -0
  78. package/tests/hydration/compiled/server/events.js +1 -1
  79. package/tests/hydration/compiled/server/for.js +891 -1
  80. package/tests/hydration/compiled/server/head.js +291 -0
  81. package/tests/hydration/compiled/server/html.js +133 -0
  82. package/tests/hydration/compiled/server/if.js +1 -1
  83. package/tests/hydration/compiled/server/portal.js +250 -0
  84. package/tests/hydration/compiled/server/reactivity.js +1 -1
  85. package/tests/hydration/compiled/server/return.js +1969 -0
  86. package/tests/hydration/compiled/server/switch.js +130 -0
  87. package/tests/hydration/components/basic.ripple +55 -0
  88. package/tests/hydration/components/composite.ripple +37 -0
  89. package/tests/hydration/components/for.ripple +403 -0
  90. package/tests/hydration/components/head.ripple +111 -0
  91. package/tests/hydration/components/html.ripple +38 -0
  92. package/tests/hydration/components/portal.ripple +49 -0
  93. package/tests/hydration/components/return.ripple +564 -0
  94. package/tests/hydration/components/switch.ripple +51 -0
  95. package/tests/hydration/composite.test.js +42 -0
  96. package/tests/hydration/for.test.js +363 -0
  97. package/tests/hydration/head.test.js +105 -0
  98. package/tests/hydration/html.test.js +46 -0
  99. package/tests/hydration/portal.test.js +71 -0
  100. package/tests/hydration/return.test.js +544 -0
  101. package/tests/hydration/switch.test.js +42 -0
  102. package/tests/server/basic.attributes.test.ripple +1 -1
  103. package/tests/server/compiler.test.ripple +22 -0
  104. package/tests/server/composite.test.ripple +5 -2
  105. package/tests/server/html-nesting-validation.test.ripple +237 -0
  106. package/tests/server/return.test.ripple +1379 -0
  107. package/tests/setup-hydration.js +6 -1
  108. package/tests/utils/escaping.test.js +3 -1
  109. package/tests/utils/normalize_css_property_name.test.js +0 -1
  110. package/tests/utils/patterns.test.js +6 -2
  111. package/tests/utils/sanitize_template_string.test.js +3 -2
  112. package/types/server.d.ts +16 -0
@@ -55,6 +55,7 @@ import {
55
55
  index_to_key,
56
56
  is_element_dynamic,
57
57
  is_inside_left_side_assignment,
58
+ hash,
58
59
  } from '../../../utils.js';
59
60
  import {
60
61
  CSS_HASH_IDENTIFIER,
@@ -130,9 +131,10 @@ function visit_function(node, context) {
130
131
 
131
132
  /**
132
133
  * @param {AST.Element} node
134
+ * @param {number} index
133
135
  * @param {TransformClientContext} context
134
136
  */
135
- function visit_head_element(node, context) {
137
+ function visit_head_element(node, index, context) {
136
138
  const { state, visit } = context;
137
139
 
138
140
  /** @type {TransformClientState['init']} */
@@ -154,10 +156,16 @@ function visit_head_element(node, context) {
154
156
  );
155
157
 
156
158
  if (init.length > 0 || update.length > 0 || final.length > 0) {
159
+ // Generate a hash for this head element based on filename and index
160
+ // Use both filename and index to ensure uniqueness across multiple head blocks
161
+ const hash_source = `${state.filename}:head:${index}:${node.start ?? 0}`;
162
+ const hash_value = hash(hash_source);
163
+
157
164
  context.state.init?.push(
158
165
  b.stmt(
159
166
  b.call(
160
167
  '_$_.head',
168
+ b.literal(hash_value),
161
169
  b.arrow(
162
170
  [b.id('__anchor')],
163
171
  b.block([
@@ -1466,7 +1474,13 @@ const visitors = {
1466
1474
  node.children,
1467
1475
  /** @type {VisitorClientContext} */ ({
1468
1476
  visit,
1469
- state: { ...state, init, update, namespace: child_namespace },
1477
+ state: {
1478
+ ...state,
1479
+ init,
1480
+ update,
1481
+ namespace: child_namespace,
1482
+ skip_children_traversal: true,
1483
+ },
1470
1484
  root: false,
1471
1485
  }),
1472
1486
  );
@@ -2105,6 +2119,44 @@ const visitors = {
2105
2119
  const id = context.state.flush_node?.();
2106
2120
  const statements = [];
2107
2121
 
2122
+ // Optimize lone return: if (cond) { return; } with no alternate
2123
+ if (node.metadata?.lone_return && context.state.return_flags) {
2124
+ const consequent_body =
2125
+ node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
2126
+ const ret = /** @type {AST.ReturnStatement} */ (consequent_body[0]);
2127
+ const info = context.state.return_flags.get(ret);
2128
+
2129
+ if (info) {
2130
+ /** @type {AST.Statement[]} */
2131
+ const callback_body = [];
2132
+
2133
+ if (info.tracked) {
2134
+ callback_body.push(b.stmt(b.call('_$_.set', b.id(info.name), b.false)));
2135
+ callback_body.push(
2136
+ b.if(
2137
+ /** @type {AST.Expression} */ (context.visit(node.test)),
2138
+ b.stmt(b.call('_$_.set', b.id(info.name), b.true)),
2139
+ ),
2140
+ );
2141
+ } else {
2142
+ callback_body.push(b.stmt(b.assignment('=', b.id(info.name), b.false)));
2143
+ callback_body.push(
2144
+ b.if(
2145
+ /** @type {AST.Expression} */ (context.visit(node.test)),
2146
+ b.stmt(b.assignment('=', b.id(info.name), b.true)),
2147
+ ),
2148
+ );
2149
+ }
2150
+
2151
+ statements.push(
2152
+ b.stmt(b.call('_$_.if', id, b.arrow([b.id('__render')], b.block(callback_body)))),
2153
+ );
2154
+
2155
+ context.state.init?.push(b.block(statements));
2156
+ return;
2157
+ }
2158
+ }
2159
+
2108
2160
  const consequent_scope =
2109
2161
  /** @type {ScopeInterface} */ (context.state.scopes.get(node.consequent)) ||
2110
2162
  context.state.scope;
@@ -2137,36 +2189,65 @@ const visitors = {
2137
2189
  statements.push(b.var(b.id(alternate_id), b.arrow([b.id('__anchor')], alternate_block)));
2138
2190
  }
2139
2191
 
2140
- statements.push(
2141
- b.stmt(
2142
- b.call(
2143
- '_$_.if',
2144
- id,
2145
- b.arrow(
2146
- [b.id('__render')],
2147
- b.block([
2148
- b.if(
2149
- /** @type {AST.Expression} */ (context.visit(node.test)),
2150
- b.stmt(b.call(b.id('__render'), b.id(consequent_id))),
2151
- alternate_id
2152
- ? b.stmt(
2153
- b.call(
2154
- b.id('__render'),
2155
- b.id(alternate_id),
2156
- node.alternate ? b.literal(false) : undefined,
2157
- ),
2158
- )
2159
- : undefined,
2192
+ // Collect return flag resets for IfStatements that contain returns
2193
+ /** @type {AST.Statement[]} */
2194
+ const callback_body = [];
2195
+
2196
+ if (node.metadata?.has_return && context.state.return_flags) {
2197
+ const returns = node.metadata.returns || [];
2198
+ for (const ret of returns) {
2199
+ const info = context.state.return_flags.get(ret);
2200
+ if (info) {
2201
+ if (info.tracked) {
2202
+ callback_body.push(b.stmt(b.call('_$_.set', b.id(info.name), b.false)));
2203
+ } else {
2204
+ callback_body.push(b.stmt(b.assignment('=', b.id(info.name), b.false)));
2205
+ }
2206
+ }
2207
+ }
2208
+ }
2209
+
2210
+ callback_body.push(
2211
+ b.if(
2212
+ /** @type {AST.Expression} */ (context.visit(node.test)),
2213
+ b.stmt(b.call(b.id('__render'), b.id(consequent_id))),
2214
+ alternate_id
2215
+ ? b.stmt(
2216
+ b.call(
2217
+ b.id('__render'),
2218
+ b.id(alternate_id),
2219
+ node.alternate ? b.literal(false) : undefined,
2160
2220
  ),
2161
- ]),
2162
- ),
2163
- ),
2221
+ )
2222
+ : undefined,
2164
2223
  ),
2165
2224
  );
2166
2225
 
2226
+ statements.push(
2227
+ b.stmt(b.call('_$_.if', id, b.arrow([b.id('__render')], b.block(callback_body)))),
2228
+ );
2229
+
2167
2230
  context.state.init?.push(b.block(statements));
2168
2231
  },
2169
2232
 
2233
+ ReturnStatement(node, context) {
2234
+ if (!is_inside_component(context)) {
2235
+ return context.next();
2236
+ }
2237
+ if (context.state.to_ts) {
2238
+ return context.next();
2239
+ }
2240
+ const info = context.state.return_flags?.get(node);
2241
+ if (info) {
2242
+ if (info.tracked) {
2243
+ return b.stmt(b.call('_$_.set', b.id(info.name), b.true));
2244
+ } else {
2245
+ return b.stmt(b.assignment('=', b.id(info.name), b.true));
2246
+ }
2247
+ }
2248
+ return context.next();
2249
+ },
2250
+
2170
2251
  TSAsExpression(node, context) {
2171
2252
  if (!context.state.to_ts) {
2172
2253
  return context.visit(/** @type {AST.Expression} */ (node.expression));
@@ -2525,26 +2606,30 @@ function transform_ts_child(node, context) {
2525
2606
 
2526
2607
  const jsx_attr = b.jsx_attribute(
2527
2608
  jsx_name,
2528
- b.jsx_expression_container(
2529
- /** @type {AST.Expression} */ (value),
2530
- attr_value === null
2531
- ? /** @type {AST.NodeWithLocation} */ (value)
2532
- : // account location for opening and closing braces around the expression
2533
- /** @type {AST.NodeWithLocation} */ ({
2534
- start: attr_value.start - 1,
2535
- end: attr_value.end + 1,
2536
- loc: {
2537
- start: {
2538
- line: attr_value.loc.start.line,
2539
- column: attr_value.loc.start.column - 1,
2540
- },
2541
- end: {
2542
- line: attr_value.loc.end.line,
2543
- column: attr_value.loc.end.column + 1,
2544
- },
2545
- },
2546
- }),
2547
- ),
2609
+ // match the source code usage of expressions for literals
2610
+ // for proper source mapping to avoid turning strings into expressions
2611
+ attr_value?.type === 'Literal' && !attr_value.was_expression
2612
+ ? /** @type {AST.Literal} */ (value)
2613
+ : b.jsx_expression_container(
2614
+ /** @type {AST.Expression} */ (value),
2615
+ attr_value === null
2616
+ ? /** @type {AST.NodeWithLocation} */ (value)
2617
+ : // account location for opening and closing braces around the expression
2618
+ /** @type {AST.NodeWithLocation} */ ({
2619
+ start: attr_value.start - 1,
2620
+ end: attr_value.end + 1,
2621
+ loc: {
2622
+ start: {
2623
+ line: attr_value.loc.start.line,
2624
+ column: attr_value.loc.start.column - 1,
2625
+ },
2626
+ end: {
2627
+ line: attr_value.loc.end.line,
2628
+ column: attr_value.loc.end.column + 1,
2629
+ },
2630
+ },
2631
+ }),
2632
+ ),
2548
2633
  attr.shorthand ?? false,
2549
2634
  /** @type {AST.NodeWithLocation} */ (attr),
2550
2635
  );
@@ -2589,6 +2674,7 @@ function transform_ts_child(node, context) {
2589
2674
  /** @type {AST.Identifier} */ (node.id).name === 'head'
2590
2675
  ? true
2591
2676
  : state.inside_head,
2677
+ skip_children_traversal: is_dom_element,
2592
2678
  },
2593
2679
  }),
2594
2680
  ),
@@ -2848,6 +2934,71 @@ function transform_ts_child(node, context) {
2848
2934
  }
2849
2935
  }
2850
2936
 
2937
+ /**
2938
+ * Checks if a node is template or control-flow content
2939
+ * @param {AST.Node} node
2940
+ * @returns {boolean}
2941
+ */
2942
+ function is_template_or_control_flow(node) {
2943
+ return (
2944
+ node.type === 'Element' ||
2945
+ node.type === 'Text' ||
2946
+ node.type === 'Html' ||
2947
+ node.type === 'TsxCompat' ||
2948
+ node.type === 'IfStatement' ||
2949
+ node.type === 'ForOfStatement' ||
2950
+ node.type === 'TryStatement' ||
2951
+ node.type === 'SwitchStatement'
2952
+ );
2953
+ }
2954
+
2955
+ /**
2956
+ * Builds a negated AND condition from return flag info: !flag1 && !flag2 && ...
2957
+ * Uses _$_.get() for tracked flags and direct reference for plain booleans.
2958
+ * @param {{ name: string, tracked: boolean }[]} flags
2959
+ * @returns {AST.Expression}
2960
+ */
2961
+ function build_return_guard(flags) {
2962
+ /** @param {{ name: string, tracked: boolean }} flag */
2963
+ const negate_flag = (flag) =>
2964
+ flag.tracked ? b.unary('!', b.call('_$_.get', b.id(flag.name))) : b.unary('!', b.id(flag.name));
2965
+
2966
+ /** @type {AST.Expression} */
2967
+ let condition = negate_flag(flags[0]);
2968
+ for (let i = 1; i < flags.length; i++) {
2969
+ condition = b.logical('&&', condition, negate_flag(flags[i]));
2970
+ }
2971
+ return condition;
2972
+ }
2973
+
2974
+ /**
2975
+ * Collects all unique return statements from direct children
2976
+ * @param {AST.Node[]} children
2977
+ * @returns {AST.ReturnStatement[]}
2978
+ */
2979
+ function collect_returns_from_children(children) {
2980
+ /** @type {AST.ReturnStatement[]} */
2981
+ const returns = [];
2982
+ const seen = new Set();
2983
+ for (const node of children) {
2984
+ if (node.type === 'ReturnStatement') {
2985
+ if (!seen.has(node)) {
2986
+ seen.add(node);
2987
+ returns.push(node);
2988
+ }
2989
+ }
2990
+ if (node.metadata?.returns) {
2991
+ for (const ret of node.metadata.returns) {
2992
+ if (!seen.has(ret)) {
2993
+ seen.add(ret);
2994
+ returns.push(ret);
2995
+ }
2996
+ }
2997
+ }
2998
+ }
2999
+ return returns;
3000
+ }
3001
+
2851
3002
  /**
2852
3003
  *
2853
3004
  * @param {AST.Node[]} children
@@ -2866,6 +3017,36 @@ function transform_children(children, context) {
2866
3017
  )
2867
3018
  );
2868
3019
 
3020
+ const all_returns = collect_returns_from_children(normalized);
3021
+ /** @type {Map<AST.ReturnStatement, { name: string, tracked: boolean }>} */
3022
+ const return_flags = new Map([...(state.return_flags || [])]);
3023
+ /** @type {AST.ReturnStatement[]} */
3024
+ const new_returns = [];
3025
+ for (const ret of all_returns) {
3026
+ if (!return_flags.has(ret)) {
3027
+ return_flags.set(ret, {
3028
+ name: state.scope.generate('__r'),
3029
+ tracked: ret.metadata?.is_reactive ?? false,
3030
+ });
3031
+ new_returns.push(ret);
3032
+ }
3033
+ }
3034
+
3035
+ if (!state.to_ts) {
3036
+ for (const ret of new_returns) {
3037
+ const info = /** @type {{ name: string, tracked: boolean }} */ (return_flags.get(ret));
3038
+ if (info.tracked) {
3039
+ state.init?.unshift(b.var(b.id(info.name), b.call('_$_.tracked', b.false)));
3040
+ } else {
3041
+ state.init?.unshift(b.var(b.id(info.name), b.false));
3042
+ }
3043
+ }
3044
+ }
3045
+
3046
+ /** @type {{ name: string, tracked: boolean }[]} */
3047
+ const accumulated_return_flags = [];
3048
+ const has_returns = all_returns.length > 0;
3049
+
2869
3050
  const is_fragment =
2870
3051
  normalized.some(
2871
3052
  (node) =>
@@ -2883,6 +3064,7 @@ function transform_children(children, context) {
2883
3064
  ).length > 1;
2884
3065
  /** @type {AST.Identifier | null} */
2885
3066
  let initial = null;
3067
+ /** @type {(() => AST.Identifier) | null} */
2886
3068
  let prev = null;
2887
3069
  let template_id = null;
2888
3070
 
@@ -2913,7 +3095,97 @@ function transform_children(children, context) {
2913
3095
  state.init?.push(b.var(id, b.call(template_id)));
2914
3096
  };
2915
3097
 
2916
- for (const node of normalized) {
3098
+ /** @type {AST.Node[]} */
3099
+ let pending_group = [];
3100
+ /** @type {{ name: string, tracked: boolean }[]} */
3101
+ let pending_guard_flags = [];
3102
+
3103
+ let skipped = 0;
3104
+
3105
+ const flush_pending_group = () => {
3106
+ if (pending_group.length === 0) return;
3107
+
3108
+ const guard_flags = pending_guard_flags;
3109
+ const group_nodes = pending_group;
3110
+ pending_group = [];
3111
+ pending_guard_flags = [];
3112
+
3113
+ // Push <!> placeholder for the _$_.if anchor
3114
+ state.template?.push('<!>');
3115
+
3116
+ if (initial === null && root) {
3117
+ create_initial(group_nodes[0]);
3118
+ }
3119
+
3120
+ const current_prev = prev;
3121
+ /** @type {AST.Identifier | null} */
3122
+ let cached_anchor = null;
3123
+ const group_flush_node = () => {
3124
+ if (cached_anchor) return cached_anchor;
3125
+ const id = b.id(state.scope.generate('node'));
3126
+ if (current_prev !== null) {
3127
+ state.init?.push(b.var(id, b.call('_$_.sibling', current_prev())));
3128
+ } else if (initial !== null) {
3129
+ if (is_fragment) {
3130
+ state.init?.push(b.var(id, b.call('_$_.first_child_frag', initial)));
3131
+ } else {
3132
+ cached_anchor = initial;
3133
+ return initial;
3134
+ }
3135
+ } else if (state.flush_node !== null) {
3136
+ state.init?.push(b.var(id, b.call('_$_.child', state.flush_node?.())));
3137
+ }
3138
+ cached_anchor = id;
3139
+ return id;
3140
+ };
3141
+
3142
+ prev = group_flush_node;
3143
+
3144
+ const anchor = group_flush_node();
3145
+
3146
+ // Process group nodes through transform_body
3147
+ const body = transform_body(group_nodes, {
3148
+ ...context,
3149
+ state: { ...context.state, flush_node: null, return_flags },
3150
+ });
3151
+
3152
+ const content_id = state.scope.generate('content');
3153
+ const guard_condition = build_return_guard(guard_flags);
3154
+
3155
+ /** @type {AST.Statement[]} */
3156
+ const callback_body = [
3157
+ b.if(guard_condition, b.stmt(b.call(b.id('__render'), b.id(content_id)))),
3158
+ ];
3159
+
3160
+ state.init?.push(b.var(b.id(content_id), b.arrow([b.id('__anchor')], b.block(body))));
3161
+ state.init?.push(
3162
+ b.stmt(b.call('_$_.if', anchor, b.arrow([b.id('__render')], b.block(callback_body)))),
3163
+ );
3164
+ };
3165
+
3166
+ for (let node_idx = 0; node_idx < normalized.length; node_idx++) {
3167
+ const node = normalized[node_idx];
3168
+
3169
+ if (accumulated_return_flags.length > 0 && is_template_or_control_flow(node) && !state.to_ts) {
3170
+ if (pending_group.length === 0) {
3171
+ pending_guard_flags = [...accumulated_return_flags];
3172
+ }
3173
+ pending_group.push(node);
3174
+
3175
+ if (node.metadata?.has_return && node.metadata.returns) {
3176
+ flush_pending_group();
3177
+ for (const ret of node.metadata.returns) {
3178
+ const info = return_flags.get(ret);
3179
+ if (info && !accumulated_return_flags.some((f) => f.name === info.name)) {
3180
+ accumulated_return_flags.push(info);
3181
+ }
3182
+ }
3183
+ }
3184
+ continue;
3185
+ }
3186
+
3187
+ flush_pending_group();
3188
+
2917
3189
  if (
2918
3190
  node.type === 'VariableDeclaration' ||
2919
3191
  node.type === 'ExpressionStatement' ||
@@ -2923,16 +3195,25 @@ function transform_children(children, context) {
2923
3195
  node.type === 'ClassDeclaration' ||
2924
3196
  node.type === 'TSTypeAliasDeclaration' ||
2925
3197
  node.type === 'TSInterfaceDeclaration' ||
3198
+ node.type === 'ReturnStatement' ||
2926
3199
  node.type === 'Component'
2927
3200
  ) {
2928
3201
  const metadata = { await: false };
2929
- state.init?.push(/** @type {AST.Statement} */ (visit(node, { ...state, metadata })));
3202
+ state.init?.push(
3203
+ /** @type {AST.Statement} */ (visit(node, { ...state, return_flags, metadata })),
3204
+ );
2930
3205
  if (metadata.await) {
2931
3206
  state.init?.push(b.if(b.call('_$_.aborted'), b.return(null)));
2932
3207
  if (state.metadata?.await === false) {
2933
3208
  state.metadata.await = true;
2934
3209
  }
2935
3210
  }
3211
+ if (!state.to_ts && node.type === 'ReturnStatement') {
3212
+ const info = return_flags.get(node);
3213
+ if (info && !accumulated_return_flags.some((f) => f.name === info.name)) {
3214
+ accumulated_return_flags.push(info);
3215
+ }
3216
+ }
2936
3217
  } else if (state.to_ts) {
2937
3218
  transform_ts_child(node, /** @type {VisitorClientContext} */ ({ visit, state }));
2938
3219
  } else {
@@ -2993,19 +3274,30 @@ function transform_children(children, context) {
2993
3274
  const is_controlled = normalized.length === 1 && !root;
2994
3275
 
2995
3276
  if (node.type === 'Element') {
3277
+ if (is_element_dom_element(node)) {
3278
+ skipped++;
3279
+ } else {
3280
+ skipped = 0;
3281
+ }
3282
+
2996
3283
  visit(node, {
2997
3284
  ...state,
3285
+ return_flags,
2998
3286
  flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
2999
3287
  namespace: state.namespace,
3000
3288
  });
3001
3289
  } else if (node.type === 'TsxCompat') {
3290
+ skipped = 0;
3291
+
3002
3292
  visit(node, {
3003
3293
  ...state,
3294
+ return_flags,
3004
3295
  flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
3005
3296
  namespace: state.namespace,
3006
3297
  });
3007
3298
  } else if (node.type === 'Html') {
3008
3299
  context.state.template?.push('<!>');
3300
+ skipped = 0;
3009
3301
 
3010
3302
  const id = flush_node(false);
3011
3303
  state.update?.push({
@@ -3022,6 +3314,7 @@ function transform_children(children, context) {
3022
3314
  });
3023
3315
  } else if (node.type === 'Text') {
3024
3316
  if (metadata?.tracking) {
3317
+ skipped = 0;
3025
3318
  state.template?.push(' ');
3026
3319
  const id = flush_node(true);
3027
3320
  state.update?.push({
@@ -3034,6 +3327,7 @@ function transform_children(children, context) {
3034
3327
  /** @type {NonNullable<TransformClientState['update']>} */ (state.update).async = true;
3035
3328
  }
3036
3329
  } else if (normalized.length === 1) {
3330
+ skipped++;
3037
3331
  const expr = /** @type {AST.Expression} */ (expression);
3038
3332
  if (expr.type === 'Literal') {
3039
3333
  if (
@@ -3061,6 +3355,7 @@ function transform_children(children, context) {
3061
3355
  );
3062
3356
  }
3063
3357
  } else {
3358
+ skipped++;
3064
3359
  // Handle Text nodes in fragments
3065
3360
  const expr = /** @type {AST.Expression} */ (expression);
3066
3361
  if (expr.type === 'Literal') {
@@ -3081,6 +3376,7 @@ function transform_children(children, context) {
3081
3376
  }
3082
3377
  }
3083
3378
  } else if (node.type === 'ForOfStatement') {
3379
+ skipped = 0;
3084
3380
  node.is_controlled = is_controlled;
3085
3381
  visit(node, {
3086
3382
  ...state,
@@ -3088,13 +3384,16 @@ function transform_children(children, context) {
3088
3384
  namespace: state.namespace,
3089
3385
  });
3090
3386
  } else if (node.type === 'IfStatement') {
3387
+ skipped = 0;
3091
3388
  node.is_controlled = is_controlled;
3092
3389
  visit(node, {
3093
3390
  ...state,
3391
+ return_flags,
3094
3392
  flush_node: /** @type {TransformClientState['flush_node']} */ (flush_node),
3095
3393
  namespace: state.namespace,
3096
3394
  });
3097
3395
  } else if (node.type === 'TryStatement') {
3396
+ skipped = 0;
3098
3397
  node.is_controlled = is_controlled;
3099
3398
  visit(node, {
3100
3399
  ...state,
@@ -3102,6 +3401,7 @@ function transform_children(children, context) {
3102
3401
  namespace: state.namespace,
3103
3402
  });
3104
3403
  } else if (node.type === 'SwitchStatement') {
3404
+ skipped = 0;
3105
3405
  node.is_controlled = is_controlled;
3106
3406
  visit(node, {
3107
3407
  ...state,
@@ -3114,13 +3414,25 @@ function transform_children(children, context) {
3114
3414
  debugger;
3115
3415
  }
3116
3416
  }
3417
+
3418
+ if (has_returns && node.metadata?.has_return && node.metadata.returns) {
3419
+ for (const ret of node.metadata.returns) {
3420
+ const info = return_flags.get(ret);
3421
+ if (info && !accumulated_return_flags.some((f) => f.name === info.name)) {
3422
+ accumulated_return_flags.push(info);
3423
+ }
3424
+ }
3425
+ }
3117
3426
  }
3118
3427
 
3119
- for (const head_element of head_elements) {
3428
+ flush_pending_group();
3429
+
3430
+ for (let i = 0; i < head_elements.length; i++) {
3431
+ const head_element = head_elements[i];
3120
3432
  if (state.to_ts) {
3121
3433
  transform_ts_child(head_element, /** @type {VisitorClientContext} */ ({ visit, state }));
3122
3434
  } else {
3123
- visit_head_element(head_element, context);
3435
+ visit_head_element(head_element, i, context);
3124
3436
  }
3125
3437
  }
3126
3438
 
@@ -3137,6 +3449,13 @@ function transform_children(children, context) {
3137
3449
  }
3138
3450
  }
3139
3451
 
3452
+ let emitted_next = false;
3453
+ if (is_fragment && skipped > 1 && !state.skip_children_traversal) {
3454
+ skipped--;
3455
+ state.init?.push(b.stmt(b.call('_$_.next', skipped !== 1 && b.literal(skipped))));
3456
+ emitted_next = true;
3457
+ }
3458
+
3140
3459
  const template_namespace = state.namespace || 'html';
3141
3460
 
3142
3461
  if (root && initial !== null && template_id !== null) {
@@ -3146,7 +3465,9 @@ function transform_children(children, context) {
3146
3465
  } else if (template_namespace === 'mathml') {
3147
3466
  flags |= TEMPLATE_MATHML_NAMESPACE;
3148
3467
  }
3149
- state.final?.push(b.stmt(b.call('_$_.append', b.id('__anchor'), initial)));
3468
+ state.final?.push(
3469
+ b.stmt(b.call('_$_.append', b.id('__anchor'), initial, emitted_next && b.true)),
3470
+ );
3150
3471
  state.hoisted.push(
3151
3472
  b.var(
3152
3473
  template_id,
@@ -3466,6 +3787,18 @@ function create_tsx_with_typescript_support(comments) {
3466
3787
  // cover the end
3467
3788
  context.location(loc.end.line, loc.end.column);
3468
3789
  },
3790
+ ReturnStatement(node, context) {
3791
+ if (!node.loc) {
3792
+ base_tsx.ReturnStatement?.(node, context);
3793
+ return;
3794
+ }
3795
+
3796
+ const { start, end } = node.loc;
3797
+
3798
+ context.location(start.line, start.column);
3799
+ base_tsx.ReturnStatement?.(node, context);
3800
+ context.location(end.line, end.column);
3801
+ },
3469
3802
  AwaitExpression(node, context) {
3470
3803
  const loc = /** @type {AST.SourceLocation} */ (node.loc);
3471
3804
  // the start needs to be covered as we don't cover it in visitors
@@ -3597,6 +3930,10 @@ function create_tsx_with_typescript_support(comments) {
3597
3930
  }
3598
3931
  context.write('>');
3599
3932
  }
3933
+
3934
+ if (node.loc) {
3935
+ context.location(node.loc.end.line, node.loc.end.column);
3936
+ }
3600
3937
  },
3601
3938
  JSXClosingElement(node, context) {
3602
3939
  const loc = /** @type {AST.SourceLocation} */ (node.loc);
@@ -4115,6 +4452,7 @@ export function transform_client(filename, source, analysis, to_ts, minify_css)
4115
4452
  namespace: 'html',
4116
4453
  metadata: {},
4117
4454
  errors: analysis.errors,
4455
+ skip_children_traversal: false,
4118
4456
  };
4119
4457
 
4120
4458
  // Add ripple internal import once for the entire module