From 6e5f2b157a02df4dd1f4e2ad3275655bc52cd023 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Feb 2026 10:54:15 -0500 Subject: [PATCH] fix: exit resolved async blocks on correct node when hydrating (#17640) * fix: exit resolved async blocks on correct node when hydrating * expand test + fix * tweak, add note to self --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- .changeset/tiny-owls-pay.md | 5 +++++ .../src/internal/client/dom/blocks/async.js | 15 ++++++++++++++- .../samples/async-if-hydration/Child.svelte | 5 +++++ .../samples/async-if-hydration/_config.js | 11 +++++++++++ .../samples/async-if-hydration/main.svelte | 18 ++++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/tiny-owls-pay.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-if-hydration/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-if-hydration/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-if-hydration/main.svelte 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}