ripple 0.3.128 → 0.4.0

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 (192) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/package.json +10 -4
  3. package/src/runtime/element.js +23 -6
  4. package/src/runtime/index-client.js +81 -41
  5. package/src/runtime/index-server.js +1 -0
  6. package/src/runtime/internal/client/blocks.js +173 -91
  7. package/src/runtime/internal/client/component.js +4 -30
  8. package/src/runtime/internal/client/composite.js +12 -13
  9. package/src/runtime/internal/client/constants.js +9 -0
  10. package/src/runtime/internal/client/context.js +12 -19
  11. package/src/runtime/internal/client/css.js +7 -9
  12. package/src/runtime/internal/client/events.js +178 -90
  13. package/src/runtime/internal/client/expression.js +45 -25
  14. package/src/runtime/internal/client/for.js +409 -129
  15. package/src/runtime/internal/client/head.js +10 -5
  16. package/src/runtime/internal/client/html.js +1 -0
  17. package/src/runtime/internal/client/hydration.js +152 -10
  18. package/src/runtime/internal/client/if.js +239 -50
  19. package/src/runtime/internal/client/index.js +4 -12
  20. package/src/runtime/internal/client/operations.js +40 -61
  21. package/src/runtime/internal/client/portal.js +163 -85
  22. package/src/runtime/internal/client/render.js +39 -39
  23. package/src/runtime/internal/client/rpc.js +3 -3
  24. package/src/runtime/internal/client/runtime.js +592 -433
  25. package/src/runtime/internal/client/selector.js +5 -2
  26. package/src/runtime/internal/client/template.js +133 -158
  27. package/src/runtime/internal/client/transport.js +38 -0
  28. package/src/runtime/internal/client/try.js +465 -426
  29. package/src/runtime/internal/client/types.d.ts +56 -23
  30. package/src/runtime/internal/client/utils.js +1 -3
  31. package/src/runtime/internal/server/dynamic.js +20 -21
  32. package/src/runtime/internal/server/index.js +325 -275
  33. package/src/runtime/internal/server/rpc.js +3 -2
  34. package/src/runtime/internal/server/transport.js +16 -0
  35. package/src/runtime/transport.js +21 -0
  36. package/tests/client/__snapshots__/for.test.tsrx.snap +0 -5
  37. package/tests/client/array/array.copy-within.test.tsrx +12 -12
  38. package/tests/client/array/array.derived.test.tsrx +46 -46
  39. package/tests/client/array/array.iteration.test.tsrx +10 -10
  40. package/tests/client/array/array.mutations.test.tsrx +20 -20
  41. package/tests/client/array/array.static.test.tsrx +11 -11
  42. package/tests/client/array/array.to-methods.test.tsrx +6 -6
  43. package/tests/client/async-suspend.test.tsrx +74 -75
  44. package/tests/client/basic/basic.attributes.test.tsrx +86 -83
  45. package/tests/client/basic/basic.collections.test.tsrx +12 -12
  46. package/tests/client/basic/basic.components.test.tsrx +99 -25
  47. package/tests/client/basic/basic.errors.test.tsrx +18 -18
  48. package/tests/client/basic/basic.events.test.tsrx +42 -42
  49. package/tests/client/basic/basic.get-set.test.tsrx +8 -8
  50. package/tests/client/basic/basic.reactivity.test.tsrx +73 -38
  51. package/tests/client/basic/basic.rendering.test.tsrx +25 -25
  52. package/tests/client/basic/basic.utilities.test.tsrx +3 -3
  53. package/tests/client/boundaries.test.tsrx +8 -8
  54. package/tests/client/boxed-locals.test.tsrx +452 -0
  55. package/tests/client/compiler/__snapshots__/compiler.assignments.test.tsrx.snap +6 -6
  56. package/tests/client/compiler/compiler.assignments.test.tsrx +9 -73
  57. package/tests/client/compiler/compiler.attributes.test.tsrx +23 -23
  58. package/tests/client/compiler/compiler.basic.test.tsrx +15 -60
  59. package/tests/client/compiler/compiler.tracked-access.test.tsrx +18 -43
  60. package/tests/client/compiler/compiler.try-in-function.test.tsrx +2 -2
  61. package/tests/client/composite/__snapshots__/composite.render.test.tsrx.snap +0 -1
  62. package/tests/client/composite/composite.dynamic-components.test.tsrx +77 -6
  63. package/tests/client/composite/composite.props.test.tsrx +149 -12
  64. package/tests/client/composite/composite.reactivity.test.tsrx +51 -52
  65. package/tests/client/composite/composite.render.test.tsrx +26 -9
  66. package/tests/client/css/scoped-styles.test.tsrx +8 -8
  67. package/tests/client/date.test.tsrx +42 -42
  68. package/tests/client/derived-release.test.tsrx +370 -0
  69. package/tests/client/dynamic-elements.test.tsrx +45 -45
  70. package/tests/client/events.test.tsrx +97 -71
  71. package/tests/client/for-selector.test.tsrx +14 -14
  72. package/tests/client/for-single-root-items.test.tsrx +118 -3
  73. package/tests/client/for.test.tsrx +332 -31
  74. package/tests/client/head.test.tsrx +19 -19
  75. package/tests/client/html.test.tsrx +11 -9
  76. package/tests/client/if-block.test.tsrx +80 -0
  77. package/tests/client/map.test.tsrx +16 -16
  78. package/tests/client/media-query.test.tsrx +7 -7
  79. package/tests/client/portal.test.tsrx +224 -26
  80. package/tests/client/primitive-text.test.tsrx +50 -0
  81. package/tests/client/prop-kind-switch.test.tsrx +141 -0
  82. package/tests/client/ref.test.tsrx +17 -17
  83. package/tests/client/return.test.tsrx +26 -3
  84. package/tests/client/root-boundary.test.tsrx +42 -0
  85. package/tests/client/scoped-flush.test.tsrx +8 -8
  86. package/tests/client/set.test.tsrx +6 -6
  87. package/tests/client/svg.test.tsrx +11 -11
  88. package/tests/client/switch.test.tsrx +44 -44
  89. package/tests/client/track-async-hydration.test.tsrx +4 -4
  90. package/tests/client/try.test.tsrx +151 -83
  91. package/tests/client/tsx.test.tsrx +43 -40
  92. package/tests/client/url/url.derived.test.tsrx +6 -6
  93. package/tests/client/url-search-params/url-search-params.derived.test.tsrx +8 -8
  94. package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +10 -10
  95. package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +10 -10
  96. package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +18 -18
  97. package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +2 -2
  98. package/tests/hydration/basic.test.js +74 -1
  99. package/tests/hydration/build-components.js +22 -1
  100. package/tests/hydration/compiled/client/basic.js +1142 -713
  101. package/tests/hydration/compiled/client/composite.js +151 -100
  102. package/tests/hydration/compiled/client/events.js +184 -171
  103. package/tests/hydration/compiled/client/for.js +1472 -1132
  104. package/tests/hydration/compiled/client/fragment-cursor.js +2341 -0
  105. package/tests/hydration/compiled/client/head.js +254 -201
  106. package/tests/hydration/compiled/client/hmr.js +54 -41
  107. package/tests/hydration/compiled/client/html-in-template.js +54 -39
  108. package/tests/hydration/compiled/client/html.js +1633 -1263
  109. package/tests/hydration/compiled/client/if-children.js +420 -270
  110. package/tests/hydration/compiled/client/if-fragment-controlflow.js +363 -317
  111. package/tests/hydration/compiled/client/if.js +229 -219
  112. package/tests/hydration/compiled/client/mixed-control-flow.js +420 -404
  113. package/tests/hydration/compiled/client/nested-control-flow.js +1326 -1277
  114. package/tests/hydration/compiled/client/portal.js +87 -95
  115. package/tests/hydration/compiled/client/reactivity.js +135 -101
  116. package/tests/hydration/compiled/client/return.js +28 -19
  117. package/tests/hydration/compiled/client/streaming.js +390 -306
  118. package/tests/hydration/compiled/client/switch.js +258 -266
  119. package/tests/hydration/compiled/client/track-async-serialization.js +448 -280
  120. package/tests/hydration/compiled/client/try.js +173 -129
  121. package/tests/hydration/compiled/client/typed-text.js +71 -0
  122. package/tests/hydration/compiled/fixtures/money.js +10 -0
  123. package/tests/hydration/compiled/server/basic.js +123 -6
  124. package/tests/hydration/compiled/server/composite.js +16 -30
  125. package/tests/hydration/compiled/server/events.js +66 -18
  126. package/tests/hydration/compiled/server/for.js +117 -41
  127. package/tests/hydration/compiled/server/fragment-cursor.js +1687 -0
  128. package/tests/hydration/compiled/server/head.js +20 -20
  129. package/tests/hydration/compiled/server/hmr.js +2 -2
  130. package/tests/hydration/compiled/server/html.js +70 -238
  131. package/tests/hydration/compiled/server/if-children.js +107 -15
  132. package/tests/hydration/compiled/server/if.js +11 -15
  133. package/tests/hydration/compiled/server/mixed-control-flow.js +8 -8
  134. package/tests/hydration/compiled/server/portal.js +14 -22
  135. package/tests/hydration/compiled/server/reactivity.js +14 -14
  136. package/tests/hydration/compiled/server/streaming.js +19 -19
  137. package/tests/hydration/compiled/server/switch.js +8 -8
  138. package/tests/hydration/compiled/server/track-async-serialization.js +150 -23
  139. package/tests/hydration/compiled/server/try.js +8 -8
  140. package/tests/hydration/compiled/server/typed-text.js +17 -0
  141. package/tests/hydration/components/basic.tsrx +44 -8
  142. package/tests/hydration/components/composite.tsrx +2 -2
  143. package/tests/hydration/components/events.tsrx +25 -25
  144. package/tests/hydration/components/for.tsrx +115 -67
  145. package/tests/hydration/components/fragment-cursor.tsrx +650 -0
  146. package/tests/hydration/components/head.tsrx +16 -16
  147. package/tests/hydration/components/hmr.tsrx +2 -2
  148. package/tests/hydration/components/html.tsrx +7 -7
  149. package/tests/hydration/components/if-children.tsrx +72 -25
  150. package/tests/hydration/components/if.tsrx +18 -18
  151. package/tests/hydration/components/mixed-control-flow.tsrx +11 -11
  152. package/tests/hydration/components/portal.tsrx +3 -3
  153. package/tests/hydration/components/reactivity.tsrx +16 -16
  154. package/tests/hydration/components/streaming.tsrx +20 -20
  155. package/tests/hydration/components/switch.tsrx +20 -20
  156. package/tests/hydration/components/text-types.ts +4 -0
  157. package/tests/hydration/components/track-async-serialization.tsrx +80 -26
  158. package/tests/hydration/components/try.tsrx +8 -8
  159. package/tests/hydration/components/typed-text.tsrx +22 -0
  160. package/tests/hydration/fixtures/money.js +10 -0
  161. package/tests/hydration/for.test.js +36 -0
  162. package/tests/hydration/fragment-cursor.test.js +111 -0
  163. package/tests/hydration/if-children.test.js +24 -0
  164. package/tests/hydration/streaming.test.js +19 -0
  165. package/tests/hydration/track-async-serialization.test.js +87 -2
  166. package/tests/hydration/tsconfig.text-types.json +10 -0
  167. package/tests/hydration/typed-text.test.js +34 -0
  168. package/tests/server/await.test.tsrx +3 -3
  169. package/tests/server/basic.attributes.test.tsrx +42 -42
  170. package/tests/server/basic.components.test.tsrx +64 -8
  171. package/tests/server/basic.test.tsrx +34 -32
  172. package/tests/server/compiler.test.tsrx +1 -70
  173. package/tests/server/composite.props.test.tsrx +6 -6
  174. package/tests/server/dynamic-elements.test.tsrx +31 -8
  175. package/tests/server/for.test.tsrx +94 -0
  176. package/tests/server/head.test.tsrx +11 -11
  177. package/tests/server/scoped-styles.test.tsrx +2 -2
  178. package/tests/server/streaming-ssr.test.tsrx +48 -43
  179. package/tests/server/track-async-serialization.test.tsrx +204 -21
  180. package/tests/server/try.test.tsrx +48 -48
  181. package/tests/types/transport.ts +40 -0
  182. package/tests/utils/vite-plugin-config.test.js +23 -0
  183. package/types/index.d.ts +48 -7
  184. package/types/server.d.ts +3 -0
  185. package/types/transport.d.ts +15 -0
  186. package/src/runtime/internal/client/switch.js +0 -113
  187. package/src/utils/errors.js +0 -13
  188. package/tests/client/lazy-array.test.tsrx +0 -31
  189. package/tests/client/lazy-destructuring.test.tsrx +0 -438
  190. package/tests/client/tracked-index-access.test.tsrx +0 -113
  191. package/tests/server/lazy-destructuring.test.tsrx +0 -595
  192. package/tests/server/tracked-index-access.test.tsrx +0 -76
