From 9a52007785dd44c978ea56e2766e207e138401d1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 19 Jul 2025 17:37:53 -0400 Subject: [PATCH] fix: prevent batches from getting intertwined (#16446) * ensure batches are kept separate * changeset * activate batch before decrementing --- .changeset/fresh-birds-jam.md | 5 ++++ .../internal/client/dom/blocks/boundary.js | 2 +- .../src/internal/client/reactivity/batch.js | 30 +++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 .changeset/fresh-birds-jam.md diff --git a/.changeset/fresh-birds-jam.md b/.changeset/fresh-birds-jam.md new file mode 100644 index 0000000000..41d62d5366 --- /dev/null +++ b/.changeset/fresh-birds-jam.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent batches from getting intertwined diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 4ea137bfa8..1866931ef2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -148,7 +148,7 @@ export class Boundary { // need to use hydration boundary comments to report whether // the pending or main block was rendered for a given // boundary, and hydrate accordingly - queueMicrotask(() => { + Batch.enqueue(() => { this.#main_effect = this.#run(() => { Batch.ensure(); return branch(() => this.#children(this.#anchor)); diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index ed82af94ed..ec082bb595 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -49,6 +49,19 @@ export let batch_deriveds = null; /** @type {Set<() => void>} */ export let effect_pending_updates = new Set(); +/** @type {Array<() => void>} */ +let tasks = []; + +function dequeue() { + const task = /** @type {() => void} */ (tasks.shift()); + + if (tasks.length > 0) { + queueMicrotask(dequeue); + } + + task(); +} + /** @type {Effect[]} */ let queued_root_effects = []; @@ -438,7 +451,7 @@ export class Batch { batches.add(current_batch); if (autoflush) { - queueMicrotask(() => { + Batch.enqueue(() => { if (current_batch !== batch) { // a flushSync happened in the meantime return; @@ -451,6 +464,15 @@ export class Batch { return current_batch; } + + /** @param {() => void} task */ + static enqueue(task) { + if (tasks.length === 0) { + queueMicrotask(dequeue); + } + + tasks.unshift(task); + } } /** @@ -593,7 +615,11 @@ export function suspend() { return function unsuspend() { boundary.update_pending_count(-1); - if (!pending) batch.decrement(); + + if (!pending) { + batch.activate(); + batch.decrement(); + } unset_context(); };