From fe12ffc2a3ebfb4fcf43f283fccb984363c1ea84 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 25 Jan 2026 07:16:45 -0800 Subject: [PATCH] fix: avoid unnecessary block effect re-runs after async work completes (#17535) * add samples for async readable freeze * test updating without changing reference too * fix: treat block effects as maybe dirty in revive() * even simpler - put block effects in maybe_dirty_effects --------- Co-authored-by: Rich Harris --- .changeset/wise-emus-hunt.md | 5 +++ .../src/internal/client/reactivity/batch.js | 2 +- .../async-await-store-mutate/_config.js | 24 ++++++++++++++ .../async-await-store-mutate/main.svelte | 26 +++++++++++++++ .../async-each-await-store-update/_config.js | 32 +++++++++++++++++++ .../async-each-await-store-update/main.svelte | 31 ++++++++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 .changeset/wise-emus-hunt.md create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/main.svelte diff --git a/.changeset/wise-emus-hunt.md b/.changeset/wise-emus-hunt.md new file mode 100644 index 0000000000..c8b3f93de8 --- /dev/null +++ b/.changeset/wise-emus-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid unnecessary block effect re-runs after async work completes diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c4caf8a20b..5647c08ba5 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -243,7 +243,7 @@ export class Batch { } else if (async_mode_flag && (flags & (RENDER_EFFECT | MANAGED_EFFECT)) !== 0) { render_effects.push(effect); } else if (is_dirty(effect)) { - if ((flags & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect); + if ((flags & BLOCK_EFFECT) !== 0) this.#maybe_dirty_effects.add(effect); update_effect(effect); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/_config.js b/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/_config.js new file mode 100644 index 0000000000..e89065865f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/_config.js @@ -0,0 +1,24 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +// ensure in-place object mutations stay reactive in async +export default test({ + skip_no_async: true, + async test({ assert, target }) { + const button = /** @type {HTMLElement} */ (target.querySelector('button')); + + await tick(); + + assert.htmlEqual(target.innerHTML, `

count: 1, computed: 10

`); + + button.click(); + await tick(); + + assert.htmlEqual(target.innerHTML, `

count: 2, computed: 20

`); + + button.click(); + await tick(); + + assert.htmlEqual(target.innerHTML, `

count: 3, computed: 30

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/main.svelte new file mode 100644 index 0000000000..327c775d6e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-await-store-mutate/main.svelte @@ -0,0 +1,26 @@ + + + +

count: {$data.count}, computed: {await compute($data)}

+ + {#snippet pending()} +

pending

+ {/snippet} +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/_config.js b/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/_config.js new file mode 100644 index 0000000000..165577af96 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/_config.js @@ -0,0 +1,32 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip_no_async: true, + async test({ assert, target }) { + const button = /** @type {HTMLElement} */ (target.querySelector('button')); + + await tick(); + + assert.htmlEqual( + target.innerHTML, + `` + ); + + button.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + `` + ); + + button.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + `` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/main.svelte new file mode 100644 index 0000000000..41982b767d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-await-store-update/main.svelte @@ -0,0 +1,31 @@ + + + +
    + {#each $items as item (item.id)} +
  • + {#each await query(item) as value} + {value} + {/each} +
  • + {/each} +
+ + {#snippet pending()} +

pending

+ {/snippet} +
+ +