ripple 0.2.208 → 0.2.210

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 (108) hide show
  1. package/CHANGELOG.md +43 -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 +15 -6
  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 +16 -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 +117 -11
  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/for.js +1140 -23
  69. package/tests/hydration/compiled/client/head.js +234 -0
  70. package/tests/hydration/compiled/client/html.js +135 -0
  71. package/tests/hydration/compiled/client/portal.js +172 -0
  72. package/tests/hydration/compiled/client/reactivity.js +3 -1
  73. package/tests/hydration/compiled/client/return.js +1976 -0
  74. package/tests/hydration/compiled/client/switch.js +162 -0
  75. package/tests/hydration/compiled/server/basic.js +249 -0
  76. package/tests/hydration/compiled/server/events.js +1 -1
  77. package/tests/hydration/compiled/server/for.js +891 -1
  78. package/tests/hydration/compiled/server/head.js +291 -0
  79. package/tests/hydration/compiled/server/html.js +133 -0
  80. package/tests/hydration/compiled/server/if.js +1 -1
  81. package/tests/hydration/compiled/server/portal.js +250 -0
  82. package/tests/hydration/compiled/server/reactivity.js +1 -1
  83. package/tests/hydration/compiled/server/return.js +1969 -0
  84. package/tests/hydration/compiled/server/switch.js +130 -0
  85. package/tests/hydration/components/basic.ripple +55 -0
  86. package/tests/hydration/components/for.ripple +403 -0
  87. package/tests/hydration/components/head.ripple +111 -0
  88. package/tests/hydration/components/html.ripple +38 -0
  89. package/tests/hydration/components/portal.ripple +49 -0
  90. package/tests/hydration/components/return.ripple +564 -0
  91. package/tests/hydration/components/switch.ripple +51 -0
  92. package/tests/hydration/for.test.js +363 -0
  93. package/tests/hydration/head.test.js +105 -0
  94. package/tests/hydration/html.test.js +46 -0
  95. package/tests/hydration/portal.test.js +71 -0
  96. package/tests/hydration/return.test.js +544 -0
  97. package/tests/hydration/switch.test.js +42 -0
  98. package/tests/server/basic.attributes.test.ripple +1 -1
  99. package/tests/server/compiler.test.ripple +22 -0
  100. package/tests/server/composite.test.ripple +5 -2
  101. package/tests/server/html-nesting-validation.test.ripple +237 -0
  102. package/tests/server/return.test.ripple +1379 -0
  103. package/tests/setup-hydration.js +6 -1
  104. package/tests/utils/escaping.test.js +3 -1
  105. package/tests/utils/normalize_css_property_name.test.js +0 -1
  106. package/tests/utils/patterns.test.js +6 -2
  107. package/tests/utils/sanitize_template_string.test.js +3 -2
  108. package/types/server.d.ts +16 -0
@@ -13,15 +13,15 @@
13
13
  * @returns {void} Ripple components don't return anything
14
14
  */
15
15
  export function jsx(type, props, key) {
16
- // Ripple components are imperative - they don't return JSX elements
17
- // This is a placeholder for the actual Ripple rendering logic
18
- if (typeof type === 'function') {
19
- // Call the Ripple component function
20
- type(props);
21
- } else {
22
- // Handle DOM elements
23
- console.warn('DOM element rendering not implemented in jsx runtime:', type, props);
24
- }
16
+ // Ripple components are imperative - they don't return JSX elements
17
+ // This is a placeholder for the actual Ripple rendering logic
18
+ if (typeof type === 'function') {
19
+ // Call the Ripple component function
20
+ type(props);
21
+ } else {
22
+ // Handle DOM elements
23
+ console.warn('DOM element rendering not implemented in jsx runtime:', type, props);
24
+ }
25
25
  }
26
26
 
27
27
  /**
@@ -32,7 +32,7 @@ export function jsx(type, props, key) {
32
32
  * @returns {void} Ripple components don't return anything
33
33
  */
34
34
  export function jsxs(type, props, key) {
35
- return jsx(type, props, key);
35
+ return jsx(type, props, key);
36
36
  }
37
37
 
