From 12160e0c33c08d4a59361b13de0bbe0ea8a40866 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 16:44:20 -0400 Subject: [PATCH 1/7] WIP sync effect pending updates --- .../src/internal/client/dom/blocks/boundary.js | 15 +++++++++------ .../src/internal/client/reactivity/batch.js | 9 +++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 5ed0515210ec..85f52d956b56 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -22,10 +22,11 @@ import { get_next_sibling } from '../operations.js'; import { queue_micro_task } from '../task.js'; import * as e from '../../errors.js'; import { DEV } from 'esm-env'; -import { Batch } from '../../reactivity/batch.js'; +import { Batch, effect_pending_updates } from '../../reactivity/batch.js'; import { internal_set, source } from '../../reactivity/sources.js'; import { tag } from '../../dev/tracing.js'; import { createSubscriber } from '../../../../reactivity/create-subscriber.js'; +import { raf } from '../../timing.js'; /** * @typedef {{ @@ -92,6 +93,12 @@ export class Boundary { */ #effect_pending = null; + #effect_pending_update = () => { + if (this.#effect_pending) { + internal_set(this.#effect_pending, this.#pending_count); + } + }; + #effect_pending_subscriber = createSubscriber(() => { this.#effect_pending = source(this.#pending_count); @@ -238,11 +245,7 @@ export class Boundary { this.parent.#update_pending_count(d); } - queueMicrotask(() => { - if (this.#effect_pending) { - internal_set(this.#effect_pending, this.#pending_count); - } - }); + effect_pending_updates.add(this.#effect_pending_update); } get_effect_pending() { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 1126946ce9b1..2189535adb38 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -49,6 +49,9 @@ export let batch_deriveds = null; /** @type {Effect[]} Stack of effects, dev only */ export let dev_effect_stack = []; +/** @type {Set<() => void>} */ +export let effect_pending_updates = new Set(); + /** @type {Effect[]} */ let queued_root_effects = []; @@ -320,6 +323,12 @@ export class Batch { } current_batch = null; + + for (const update of effect_pending_updates) { + effect_pending_updates.delete(update); + update(); + break; + } } flush_effects() { From 6a3d785505b0ed759e9806ce1845a86c1f11184e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 17:18:54 -0400 Subject: [PATCH 2/7] fix --- .../src/internal/client/reactivity/batch.js | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 2189535adb38..1f87c73856b7 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -299,6 +299,8 @@ export class Batch { deactivate() { current_batch = null; + + flush_pending_update(); } neuter() { @@ -322,13 +324,7 @@ export class Batch { batches.delete(this); } - current_batch = null; - - for (const update of effect_pending_updates) { - effect_pending_updates.delete(update); - update(); - break; - } + this.deactivate(); } flush_effects() { @@ -429,6 +425,18 @@ export class Batch { } } +function flush_pending_update() { + for (const update of effect_pending_updates) { + effect_pending_updates.delete(update); + update(); + + if (current_batch !== null) { + // only do one at a time + break; + } + } +} + /** * Synchronously flush any pending updates. * Returns void if no callback is provided, otherwise returns the result of calling the callback. From ef8b8523796116a6dac5744a4c837063f3b59a2e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 17:19:20 -0400 Subject: [PATCH 3/7] changeset --- .changeset/popular-tips-lie.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-tips-lie.md diff --git a/.changeset/popular-tips-lie.md b/.changeset/popular-tips-lie.md new file mode 100644 index 000000000000..45bd3e23f358 --- /dev/null +++ b/.changeset/popular-tips-lie.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: update `$effect.pending()` immediately after a batch is removed From 0b2ef3c2a44ccb2d4ae5826efce4a55d96658db5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 18:58:38 -0400 Subject: [PATCH 4/7] fix --- packages/svelte/src/internal/client/reactivity/batch.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 1f87c73856b7..c440b7df0bef 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -394,6 +394,8 @@ export class Batch { this.#effects = []; this.flush(); + } else { + this.deactivate(); } } From 9eece0e4a769cd6579beb732f6b83b727b1dfb18 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 18:59:15 -0400 Subject: [PATCH 5/7] add test --- .../samples/async-effect-pending/_config.js | 81 +++++++++++++++++++ .../samples/async-effect-pending/main.svelte | 32 ++++++++ 2 files changed, 113 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-pending/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-effect-pending/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-pending/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-pending/_config.js new file mode 100644 index 000000000000..9df362079827 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-pending/_config.js @@ -0,0 +1,81 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, shift] = target.querySelectorAll('button'); + + shift.click(); + shift.click(); + shift.click(); + + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

0

+

0

+

0

+

pending: 0

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

0

+

0

+

0

+

pending: 3

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

0

+

0

+

0

+

pending: 2

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

0

+

0

+

0

+

pending: 1

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

1

+

1

+

1

+

pending: 0

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-pending/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-pending/main.svelte new file mode 100644 index 000000000000..89cead2cc6b1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-pending/main.svelte @@ -0,0 +1,32 @@ + + + + + + +

{await push(value)}

+

{await push(value)}

+

{await push(value)}

+ +

pending: {$effect.pending()}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
+ + From 9b89b57558a76bb6d5f580af39b8b7747290ac29 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 19:01:18 -0400 Subject: [PATCH 6/7] inline --- .../src/internal/client/reactivity/batch.js | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c440b7df0bef..f881330e90df 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -300,7 +300,15 @@ export class Batch { deactivate() { current_batch = null; - flush_pending_update(); + for (const update of effect_pending_updates) { + effect_pending_updates.delete(update); + update(); + + if (current_batch !== null) { + // only do one at a time + break; + } + } } neuter() { @@ -427,18 +435,6 @@ export class Batch { } } -function flush_pending_update() { - for (const update of effect_pending_updates) { - effect_pending_updates.delete(update); - update(); - - if (current_batch !== null) { - // only do one at a time - break; - } - } -} - /** * Synchronously flush any pending updates. * Returns void if no callback is provided, otherwise returns the result of calling the callback. From d3f9269e7daaefe4ded8b08b19d43787281168bc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 15 Jul 2025 19:01:58 -0400 Subject: [PATCH 7/7] unused --- packages/svelte/src/internal/client/dom/blocks/boundary.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 85f52d956b56..5e678ab113ce 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -26,7 +26,6 @@ import { Batch, effect_pending_updates } from '../../reactivity/batch.js'; import { internal_set, source } from '../../reactivity/sources.js'; import { tag } from '../../dev/tracing.js'; import { createSubscriber } from '../../../../reactivity/create-subscriber.js'; -import { raf } from '../../timing.js'; /** * @typedef {{