@@ -53,9 +53,9 @@ import {
53
53
  } from '../client/constants.js';
54
54
  import { DEV } from 'esm-env';
55
55
  import { is_ripple_object } from '../client/utils.js';
56
- import { iterable_array_from, array_slice, is_array } from '@tsrx/core/runtime/language-helpers';
56
+ import { is_array } from '@tsrx/core/runtime/language-helpers';
57
57
  import {
58
- escape,
58
+ escape as escape_html,
59
59
  escape_script,
60
60
  is_boolean_attribute,
61
61
  normalize_css_property_name,
@@ -79,10 +79,6 @@ import {
79
79
  is_tag_valid_with_ancestor,
80
80
  } from '../../../html-tree-validation.js';
81
81
  import { get_async_track_result } from '../../../utils/async.js';
82
- import {
83
- throw_tracked_index_reference_error,
84
- throw_tracked_index_value_error,
85
- } from '../../../utils/errors.js';
86
82
  import { get_track_async_script_id } from '../../../utils/track-async-serialization.js';
87
83
  import * as devalue from 'devalue';
88
84
  import {
@@ -94,12 +90,10 @@ import {
94
90
  } from './blocks.js';
95
91
  import { COMPONENT_BLOCK, ROOT_BLOCK, TRY_BLOCK } from './constants.js';
96
92
 
97
- export { escape };
98
93
  export { register_component_css as register_css } from './css-registry.js';
99
94
  export { simple_hash, strong_hash } from '@tsrx/core/runtime/hash';
100
95
  export { context } from './context.js';
101
96
  export { try_block, component_block, regular_block } from './blocks.js';
102
- export { array_slice };
103
97
  export { is_tsrx_element, tsrx_element, normalize_children };
104
98
  export { create_ref_prop };
105
99
 
@@ -181,6 +175,11 @@ export function render_expression(value) {
181
175
  * @returns {void}
182
176
  */
183
177
  export function render_component(fn, props) {
178
+ // An optional component (an undefined prop, an import that resolved to
179
+ // nothing) renders nothing; any other non-function is a programming error.
180
+ if (fn == null) {
181
+ return;
182
+ }
184
183
  if (typeof fn !== 'function' || is_tsrx_element(fn)) {
185
184
  throw_invalid_component_type(fn);
186
185
  }
@@ -283,7 +282,8 @@ let inside_async_track = false;
283
282
  /** @type {ElementContext | undefined} */
284
283
  let current_element;
285
284
  /** @type {Set<string>} */
286
- let seen_warnings = new Set();
285
+ /** @type {Set<string> | null} */
286
+ let seen_warnings = null;
287
287
 
288
288
  /**
289
289
  * @returns {void}
@@ -294,7 +294,7 @@ export function reset_state() {
294
294
  active_dependency = null;
295
295
  inside_async_track = false;
296
296
  tracking = false;
297
- seen_warnings = new Set();
297
+ seen_warnings = null;
298
298
  current_element = undefined;
299
299
  }
300
300
 
@@ -461,6 +461,18 @@ function run_derived(computed) {
461
461
  }
462
462
  }
463
463
 
464
+ /**
465
+ * Runs `fn` after the current task's microtasks have drained, so work
466
+ * completing in the same task can be batched into it.
467
+ * @type {(fn: () => void) => void}
468
+ */
469
+ const defer =
470
+ typeof setImmediate === 'function'
471
+ ? setImmediate
472
+ : (fn) => {
473
+ setTimeout(fn, 0);
474
+ };
475
+
464
476
  /**
465
477
  * `<div translate={false}>` should be rendered as `<div translate="no">` and _not_
466
478
  * `<div translate="false">`, which is equivalent to `<div translate="yes">`. There
@@ -481,8 +493,8 @@ export class Output {
481
493
  #head = [];
482
494
  /** @type {NestedArray<string>} */
483
495
  #body = [];
484
- /** @type {Set<string>} */
485
- #css = new Set();
496
+ /** @type {Set<string> | null} root only */
497
+ #css = null;
486
498
  /** @type {null | Output} */
487
499
  #parent = null;
488
500
  /** @type {StreamSink | null} */
@@ -501,8 +513,8 @@ export class Output {
501
513
  #promise_reject = null;
502
514
  #is_root = false;
503
515
  #sync_run = false;
504
- /** @type {Set<RegisteredAsyncOperation>} */
505
- #async_operations = new Set();
516
+ /** @type {Set<RegisteredAsyncOperation> | null} created by the first registration */
517
+ #async_operations = null;
506
518
  /** @type {null | 'head'} */
507
519
  target = null;
508
520
  /** @type {null | FlushUnit} */
@@ -513,13 +525,14 @@ export class Output {
513
525
  #head_redirect = null;
514
526
  // streaming state, only used on the root instance
515
527
  #next_unit_id = 1;
516
- /** @type {FlushUnit[]} */
517
- #units = [];
518
- /** @type {WeakMap<NestedArray<string>, FlushUnit>} */
519
- #unit_slots = new WeakMap();
520
- /** @type {Set<string>} */
521
- #sent_css = new Set();
528
+ /** @type {FlushUnit[] | null} */
529
+ #units = null;
530
+ /** @type {WeakMap<NestedArray<string>, FlushUnit> | null} */
531
+ #unit_slots = null;
532
+ /** @type {Set<string> | null} */
533
+ #sent_css = null;
522
534
  #shell_flushed = false;
535
+ #flush_scheduled = false;
523
536
 
524
537
  get root() {
525
538
  return this.#root;
@@ -534,7 +547,7 @@ export class Output {
534
547
  }
535
548
 
536
549
  get css() {
537
- return this.#css;
550
+ return /** @type {Set<string>} */ (this.#root.#css);
538
551
  }
539
552
 
540
553
  get promise() {
@@ -552,6 +565,10 @@ export class Output {
552
565
  if (!parent) {
553
566
  this.#root = this;
554
567
  this.#is_root = true;
568
+ this.#css = new Set();
569
+ this.#units = [];
570
+ this.#unit_slots = new WeakMap();
571
+ this.#sent_css = new Set();
555
572
  this.#promise = new Promise((resolve, reject) => {
556
573
  this.#promise_resolve = resolve;
557
574
  this.#promise_reject = reject;
@@ -654,7 +671,9 @@ export class Output {
654
671
  }
655
672
  this.#head.length = 0;
656
673
  this.#body.length = 0;
657
- this.#css.clear();
674
+ if (this.#is_root) {
675
+ /** @type {Set<string>} */ (this.#css).clear();
676
+ }
658
677
  }
659
678
 
660
679
  /**
@@ -670,8 +689,9 @@ export class Output {
670
689
  return;
671
690
  }
672
691
  if (root.#shell_flushed) {
673
- if (!root.#sent_css.has(hash)) {
674
- root.#sent_css.add(hash);
692
+ var sent_css = /** @type {Set<string>} */ (root.#sent_css);
693
+ if (!sent_css.has(hash)) {
694
+ sent_css.add(hash);
675
695
  var css_text = get_css_text(new Set([hash]));
676
696
  if (css_text) {
677
697
  root.#streamOutput.push('<style data-ripple-ssr>' + css_text + '</style>');
@@ -680,7 +700,7 @@ export class Output {
680
700
  return;
681
701
  }
682
702
  }
683
- root.#css.add(hash);
703
+ /** @type {Set<string>} */ (root.#css).add(hash);
684
704
  }
685
705
 
686
706
  /**
@@ -730,7 +750,7 @@ export class Output {
730
750
  * @return {void}
731
751
  */
732
752
  registerAsync(operation) {
733
- this.#async_operations.add(operation);
753
+ (this.#async_operations ??= new Set()).add(operation);
734
754
  this.#root._incrementPending();
735
755
  }
736
756
 
@@ -739,19 +759,22 @@ export class Output {
739
759
  * @returns {void}
740
760
  */
741
761
  resolveAsync(operation) {
742
- this.#async_operations.delete(operation);
762
+ var operations = /** @type {Set<RegisteredAsyncOperation>} */ (this.#async_operations);
763
+ operations.delete(operation);
743
764
  this.#root._decrementPending();
744
765
  var unit = this.unit;
745
- if (unit !== null && !unit.flushed && !unit.settled && this.#async_operations.size === 0) {
766
+ if (unit !== null && !unit.flushed && !unit.settled && operations.size === 0) {
746
767
  unit.settled = true;
747
768
  this.#root._maybeFlushUnit(unit);
748
769
  }
749
770
  }
750
771
 
751
772
  cancelAsyncOperations() {
752
- for (const operation of this.#async_operations) {
773
+ var operations = this.#async_operations;
774
+ if (operations === null) return;
775
+ for (const operation of operations) {
753
776
  operation.cancel();
754
- this.#async_operations.delete(operation);
777
+ operations.delete(operation);
755
778
  this.clear();
756
779
  this.#root._decrementPending();
757
780
  }
@@ -761,7 +784,8 @@ export class Output {
761
784
  * @returns {boolean}
762
785
  */
763
786
  hasPendingAsyncOperations() {
764
- return this.#async_operations.size > 0;
787
+ var operations = this.#async_operations;
788
+ return operations !== null && operations.size > 0;
765
789
  }
766
790
 
767
791
  /**
@@ -804,8 +828,8 @@ export class Output {
804
828
  error: null,
805
829
  };
806
830
  this.unit = unit;
807
- root.#unit_slots.set(this.#body, unit);
808
- root.#units.push(unit);
831
+ /** @type {WeakMap<NestedArray<string>, FlushUnit>} */ (root.#unit_slots).set(this.#body, unit);
832
+ /** @type {FlushUnit[]} */ (root.#units).push(unit);
809
833
  return unit;
810
834
  }
811
835
 
@@ -840,7 +864,9 @@ export class Output {
840
864
  out += item;
841
865
  continue;
842
866
  }
843
- var unit = this.#unit_slots.get(item);
867
+ var unit = /** @type {WeakMap<NestedArray<string>, FlushUnit>} */ (this.#unit_slots).get(
868
+ item,
869
+ );
844
870
  if (unit !== undefined && !unit.flushed) {
845
871
  if (unit.settled && !unit.errored) {
846
872
  out += this.#collect_unit(unit, chunk);
@@ -877,9 +903,10 @@ export class Output {
877
903
  #chunk_css(hashes) {
878
904
  /** @type {Set<string>} */
879
905
  var fresh = new Set();
906
+ var sent_css = /** @type {Set<string>} */ (this.#sent_css);
880
907
  for (var hash of hashes) {
881
- if (!this.#sent_css.has(hash)) {
882
- this.#sent_css.add(hash);
908
+ if (!sent_css.has(hash)) {
909
+ sent_css.add(hash);
883
910
  fresh.add(hash);
884
911
  }
885
912
  }
@@ -901,33 +928,42 @@ export class Output {
901
928
  if (this.#streamOutput === null || this.#stream_finished || !this.#shell_flushed) {
902
929
  return;
903
930
  }
904
- if (!unit.settled || unit.flushed || !unit.slot_sent) {
931
+ if (!unit.settled || unit.flushed || !unit.slot_sent || this.#flush_scheduled) {
905
932
  return;
906
933
  }
907
- this.#flush_unit(unit);
908
- this.#sweep_units();
934
+ // Units settling in the same macrotask (data arriving together, a
935
+ // batch of promises resolved by one timer or I/O callback) travel in
936
+ // one chunk: the flush runs once the current task's microtasks have
937
+ // drained. Closing the stream flushes whatever is still queued first.
938
+ this.#flush_scheduled = true;
939
+ defer(() => this.#flush_settled());
940
+ }
941
+
942
+ #flush_settled() {
943
+ this.#flush_scheduled = false;
944
+ if (!this.#stream_finished) {
945
+ this.#sweep_units();
946
+ }
909
947
  }
910
948
 
911
949
  /**
912
950
  * @param {FlushUnit} unit
913
- * @returns {void}
951
+ * @returns {string}
914
952
  */
915
- #flush_unit(unit) {
916
- var sink = /** @type {StreamSink} */ (this.#streamOutput);
953
+ #unit_chunk(unit) {
917
954
  if (unit.errored) {
918
955
  unit.flushed = true;
919
- sink.push(
956
+ return (
920
957
  '<script id="' +
921
- STREAM_ERROR_SCRIPT_PREFIX +
922
- unit.id +
923
- '" type="application/json">' +
924
- escape_script(JSON.stringify({ message: unit.error })) +
925
- '</script>' +
926
- '<script>__RIPPLE_S__(' +
927
- unit.id +
928
- ',1)</script>',
958
+ STREAM_ERROR_SCRIPT_PREFIX +
959
+ unit.id +
960
+ '" type="application/json">' +
961
+ escape_inline_script(JSON.stringify({ message: unit.error })) +
962
+ '</script>' +
963
+ '<script>__RIPPLE_S__(' +
964
+ unit.id +
965
+ ',1)</script>'
929
966
  );
930
- return;
931
967
  }
932
968
  /** @type {StreamChunk} */
933
969
  var chunk = { css: new Set(), scripts: [], head: '' };
@@ -939,28 +975,32 @@ export class Output {
939
975
  out += chunk.scripts.join('');
940
976
  out += '<template ' + STREAM_CHUNK_ATTR + '="' + unit.id + '">' + html + '</template>';
941
977
  out += '<script>__RIPPLE_S__(' + unit.id + ')</script>';
942
- sink.push(out);
978
+ return out;
943
979
  }
944
980
 
945
981
  /**
946
- * Flushes every unit whose slot has been sent and whose async work has
947
- * settled. Flushing a parent chunk marks nested unsettled slots as sent,
948
- * which can make further units eligible — loop until a fixpoint.
982
+ * Streams every unit whose slot has been sent and whose async work has
983
+ * settled, as one chunk. Flushing a parent marks nested unsettled slots
984
+ * as sent, which can make further units eligible — loop until a fixpoint.
949
985
  * @returns {void}
950
986
  */
951
987
  #sweep_units() {
952
- var units = this.#units;
988
+ var units = /** @type {FlushUnit[]} */ (this.#units);
989
+ var out = '';
953
990
  var progressed = true;
954
991
  while (progressed) {
955
992
  progressed = false;
956
993
  for (var i = 0; i < units.length; i++) {
957
994
  var unit = units[i];
958
995
  if (unit.settled && !unit.flushed && unit.slot_sent) {
959
- this.#flush_unit(unit);
996
+ out += this.#unit_chunk(unit);
960
997
  progressed = true;
961
998
  }
962
999
  }
963
1000
  }
1001
+ if (out !== '') {
1002
+ /** @type {StreamSink} */ (this.#streamOutput).push(out);
1003
+ }
964
1004
  }
965
1005
 
966
1006
  /**
@@ -984,7 +1024,7 @@ export class Output {
984
1024
  // the root boundary's slot is the shell body itself
985
1025
  this.unit.slot_sent = true;
986
1026
  }
987
- for (var hash of this.#css) {
1027
+ for (var hash of /** @type {Set<string>} */ (this.#css)) {
988
1028
  chunk.css.add(hash);
989
1029
  }
990
1030
  var template = this.#stream_template;
@@ -1003,8 +1043,9 @@ export class Output {
1003
1043
  out += BLOCK_OPEN + body + BLOCK_CLOSE;
1004
1044
  }
1005
1045
  var has_open_units = false;
1006
- for (var i = 0; i < this.#units.length; i++) {
1007
- if (!this.#units[i].flushed) {
1046
+ var units = /** @type {FlushUnit[]} */ (this.#units);
1047
+ for (var i = 0; i < units.length; i++) {
1048
+ if (!units[i].flushed) {
1008
1049
  has_open_units = true;
1009
1050
  break;
1010
1051
  }
@@ -1072,6 +1113,7 @@ export class Output {
1072
1113
  _closeStream() {
1073
1114
  if (this.#is_root) {
1074
1115
  if (this.#streamOutput && this.#stream_started && !this.#stream_finished) {
1116
+ this.#sweep_units();
1075
1117
  this.#stream_finished = true;
1076
1118
  if (this.#stream_template !== null && this.#stream_template.after !== '') {
1077
1119
  this.#streamOutput.push(this.#stream_template.after);
@@ -1113,17 +1155,28 @@ export class Output {
1113
1155
  }
1114
1156
  }
1115
1157
 
1158
+ /**
1159
+ * Concatenates a buffer tree (see `Output`) into one string. A hand-rolled
1160
+ * walk is far cheaper than `flat(Infinity).join('')` on a cold isolate, where
1161
+ * a render's cost is dominated by unoptimized code.
1162
+ * @param {NestedArray<string>} tree
1163
+ * @returns {string}
1164
+ */
1165
+ function flatten_buffer(tree) {
1166
+ var out = '';
1167
+ for (var i = 0; i < tree.length; i++) {
1168
+ var item = tree[i];
1169
+ out += typeof item === 'string' ? item : flatten_buffer(item);
1170
+ }
1171
+ return out;
1172
+ }
1173
+
1116
1174
  /**
1117
1175
  * @param {RenderComponent} component
1118
- * @param {BaseRenderOptions} [passed_in_options]
1176
+ * @param {BaseRenderOptions} [options]
1119
1177
  * @returns {Promise<RenderResult | RenderStreamResult>}
1120
1178
  */
1121
- export async function render(component, passed_in_options = {}) {
1122
- /** @type {BaseRenderOptions} */
1123
- var options = {
1124
- ...(passed_in_options.stream ? { closeStream: true } : {}),
1125
- ...passed_in_options,
1126
- };
1179
+ export async function render(component, options = {}) {
1127
1180
  /** @type {Error | null } */
1128
1181
  var top_level_error = null;
1129
1182
  var head = '';
@@ -1182,7 +1235,7 @@ export async function render(component, passed_in_options = {}) {
1182
1235
  await output.promise;
1183
1236
  reset_state();
1184
1237
 
1185
- if (output.isStreamMode() && options.closeStream) {
1238
+ if (output.isStreamMode() && ('closeStream' in options ? options.closeStream : true)) {
1186
1239
  output._closeStream();
1187
1240
  }
1188
1241
 
@@ -1199,12 +1252,40 @@ export async function render(component, passed_in_options = {}) {
1199
1252
  * @returns {void}
1200
1253
  */
1201
1254
  function sync_buffers_to_string(output) {
1202
- head = /** @type {string[]} */ (output.head).flat(Infinity).join('');
1203
- body = BLOCK_OPEN + /** @type {string[]} */ (output.body).flat(Infinity).join('') + BLOCK_CLOSE;
1255
+ head = flatten_buffer(output.head);
1256
+ body = BLOCK_OPEN + flatten_buffer(output.body) + BLOCK_CLOSE;
1204
1257
  css = output.css;
1205
1258
  }
1206
1259
  }
1207
1260
 
1261
+ var CONTENT_SPECIAL = /[&<]/;
1262
+ var ATTR_SPECIAL = /[&"<]/;
1263
+ var SCRIPT_SPECIAL = /[<>]/;
1264
+
1265
+ /**
1266
+ * Escapes the characters that could end an inline `<script>` early. JSON
1267
+ * payloads rarely contain `<` or `>`, so one test replaces two global
1268
+ * replace scans in the common case.
1269
+ * @param {string} str
1270
+ * @returns {string}
1271
+ */
1272
+ function escape_inline_script(str) {
1273
+ return SCRIPT_SPECIAL.test(str) ? escape_script(str) : str;
1274
+ }
1275
+
1276
+ /**
1277
+ * Escapes text or attribute content. Strings without a character to escape
1278
+ * (the common case) return as they are after a single regex test; anything
1279
+ * else goes through the general escaper.
1280
+ * @param {unknown} value
1281
+ * @param {boolean} [is_attr]
1282
+ * @returns {string}
1283
+ */
1284
+ export function escape(value, is_attr) {
1285
+ var str = typeof value === 'string' ? value : value == null ? '' : String(value);
1286
+ return (is_attr ? ATTR_SPECIAL : CONTENT_SPECIAL).test(str) ? escape_html(str, is_attr) : str;
1287
+ }
1288
+
1208
1289
  /**
1209
1290
  * @returns {void}
1210
1291
  */
@@ -1213,7 +1294,7 @@ export function push_component() {
1213
1294
  c: null,
1214
1295
  p: active_component,
1215
1296
  };
1216
- active_block = component_block(() => {});
1297
+ active_block = component_block(noop);
1217
1298
  }
1218
1299
 
1219
1300
  /**
@@ -1268,7 +1349,11 @@ function print_nesting_error(message) {
1268
1349
  `node_invalid_placement_ssr: ${message}\n\n` +
1269
1350
  'This can cause content to shift around as the browser repairs the HTML, and will likely result in a hydration mismatch.';
1270
1351
 
1271
- if (seen_warnings.has(message)) return;
1352
+ if (seen_warnings === null) {
1353
+ seen_warnings = new Set();
1354
+ } else if (seen_warnings.has(message)) {
1355
+ return;
1356
+ }
1272
1357
  seen_warnings.add(message);
1273
1358
 
1274
1359
  // eslint-disable-next-line no-console
@@ -1404,60 +1489,6 @@ export function get_derived(tracked) {
1404
1489
  return g ? g(tracked.v) : tracked.v;
1405
1490
  }
1406
1491
 
1407
- /**
1408
- * @param {any} lazy
1409
- * @param {number} [index]
1410
- * @returns {any}
1411
- */
1412
- export function lazy_array_get(lazy, index = 0) {
1413
- if (is_array(lazy)) {
1414
- return lazy[index];
1415
- }
1416
- var flags = lazy.f;
1417
- if (flags === TRACKED) {
1418
- return index === 0
1419
- ? get_tracked(/** @type {Tracked} */ (lazy))
1420
- : index === 1
1421
- ? lazy
1422
- : undefined;
1423
- }
1424
- if (flags === DERIVED) {
1425
- return index === 0
1426
- ? get_derived(/** @type {Derived} */ (lazy))
1427
- : index === 1
1428
- ? lazy
1429
- : undefined;
1430
- }
1431
- return iterable_array_from(lazy, index)[0];
1432
- }
1433
-
1434
- /**
1435
- * @param {any} lazy
1436
- * @param {number} [index]
1437
- * @returns {any[]}
1438
- */
1439
- export function lazy_array_rest(lazy, index = 0) {
1440
- if (is_array(lazy)) {
1441
- return lazy.slice(index);
1442
- }
1443
- var flags = lazy.f;
1444
- if (flags === TRACKED) {
1445
- return index === 0
1446
- ? [get_tracked(/** @type {Tracked} */ (lazy)), lazy]
1447
- : index === 1
1448
- ? [lazy]
1449
- : [];
1450
- }
1451
- if (flags === DERIVED) {
1452
- return index === 0
1453
- ? [get_derived(/** @type {Derived} */ (lazy)), lazy]
1454
- : index === 1
1455
- ? [lazy]
1456
- : [];
1457
- }
1458
- return iterable_array_from(lazy, index);
1459
- }
1460
-
1461
1492
  /**
1462
1493
  * @param {Derived | Tracked} tracked
1463
1494
  * @param {any} value
@@ -1467,62 +1498,11 @@ export function set(tracked, value) {
1467
1498
 
1468
1499
  if (value !== old_value) {
1469
1500
  var s = tracked.a.set;
1470
- tracked.v = s ? s(value, tracked.v) : value;
1501
+ tracked.v = typeof s === 'function' ? s(value, tracked.v) : value;
1471
1502
  tracked.c = increment_clock();
1472
1503
  }
1473
1504
  }
1474
1505
 
1475
- /**
1476
- * @param {any} lazy
1477
- * @param {any} value
1478
- * @param {number} [index]
1479
- * @returns {void}
1480
- */
1481
- export function lazy_array_set(lazy, value, index = 0) {
1482
- if (is_array(lazy)) {
1483
- lazy[index] = value;
1484
- return;
1485
- }
1486
- var flags = lazy.f;
1487
- if (flags === TRACKED || flags === DERIVED) {
1488
- if (index === 0) {
1489
- set(/** @type {Derived | Tracked} */ (lazy), value);
1490
- return;
1491
- }
1492
- if (index === 1) {
1493
- throw_tracked_index_reference_error();
1494
- }
1495
- return;
1496
- }
1497
- lazy[index] = value;
1498
- }
1499
-
1500
- /**
1501
- * @param {any} lazy
1502
- * @param {number} [index]
1503
- * @param {number} [d]
1504
- * @returns {number}
1505
- */
1506
- export function lazy_array_update(lazy, index = 0, d = 1) {
1507
- var value = lazy_array_get(lazy, index);
1508
- var result = d === 1 ? value++ : value--;
1509
- lazy_array_set(lazy, value, index);
1510
- return result;
1511
- }
1512
-
1513
- /**
1514
- * @param {any} lazy
1515
- * @param {number} [index]
1516
- * @param {number} [d]
1517
- * @returns {number}
1518
- */
1519
- export function lazy_array_update_pre(lazy, index = 0, d = 1) {
1520
- var value = lazy_array_get(lazy, index);
1521
- var new_value = d === 1 ? ++value : --value;
1522
- lazy_array_set(lazy, new_value, index);
1523
- return new_value;
1524
- }
1525
-
1526
1506
  /**
1527
1507
  * @param {Tracked} tracked
1528
1508
  * @param {number} [d]
@@ -1645,11 +1625,10 @@ function get_styles(styles) {
1645
1625
 
1646
1626
  /**
1647
1627
  * @param {Record<string, any>} attrs
1648
- * @param {string | undefined} css_hash
1649
- * @param {string} [exclude_prop]
1628
+ * @param {string | undefined} [css_hash]
1650
1629
  * @returns {string}
1651
1630
  */
1652
- export function spread_attrs(attrs, css_hash, exclude_prop) {
1631
+ export function spread_attrs(attrs, css_hash) {
1653
1632
  let attr_str = '';
1654
1633
  let name;
1655
1634
 
@@ -1664,7 +1643,6 @@ export function spread_attrs(attrs, css_hash, exclude_prop) {
1664
1643
  name === 'children' ||
1665
1644
  name === 'innerHTML' ||
1666
1645
  name === '#class' ||
1667
- name === exclude_prop ||
1668
1646
  typeof value === 'function' ||
1669
1647
  is_tsrx_element(value)
1670
1648
  )
@@ -1707,11 +1685,11 @@ var empty_get_set = { get: undefined, set: undefined };
1707
1685
  class TrackedValue {
1708
1686
  /**
1709
1687
  * @param {any} v
1710
- * @param {{ get?: Function; set?: Function }} a
1688
+ * @param {{ get?: Function; set?: Function | true }} a
1711
1689
  * @param {string} hash
1712
1690
  */
1713
1691
  constructor(v, a, hash) {
1714
- /** @type {{ get?: Function; set?: Function }} */
1692
+ /** @type {{ get?: Function; set?: Function | true }} */
1715
1693
  this.a = a;
1716
1694
  /** @type {AbortController | null} */
1717
1695
  this.aa = null;
@@ -1729,18 +1707,6 @@ class TrackedValue {
1729
1707
  this.v = v;
1730
1708
  }
1731
1709
  /** @returns {any} */
1732
- get [0]() {
1733
- return throw_tracked_index_value_error();
1734
- }
1735
- /** @param {any} v */
1736
- set [0](v) {
1737
- throw_tracked_index_value_error();
1738
- }
1739
- /** @returns {Tracked} */
1740
- get [1]() {
1741
- return throw_tracked_index_reference_error();
1742
- }
1743
- /** @returns {any} */
1744
1710
  get value() {
1745
1711
  return get_tracked(/** @type {Tracked} */ (this));
1746
1712
  }
@@ -1748,25 +1714,25 @@ class TrackedValue {
1748
1714
  set value(v) {
1749
1715
  set(/** @type {Tracked} */ (this), v);
1750
1716
  }
1751
- /** @returns {2} */
1752
- get length() {
1753
- return 2;
1754
- }
1755
- /** @returns {Iterator<any | Tracked>} */
1756
- *[Symbol.iterator]() {
1757
- yield get_tracked(/** @type {Tracked} */ (this));
1758
- yield this;
1717
+ /**
1718
+ * A read-only view: a derived over this value, for a receiver that should
1719
+ * read but not write it. Equivalent to `track(() => tracked.value)`. The
1720
+ * view has no hash of its own: it is not a serialized value.
1721
+ * @returns {Derived}
1722
+ */
1723
+ readOnly() {
1724
+ return derived(() => get_tracked(/** @type {Tracked} */ (this)));
1759
1725
  }
1760
1726
  }
1761
1727
 
1762
1728
  class DerivedValue {
1763
1729
  /**
1764
1730
  * @param {Function} fn
1765
- * @param {{ get?: Function; set?: Function }} a
1766
- * @param {string} hash
1731
+ * @param {{ get?: Function; set?: Function | true }} a
1732
+ * @param {string} [hash]
1767
1733
  */
1768
1734
  constructor(fn, a, hash) {
1769
- /** @type {{ get?: Function; set?: Function }} */
1735
+ /** @type {{ get?: Function; set?: Function | true }} */
1770
1736
  this.a = a;
1771
1737
  // we always should have an active block
1772
1738
  /** @type {Block} */
@@ -1781,24 +1747,12 @@ class DerivedValue {
1781
1747
  this.f = DERIVED;
1782
1748
  /** @type {Function} */
1783
1749
  this.fn = fn;
1784
- /** @type {string} */
1750
+ /** @type {string | undefined} */
1785
1751
  this.h = hash;
1786
1752
  /** @type {any} */
1787
1753
  this.v = UNINITIALIZED;
1788
1754
  }
1789
1755
  /** @returns {any} */
1790
- get [0]() {
1791
- return throw_tracked_index_value_error();
1792
- }
1793
- /** @param {any} v */
1794
- set [0](v) {
1795
- throw_tracked_index_value_error();
1796
- }
1797
- /** @returns {Derived} */
1798
- get [1]() {
1799
- return throw_tracked_index_reference_error();
1800
- }
1801
- /** @returns {any} */
1802
1756
  get value() {
1803
1757
  return get_derived(/** @type {Derived} */ (this));
1804
1758
  }
@@ -1806,14 +1760,16 @@ class DerivedValue {
1806
1760
  set value(v) {
1807
1761
  set(/** @type {Derived} */ (this), v);
1808
1762
  }
1809
- /** @returns {2} */
1810
- get length() {
1811
- return 2;
1812
- }
1813
- /** @returns {Iterator<any | Derived>} */
1814
- *[Symbol.iterator]() {
1815
- yield get_derived(/** @type {Derived} */ (this));
1816
- yield this;
1763
+ /**
1764
+ * A read-only view (see `TrackedValue#readOnly`). A derived without a
1765
+ * setter is already read-only and is returned as is; a writable one is
1766
+ * wrapped in a derived that follows it.
1767
+ * @returns {Derived}
1768
+ */
1769
+ readOnly() {
1770
+ return this.a.set === undefined
1771
+ ? /** @type {Derived} */ (this)
1772
+ : derived(() => get_derived(/** @type {Derived} */ (this)));
1817
1773
  }
1818
1774
  }
1819
1775
 
@@ -1821,7 +1777,7 @@ class DerivedValue {
1821
1777
  * @param {any} v
1822
1778
  * @param {string} hash
1823
1779
  * @param {(value: any) => any} [get]
1824
- * @param {(next: any, prev: any) => any} [set]
1780
+ * @param {((next: any, prev: any) => any) | true} [set]
1825
1781
  * @returns {Tracked}
1826
1782
  */
1827
1783
  function tracked(v, hash, get, set) {
@@ -1830,29 +1786,11 @@ function tracked(v, hash, get, set) {
1830
1786
  );
1831
1787
  }
1832
1788
 
1833
- /**
1834
- * @param {Record<string, unknown>} obj
1835
- * @param {string[]} exclude_keys
1836
- * @returns {Record<string, unknown>}
1837
- */
1838
- export function exclude_from_object(obj, exclude_keys) {
1839
- /** @type {Record<string, unknown>} */
1840
- var new_obj = {};
1841
-
1842
- for (const key of Object.keys(obj)) {
1843
- if (!exclude_keys.includes(key)) {
1844
- new_obj[key] = obj[key];
1845
- }
1846
- }
1847
-
1848
- return new_obj;
1849
- }
1850
-
1851
1789
  /**
1852
1790
  * @param {any} v
1853
- * @param {string} hash
1791
+ * @param {string} [hash]
1854
1792
  * @param {(value: any) => any} [get]
1855
- * @param {(next: any, prev: any) => any} [set]
1793
+ * @param {((next: any, prev: any) => any) | true} [set]
1856
1794
  * @returns {Derived}
1857
1795
  */
1858
1796
  function derived(v, hash, get, set) {
@@ -1865,7 +1803,7 @@ function derived(v, hash, get, set) {
1865
1803
  * @param {any} v
1866
1804
  * @param {string} hash
1867
1805
  * @param {(value: any) => any} [get]
1868
- * @param {(next: any, prev: any) => any} [set]
1806
+ * @param {((next: any, prev: any) => any) | true} [set]
1869
1807
  * @returns {Tracked | Derived}
1870
1808
  */
1871
1809
  export function track(v, hash, get, set) {
@@ -1891,12 +1829,130 @@ export function track(v, hash, get, set) {
1891
1829
  * @returns {void}
1892
1830
  */
1893
1831
  function serialize_track_async_result(output, hash, value, deps) {
1894
- /** @type {{ ok: true, payload: string, deps?: string[] }} */
1895
- var envelope = { ok: true, payload: devalue.stringify(value) };
1896
- if (deps && deps.length > 0) {
1897
- envelope.deps = deps;
1832
+ /** @type {string} */
1833
+ var envelope;
1834
+ if (is_plain_data(value, null)) {
1835
+ // plain data: the value travels as raw JSON inside the envelope, so the
1836
+ // client reads it straight off `JSON.parse` with no devalue pass
1837
+ envelope = '{"ok":true,"value":' + JSON.stringify(value);
1838
+ if (deps && deps.length > 0) {
1839
+ envelope += ',"deps":' + JSON.stringify(deps);
1840
+ }
1841
+ envelope += '}';
1842
+ } else {
1843
+ /** @type {{ ok: true, payload: string, deps?: string[] }} */
1844
+ var object = { ok: true, payload: devalue.stringify(value) };
1845
+ if (deps && deps.length > 0) {
1846
+ object.deps = deps;
1847
+ }
1848
+ envelope = JSON.stringify(object);
1898
1849
  }
1899
- push_script_for_hydration((str) => output.push_serialized_result(str), hash, envelope);
1850
+ output.push_serialized_result(
1851
+ '<script id="' +
1852
+ get_track_async_script_id(hash) +
1853
+ '" type="application/json">' +
1854
+ escape_inline_script(envelope) +
1855
+ '</script>',
1856
+ );
1857
+ }
1858
+
1859
+ const default_track_async_serializer = serialize_track_async_result;
1860
+
1861
+ /**
1862
+ * Switch once at registration, keeping the default serializer unchanged and
1863
+ * avoiding a transport lookup for every result. Unused registration code can
1864
+ * be removed from builds that only use the default renderer.
1865
+ * @param {Record<string, (value: any) => any> | undefined} reducers
1866
+ */
1867
+ export function set_track_async_transport(reducers) {
1868
+ /** @type {typeof serialize_track_async_result} */
1869
+ const serializer =
1870
+ reducers === undefined
1871
+ ? default_track_async_serializer
1872
+ : (output, hash, value, deps) => {
1873
+ // Reducers can claim plain objects: devalue must do the only walk.
1874
+ // Embed its JSON directly, avoiding another stringify of the payload
1875
+ // string and another JSON.parse on the client before revival.
1876
+ let envelope = '{"ok":true,"payload":' + devalue.stringify(value, reducers);
1877
+ if (deps && deps.length > 0) envelope += ',"deps":' + JSON.stringify(deps);
1878
+ envelope += '}';
1879
+ output.push_serialized_result(
1880
+ '<script id="' +
1881
+ get_track_async_script_id(hash) +
1882
+ '" type="application/json">' +
1883
+ escape_inline_script(envelope) +
1884
+ '</script>',
1885
+ );
1886
+ };
1887
+ // Keep the function declaration so unused registration leaves exactly the
1888
+ // original renderer in production bundles, without a mutable alias.
1889
+ // @ts-expect-error JavaScript permits reassigning a function declaration.
1890
+ serialize_track_async_result = serializer;
1891
+ }
1892
+
1893
+ /**
1894
+ * Whether a value is a plain data tree that `JSON.stringify` encodes without
1895
+ * loss: strings, finite numbers (not `-0`), booleans, `null`, arrays without
1896
+ * holes and plain objects, every object reachable exactly once. `JSON.parse`
1897
+ * on the client then yields exactly what `devalue.parse` would. Anything else
1898
+ * — `undefined`, `NaN`, `Infinity`, `-0`, bigints, symbols, functions, Dates,
1899
+ * Maps, Sets, class instances, null-prototype objects, symbol keys, cycles
1900
+ * and shared references — keeps its devalue encoding.
1901
+ * @param {unknown} value
1902
+ * @param {Set<object> | null} seen
1903
+ * @returns {boolean}
1904
+ */
1905
+ function is_plain_data(value, seen) {
1906
+ switch (typeof value) {
1907
+ case 'string':
1908
+ case 'boolean':
1909
+ return true;
1910
+ case 'number':
1911
+ return (
1912
+ value === value &&
1913
+ value !== Infinity &&
1914
+ value !== -Infinity &&
1915
+ (value !== 0 || 1 / value > 0)
1916
+ );
1917
+ case 'object':
1918
+ break;
1919
+ default:
1920
+ return false;
1921
+ }
1922
+ if (value === null) {
1923
+ return true;
1924
+ }
1925
+ var proto = Object.getPrototypeOf(value);
1926
+ var is_list = proto === Array.prototype;
1927
+ if (!is_list && proto !== Object.prototype) {
1928
+ return false;
1929
+ }
1930
+ if (seen === null) {
1931
+ seen = new Set();
1932
+ } else if (seen.has(value)) {
1933
+ return false;
1934
+ }
1935
+ seen.add(value);
1936
+ if (is_list) {
1937
+ var list = /** @type {unknown[]} */ (value);
1938
+ for (var i = 0; i < list.length; i++) {
1939
+ if (!is_plain_data(list[i], seen)) {
1940
+ return false;
1941
+ }
1942
+ }
1943
+ return true;
1944
+ }
1945
+ if (Object.getOwnPropertySymbols(value).length !== 0) {
1946
+ return false;
1947
+ }
1948
+ var record = /** @type {Record<string, unknown>} */ (value);
1949
+ var keys = Object.keys(record);
1950
+ for (var k = 0; k < keys.length; k++) {
1951
+ if (!is_plain_data(record[keys[k]], seen)) {
1952
+ return false;
1953
+ }
1954
+ }
1955
+ return true;
1900
1956
  }
1901
1957
 
1902
1958
  /**
@@ -2075,7 +2131,7 @@ function settle_unit_after_catch(catch_block) {
2075
2131
  * @returns {void}
2076
2132
  */
2077
2133
  function push_script_for_hydration(push_fn, hash, envelope) {
2078
- var serialized_envelope = escape_script(JSON.stringify(envelope));
2134
+ var serialized_envelope = escape_inline_script(JSON.stringify(envelope));
2079
2135
 
2080
2136
  push_fn(
2081
2137
  '<script id="' +
@@ -2215,20 +2271,25 @@ function run_track_async(t, fn, block, dr, dj) {
2215
2271
  }
2216
2272
 
2217
2273
  /**
2218
- * Walks a dependency chain and collects the hashes of dependencies that have
2219
- * one (i.e. were created from a compile-time track/trackAsync call).
2274
+ * The hashes of the compile-time tracked values a trackAsync read, recorded
2275
+ * in its hydration envelope so the client re-subscribes to them. A dependency
2276
+ * without a hash that is itself a derived (a `readOnly()` view, a derived made
2277
+ * outside a compiled call) stands for what it read: its own dependencies are
2278
+ * recorded in its place.
2220
2279
  * @param {Dependency | null} head
2280
+ * @param {string[] | null} [hashes]
2221
2281
  * @returns {string[] | null}
2222
2282
  */
2223
- function collect_dep_hashes(head) {
2224
- /** @type {string[] | null} */
2225
- var hashes = null;
2283
+ function collect_dep_hashes(head, hashes = null) {
2226
2284
  var dep = head;
2227
2285
  while (dep !== null) {
2228
- var h = /** @type {{ h?: string }} */ (dep.t).h;
2286
+ var t = /** @type {{ h?: string; f: number; d?: Dependency | null }} */ (dep.t);
2287
+ var h = t.h;
2229
2288
  if (h !== undefined) {
2230
2289
  if (hashes === null) hashes = [];
2231
- hashes.push(h);
2290
+ if (!hashes.includes(h)) hashes.push(h);
2291
+ } else if ((t.f & DERIVED) !== 0) {
2292
+ hashes = collect_dep_hashes(t.d ?? null, hashes);
2232
2293
  }
2233
2294
  dep = dep.n;
2234
2295
  }
@@ -2511,15 +2572,4 @@ export function ripple_map(iterable) {
2511
2572
  return new Map(iterable);
2512
2573
  }
2513
2574
 
2514
- /**
2515
- * Returns the fallback value if the given value is undefined.
2516
- * @template T
2517
- * @param {T | undefined} value
2518
- * @param {T} fallback
2519
- * @returns {T}
2520
- */
2521
- export function fallback(value, fallback) {
2522
- return value === undefined ? fallback : value;
2523
- }
2524
-
2525
2575
  export { dynamic_element } from './dynamic.js';