diff --git a/.changeset/solid-banks-teach.md b/.changeset/solid-banks-teach.md new file mode 100644 index 0000000000..eb16d051b2 --- /dev/null +++ b/.changeset/solid-banks-teach.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use right batch/branch on first run diff --git a/packages/svelte/src/internal/client/dom/blocks/branches.js b/packages/svelte/src/internal/client/dom/blocks/branches.js index 33c34f58bb..3732bc2de2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/branches.js +++ b/packages/svelte/src/internal/client/dom/blocks/branches.js @@ -109,11 +109,14 @@ export class BranchManager { } for (const [b, k] of this.#batches) { + // Keep values for newer batches. Insertion order is not always chronological: + // an older batch can re-run after a newer one has already registered. + if (b.id > batch.id) continue; + this.#batches.delete(b); if (b === batch) { - // keep values for newer batches - break; + continue; } const offscreen = this.#offscreen.get(k); diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 6aea790c36..549141dbfb 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -55,7 +55,14 @@ export function flatten(blockers, sync, async, fn) { /** @param {Value[]} values */ function finish(values) { - restore(); + var batch = get_latest_async_batch(values); + if (batch) { + restore(false); + batch.activate(); + batch.apply(); + } else { + restore(); + } try { fn(values); @@ -95,6 +102,24 @@ export function flatten(blockers, sync, async, fn) { } } +/** + * @param {Value[]} values + * @returns {Batch | null} + */ +function get_latest_async_batch(values) { + /** @type {Batch | null} */ + var latest = null; + + for (const value of values) { + var batch = /** @type {Value & { async_batch?: Batch }} */ (value).async_batch; + if (batch && (!latest || batch.id > latest.id)) { + latest = batch; + } + } + + return latest; +} + /** * @param {Blocker[]} blockers * @param {(values: Value[]) => any} fn diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 4ae49fecba..b4ee0ce8a0 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -215,6 +215,7 @@ export function async_derived(fn, label, location) { } batch.activate(); + /** @type {Source & { async_batch?: Batch }} */ (signal).async_batch = batch; if (error) { signal.f |= ERROR_VALUE; diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 0ee8570c3d..1f41e1fca6 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -7,6 +7,7 @@ import type { TransitionManager } from '#client'; import type { Boundary } from '../dom/blocks/boundary'; +import type { Batch } from './batch'; export interface Signal { /** Flags bitmask */ @@ -24,6 +25,8 @@ export interface Value extends Signal { rv: number; /** The latest value for this signal */ v: V; + /** The batch in which an async derived most recently resolved */ + async_batch?: Batch; // TODO if this is only set a few times this might mess with perf (object shape etc) // dev-only /** A label (e.g. the `foo` in `let foo = $state(...)`) used for `$inspect.trace()` */ diff --git a/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/_config.js b/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/_config.js new file mode 100644 index 0000000000..c8bc4c986f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/_config.js @@ -0,0 +1,20 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, pop] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + increment.click(); + await tick(); + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + + pop.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ` 2 2 1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/main.svelte new file mode 100644 index 0000000000..7689af049c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-new-batch-during-initial-load/main.svelte @@ -0,0 +1,26 @@ + + + + + +{#if count > 0} + + {await push(count)} {count} {other} + {#snippet failed()}boom{/snippet} + +{/if}