38
38
  /**
@@ -41,6 +41,6 @@ export function jsxs(type, props, key) {
41
41
  * @returns {void} Ripple fragments don't return anything
42
42
  */
43
43
  export function Fragment(props) {
44
- // Ripple fragments are imperative
45
- console.warn('Fragment rendering not implemented in jsx runtime:', props);
44
+ // Ripple fragments are imperative
45
+ console.warn('Fragment rendering not implemented in jsx runtime:', props);
46
46
  }
@@ -9,12 +9,12 @@ import { array_proxy } from './proxy.js';
9
9
  * @returns {TrackedArray<T>}
10
10
  */
11
11
  export function TrackedArray(...elements) {
12
- if (!new.target) {
13
- throw new Error("TrackedArray must be called with 'new'");
14
- }
12
+ if (!new.target) {
13
+ throw new Error("TrackedArray must be called with 'new'");
14
+ }
15
15
 
16
- var block = safe_scope();
17
- return array_proxy({ elements, block });
16
+ var block = safe_scope();
17
+ return array_proxy({ elements, block });
18
18
  }
19
19
 
20
20
  /**
@@ -25,9 +25,9 @@ export function TrackedArray(...elements) {
25
25
  * @returns {TrackedArray<T>}
26
26
  */
27
27
  TrackedArray.from = function (arrayLike, mapFn, thisArg) {
28
- var block = safe_scope();
29
- var elements = mapFn ? Array.from(arrayLike, mapFn, thisArg) : Array.from(arrayLike);
30
- return array_proxy({ elements, block, from_static: true });
28
+ var block = safe_scope();
29
+ var elements = mapFn ? Array.from(arrayLike, mapFn, thisArg) : Array.from(arrayLike);
30
+ return array_proxy({ elements, block, from_static: true });
31
31
  };
32
32
 
33
33
  /**
@@ -36,9 +36,9 @@ TrackedArray.from = function (arrayLike, mapFn, thisArg) {
36
36
  * @returns {TrackedArray<T>}
37
37
  */
38
38
  TrackedArray.of = function (...items) {
39
- var block = safe_scope();
40
- var elements = Array.of(...items);
41
- return array_proxy({ elements, block, from_static: true });
39
+ var block = safe_scope();
40
+ var elements = Array.of(...items);
41
+ return array_proxy({ elements, block, from_static: true });
42
42
  };
43
43
 
44
44
  /**
@@ -49,11 +49,11 @@ TrackedArray.of = function (...items) {
49
49
  * @returns {Promise<TrackedArray<T>>}
50
50
  */
51
51
  TrackedArray.fromAsync = async function (arrayLike, mapFn, thisArg) {
52
- var block = safe_scope();
53
- var elements = mapFn
54
- ? await Array.fromAsync(arrayLike, mapFn, thisArg)
55
- : await Array.fromAsync(arrayLike);
56
- return array_proxy({ elements, block, from_static: true });
52
+ var block = safe_scope();
53
+ var elements = mapFn
54
+ ? await Array.fromAsync(arrayLike, mapFn, thisArg)
55
+ : await Array.fromAsync(arrayLike);
56
+ return array_proxy({ elements, block, from_static: true });
57
57
  };
58
58
 
59
59
  /**
@@ -63,5 +63,5 @@ TrackedArray.fromAsync = async function (arrayLike, mapFn, thisArg) {
63
63
  * @returns {TrackedArray<T>}
64
64
  */
65
65
  export function tracked_array(elements, block) {
66
- return array_proxy({ elements, block, from_static: true });
66
+ return array_proxy({ elements, block, from_static: true });
67
67
  }
@@ -1,6 +1,6 @@
1
1
  /** @import { createSubscriber } from '#public' */
2
2
  import { untrack, queue_microtask } from './internal/client/runtime.js';
3
- import { effect } from './internal/client/blocks.js'
3
+ import { effect } from './internal/client/blocks.js';
4
4
 
5
5
  /** @type {createSubscriber} */
