diff --git a/.changeset/tiny-owls-pay.md b/.changeset/tiny-owls-pay.md new file mode 100644 index 0000000000..ac25500258 --- /dev/null +++ b/.changeset/tiny-owls-pay.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: exit resolved async blocks on correct node when hydrating diff --git a/packages/svelte/src/internal/client/dom/blocks/async.js b/packages/svelte/src/internal/client/dom/blocks/async.js index 0e3ab33dda..e8c9cf0643 100644 --- a/packages/svelte/src/internal/client/dom/blocks/async.js +++ b/packages/svelte/src/internal/client/dom/blocks/async.js @@ -20,13 +20,27 @@ import { get_boundary } from './boundary.js'; */ export function async(node, blockers = [], expressions = [], fn) { var was_hydrating = hydrating; + var end = null; if (was_hydrating) { hydrate_next(); + end = skip_nodes(false); } if (expressions.length === 0 && blockers.every((b) => b.settled)) { fn(node); + + // This is necessary because it is not guaranteed that the render function will + // advance the hydration node to $.async's end marker: it may stop at an inner + // block's end marker (in case of an inner if block for example), but it also may + // stop at the correct $.async end marker (in case of component child) - hence + // we can't just use hydrate_next() + // TODO this feels indicative of a bug elsewhere; ideally we wouldn't need + // to double-traverse in the already-resolved case + if (was_hydrating) { + set_hydrate_node(end); + } + return; } @@ -39,7 +53,6 @@ export function async(node, blockers = [], expressions = [], fn) { if (was_hydrating) { var previous_hydrate_node = hydrate_node; - var end = skip_nodes(false); set_hydrate_node(end); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-if-hydration/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/Child.svelte new file mode 100644 index 0000000000..02ef294d99 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/Child.svelte @@ -0,0 +1,5 @@ + + +{b} diff --git a/packages/svelte/tests/runtime-runes/samples/async-if-hydration/_config.js b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/_config.js new file mode 100644 index 0000000000..8132e9c522 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/_config.js @@ -0,0 +1,11 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['hydrate'], + + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, `

hello

true
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-if-hydration/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/main.svelte new file mode 100644 index 0000000000..3b08d41640 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-if-hydration/main.svelte @@ -0,0 +1,18 @@ + + +{#if a} +
+ {#if b} +

hello

+ {/if} +
+
+ +
+{/if}