6
6
  export function createSubscriber(start) {
@@ -17,7 +17,7 @@ import {
17
17
  set_hydrate_node,
18
18
  set_hydrating,
19
19
  } from './internal/client/hydration.js';
20
- import { COMMENT_NODE, HYDRATION_ERROR, HYDRATION_START } from '../constants.js';
20
+ import { COMMENT_NODE, HYDRATION_START } from '../constants.js';
21
21
 
22
22
  // Re-export JSX runtime functions for jsxImportSource: "ripple"
23
23
  export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
@@ -84,10 +84,6 @@ export function hydrate(component, options) {
84
84
  set_hydrate_node(/** @type {Comment} */ (anchor));
85
85
  hydrate_next();
86
86
 
87
- if (!anchor) {
88
- throw HYDRATION_ERROR;
89
- }
90
-
91
87
  _root = root(() => {
92
88
  component(/** @type {Comment} */ (anchor), props, active_block);
93
89
  }, options.compat);
@@ -56,3 +56,18 @@ export const bindTextContent = noop;
56
56
  export const bindNode = noop;
57
57
  export const bindOffsetWidth = noop;
58
58
  export const bindOffsetHeight = noop;
59
+
60
+ /**
61
+ * Portal component noop for server-side rendering.
62
+ * Portals are client-only and do not render on the server.
63
+ * However, we need to output a marker comment so hydration can work correctly.
64
+ * @param {any} output
65
+ * @param {any} __
66
+ */
67
+ export function Portal(output, __) {
68
+ // Portals are client-only, but we need to output a marker for hydration
69
+ // Output an empty HTML comment as a placeholder
70
+ if (output && typeof output.push === 'function') {
71
+ output.push('<!--portal-->');
72
+ }
73
+ }
@@ -1,13 +1,13 @@
1
1
  /** @import { CompatApi } from '#client' */
2
2
 
3
- import { ROOT_BLOCK } from "./constants.js";
4
- import { active_block } from "./runtime.js";
3
+ import { ROOT_BLOCK } from './constants.js';
4
+ import { active_block } from './runtime.js';
5
5
 
6
6
  /**
7
7
  * @param {string} kind
8
8
  * @returns {CompatApi | null}
9
9
  */
10
- function get_compat_from_root(kind) {
10
+ function get_compat_from_root(kind) {
11
11
  var current = active_block;
12
12
 
13
13
  while (current !== null) {
@@ -15,7 +15,12 @@ import { top_element_to_ns } from './utils.js';
15
15
  */
16
16
  export function composite(get_component, node, props) {
17
17
  if (hydrating) {
18
- hydrate_next();
18
+ // During hydration, `node` may already point at the first real SSR node
19
+ // (e.g. layout children). Only skip forward when we are on an empty
20
+ // comment anchor from a client template placeholder.
21
+ if (node.nodeType === 8 && /** @type {Comment} */ (node).data === '') {
22
+ hydrate_next();
23
+ }
19
24
  }
20
25
 
21
26
  var anchor = node;
@@ -1,14 +1,60 @@
1
+ /** @import { TemplateNode } from '#client' */
1
2
  import { render } from './blocks.js';
2
3
  import { HEAD_BLOCK } from './constants.js';
3
- import { create_text } from './operations.js';
4
+ import { COMMENT_NODE } from '../../../constants.js';
5
+ import { create_text, get_first_child, get_next_sibling } from './operations.js';
6
+ import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from './hydration.js';
4
7
 
5
8
  /**
9
+ * @param {string} hash
6
10
  * @param {(anchor: Node) => void} render_fn
7
11
  * @returns {void}
8
12
  */
9
- export function head(render_fn) {
13
+ export function head(hash, render_fn) {
14
+ // The head function may be called after the first hydration pass and ssr comment nodes may still be present,
15
+ // therefore we need to skip that when we detect that we're not in hydration mode.
16
+ let previous_hydrate_node = null;
17
+ let was_hydrating = hydrating;
18
+
10
19
  /** @type {Comment | Text} */
11
- var anchor = document.head.appendChild(create_text());
20
+ var anchor;
21
+
22
+ if (hydrating) {
23
+ previous_hydrate_node = hydrate_node;
24
+
25
+ var head_anchor = get_first_child(document.head);
26
+
27
+ // There might be multiple head blocks in our app, and they could have been
28
+ // rendered in an arbitrary order — find one corresponding to this component
29
+ while (
30
+ head_anchor !== null &&
31
+ (head_anchor.nodeType !== COMMENT_NODE || /** @type {Comment} */ (head_anchor).data !== hash)
32
+ ) {
33
+ head_anchor = get_next_sibling(head_anchor);
34
+ }
35
+
36
+ // If we can't find an opening hydration marker, skip hydration (this can happen
37
+ // if a framework rendered body but not head content)
38
+ if (head_anchor === null) {
39
+ set_hydrating(false);
40
+ } else {
41
+ var start = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
42
+ head_anchor.remove(); // in case this component is repeated
43
+
44
+ set_hydrate_node(start);
45
+ }
46
+ }
47
+
48
+ if (!hydrating) {
49
+ anchor = document.head.appendChild(create_text());
50
+ }
12
51
 
13
- render(() => render_fn(anchor), null, HEAD_BLOCK);
52
+ try {
53
+ render(() => render_fn(anchor), null, HEAD_BLOCK);
54
+ } finally {
55
+ if (was_hydrating) {
56
+ set_hydrating(true);
57
+ set_hydrate_node(/** @type {TemplateNode} */ (previous_hydrate_node));
58
+ }
59
+ }
14
60
  }
@@ -1,9 +1,11 @@
1
1
  /** @import { Block } from '#client' */
2
2
 
3
3
  import { remove_block_dom, render } from './blocks.js';
4
- import { get_first_child } from './operations.js';
4
+ import { get_first_child, get_next_sibling } from './operations.js';
5
5
  import { active_block } from './runtime.js';
6
6
  import { assign_nodes, create_fragment_from_html } from './template.js';
7
+ import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
8
+ import { COMMENT_NODE } from '../../../constants.js';
7
9
 
8
10
  /**
9
11
  * Renders dynamic HTML content into the DOM by inserting it before the anchor node.
@@ -13,14 +15,41 @@ import { assign_nodes, create_fragment_from_html } from './template.js';
13
15
  * @returns {void}
14
16
  */
15
17
  export function html(node, get_html, svg = false, mathml = false) {
16
- /** @type {ChildNode} */
17
18
  var anchor = node;
18
- /** @type {string} */
19
19
  var html = '';
20
20
 
21
21
  render(() => {
22
22
  var block = /** @type {Block} */ (active_block);
23
- html = get_html() + '';
23
+ var new_html = get_html() + '';
24
+
25
+ // If the HTML hasn't changed, skip the update (but still hydrate on first run)
26
+ if (html === new_html) {
27
+ // During hydration, we need to skip past the content and end marker even if value hasn't changed
28
+ if (hydrating) {
29
+ // The anchor is the hash comment - we need to skip past it and its content
30
+ set_hydrate_node(anchor);
31
+ var next = hydrate_next();
32
+
33
+ // Walk until we find the empty comment end marker
34
+ while (
35
+ next !== null &&
36
+ (next.nodeType !== COMMENT_NODE || /** @type {Comment} */ (next).data !== '')
37
+ ) {
38
+ next = get_next_sibling(next);
39
+ }
40
+
41
+ // Move past the end marker (if next sibling exists)
42
+ if (next !== null) {
43
+ var next_node = get_next_sibling(next);
44
+ if (next_node !== null) {
45
+ set_hydrate_node(next_node);
46
+ }
47
+ }
48
+ }
49
+ return;
50
+ }
51
+
52
+ html = new_html;
24
53
 
25
54
  if (svg) html = `<svg>${html}</svg>`;
26
55
  else if (mathml) html = `<math>${html}</math>`;
@@ -30,25 +59,57 @@ export function html(node, get_html, svg = false, mathml = false) {
30
59
  block.s.start = block.s.end = null;
31
60
  }
32
61
 
62
+ if (hydrating) {
63
+ set_hydrate_node(anchor);
64
+
65
+ /** @type {Node | null} */
66
+ var next = hydrate_next();
67
+
68
+ // Walk through content nodes until we hit the empty comment end marker
69
+ while (
70
+ next !== null &&
71
+ (next.nodeType !== COMMENT_NODE || /** @type {Comment} */ (next).data !== '')
72
+ ) {
73
+ next = get_next_sibling(next);
74
+ }
75
+
76
+ if (next === null) {
77
+ throw new Error('Hydration mismatch: expected end marker for HTML block');
78
+ }
79
+
80
+ // Include the hash comment and end marker in the assigned nodes
81
+ // This follows Svelte's approach where these markers are part of the effect's managed DOM
82
+ // They will be cleaned up when/if the content updates
83
+ assign_nodes(anchor, next);
84
+
85
+ // Move past the end marker for the next operation
86
+ // For empty HTML or end of container, next sibling might be null - that's okay
87
+ var next_node = get_next_sibling(next);
88
+ if (next_node !== null) {
89
+ set_hydrate_node(next_node);
90
+ }
91
+ return;
92
+ }
93
+
33
94
  if (html === '') return;
34
- /** @type {DocumentFragment | Element} */
35
- var node = create_fragment_from_html(html);
95
+
96
+ var fragment = create_fragment_from_html(html);
36
97
 
37
98
  if (svg || mathml) {
38
- node = /** @type {Element} */ (get_first_child(node));
99
+ fragment = /** @type {DocumentFragment} */ (get_first_child(fragment));
39
100
  }
40
101
 
41
102
  assign_nodes(
42
- /** @type {Element} */ (get_first_child(node)),
43
- /** @type {Element} */ (node.lastChild),
103
+ /** @type {Node} */ (get_first_child(fragment)),
104
+ /** @type {Node} */ (fragment.lastChild),
44
105
  );
45
106
 
46
107
  if (svg || mathml) {
47
- while (get_first_child(node)) {
48
- anchor.before(/** @type {Element} */ (get_first_child(node)));
108
+ while (get_first_child(fragment)) {
109
+ anchor.before(/** @type {Node} */ (get_first_child(fragment)));
49
110
  }
50
111
  } else {
51
- anchor.before(node);
112
+ anchor.before(fragment);
52
113
  }
53
114
  });
54
115
  }
@@ -28,6 +28,18 @@ export function hydrate_next() {
28
28
  return set_hydrate_node(get_next_sibling(/** @type {Node} */ (hydrate_node)));
29
29
  }
30
30
 
31
+ export function next(n = 1) {
32
+ if (hydrating) {
33
+ var node = hydrate_node;
34
+
35
+ for (var i = 0; i < n; i++) {
36
+ node = get_next_sibling(/** @type {Node} */ (node));
37
+ }
38
+
39
+ hydrate_node = node;
40
+ }
41
+ }
42
+
31
43
  /** @param {Node} node */
32
44
  export function pop(node) {
33
45
  if (!hydrating) return;
@@ -88,4 +88,4 @@ export { tsx_compat } from './compat.js';
88
88
 
89
89
  export { TRY_BLOCK } from './constants.js';
90
90
 
91
- export { pop } from './hydration.js';
91
+ export { pop, next } from './hydration.js';
@@ -5,6 +5,13 @@ import { UNINITIALIZED } from './constants.js';
5
5
  import { handle_root_events } from './events.js';
6
6
  import { create_text } from './operations.js';
7
7
  import { active_block } from './runtime.js';
8
+ import {
9
+ hydrating,
10
+ hydrate_next,
11
+ hydrate_node,
12
+ set_hydrating,
13
+ set_hydrate_node,
14
+ } from './hydration.js';
8
15
 
9
16
  /**
10
17
  * @param {any} _
@@ -12,6 +19,11 @@ import { active_block } from './runtime.js';
12
19
  * @returns {void}
13
20
  */
14
21
  export function Portal(_, props) {
22
+ // Portals are client-only and don't participate in hydration
23
+ // The compiler-generated code already handles getting the node via sibling()
24
+ var was_hydrating = hydrating;
25
+ var previous_hydrate_node = hydrate_node;
26
+
15
27
  /** @type {Element | symbol} */
16
28
  let target = UNINITIALIZED;
17
29
  /** @type {((anchor: Node, props: {}, block: Block) => void) | symbol} */
@@ -25,42 +37,55 @@ export function Portal(_, props) {
25
37
  /** @type {Node | null} */
26
38
  var dom_end = null;
27
39
 
28
- render(() => {
29
- if (target === (target = props.target)) return;
30
- if (children === (children = props.children)) return;
40
+ // Temporarily disable hydration for portal content
41
+ if (was_hydrating) {
42
+ set_hydrating(false);
43
+ }
31
44
 
32
- if (b !== null) {
33
- destroy_block(b);
34
- }
45
+ try {
46
+ render(() => {
47
+ if (target === (target = props.target)) return;
48
+ if (children === (children = props.children)) return;
35
49
 
36
- if (anchor !== null) {
37
- anchor.remove();
38
- }
50
+ if (b !== null) {
51
+ destroy_block(b);
52
+ }
39
53
 
40
- dom_start = dom_end = null;
54
+ if (anchor !== null) {
55
+ anchor.remove();
56
+ }
41
57
 
42
- anchor = create_text();
43
- /** @type {Element} */ (target).append(anchor);
58
+ dom_start = dom_end = null;
44
59
 
45
- const cleanup_events = handle_root_events(/** @type {Element} */ (target));
60
+ anchor = create_text();
61
+ /** @type {Element} */ (target).append(anchor);
46
62
 
47
- var block = /** @type {Block} */ (active_block);
63
+ const cleanup_events = handle_root_events(/** @type {Element} */ (target));
48
64
 
49
- b = branch(() => {
50
- if (typeof children === 'function') {
51
- children(/** @type {Text} */ (anchor), {}, block);
52
- }
53
- });
65
+ var block = /** @type {Block} */ (active_block);
54
66
 
55
- dom_start = b?.s?.start;
56
- dom_end = b?.s?.end;
67
+ b = branch(() => {
68
+ if (typeof children === 'function') {
69
+ children(/** @type {Text} */ (anchor), {}, block);
70
+ }
71
+ });
57
72
 
58
- return () => {
59
- cleanup_events();
60
- /** @type {Text} */ (anchor).remove();
61
- if (dom_start && dom_end) {
62
- remove_block_dom(dom_start, dom_end);
63
- }
64
- };
65
- });
73
+ dom_start = b?.s?.start;
74
+ dom_end = b?.s?.end;
75
+
76
+ return () => {
77
+ cleanup_events();
78
+ /** @type {Text} */ (anchor).remove();
79
+ if (dom_start && dom_end) {
80
+ remove_block_dom(dom_start, dom_end);
81
+ }
82
+ };
83
+ });
84
+ } finally {
85
+ // Restore hydration state
86
+ if (was_hydrating) {
87
+ set_hydrating(true);
88
+ set_hydrate_node(/** @type {any} */ (previous_hydrate_node));
89
+ }
90
+ }
66
91
  }
@@ -22,7 +22,9 @@ export async function rpc(hash, args) {
22
22
  }
23
23
 
24
24
  if (data === '') {
25
- throw new Error('The server function end-point did not return a response. Are you running a Ripple server?');
25
+ throw new Error(
26
+ 'The server function end-point did not return a response. Are you running a Ripple server?',
27
+ );
26
28
  }
27
29
 
28
30
  return devalue.parse(data).value;
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { branch, destroy_block, render } from './blocks.js';
4
4
  import { SWITCH_BLOCK } from './constants.js';
5
+ import { hydrate_next, hydrating } from './hydration.js';
5
6
  import { next_sibling } from './operations.js';
6
7
  import { append } from './template.js';
7
8
 
@@ -34,6 +35,10 @@ function move(block, anchor) {
34
35
  * @returns {void}
35
36
  */
36
37
  export function switch_block(anchor, fn) {
38
+ if (hydrating) {
39
+ hydrate_next();
40
+ }
41
+
37
42
  /** @type {((anchor: ChildNode) => void)[]} */
38
43
  var prev = [];
39
44
  /** @type {Map<(anchor: ChildNode) => void, Block>} */