From ff803ccaa93a4b833d7e8a1964790791c64e7472 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 17 Feb 2026 13:58:24 -0500 Subject: [PATCH] WIP --- .../internal/client/dom/blocks/boundary.js | 70 +++++++++++++------ .../src/internal/client/reactivity/batch.js | 9 +++ 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 08cc994494..048b5072d2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,6 +1,8 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ import { BOUNDARY_EFFECT, + BRANCH_EFFECT, + CLEAN, COMMENT_NODE, DIRTY, EFFECT_PRESERVED, @@ -202,7 +204,7 @@ export class Boundary { this.#pending_effect = null; }); - this.is_pending = false; + this.#resolve(); } }); } @@ -224,13 +226,36 @@ export class Boundary { const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); this.#pending_effect = branch(() => pending(this.#anchor)); } else { - this.is_pending = false; + this.#resolve(); } } catch (error) { this.error(error); } } + #resolve() { + this.is_pending = false; + + reset_branch(/** @type {Effect} */ (this.#main_effect)); + + // any effects that were encountered and deferred during traversal + // should be rescheduled — after the next traversal (which will happen + // immediately, due to the same update that brought us here) + // the effects will be flushed + for (const e of this.#dirty_effects) { + set_signal_status(e, DIRTY); + schedule_effect(e); + } + + for (const e of this.#maybe_dirty_effects) { + set_signal_status(e, MAYBE_DIRTY); + schedule_effect(e); + } + + this.#dirty_effects.clear(); + this.#maybe_dirty_effects.clear(); + } + /** * Defer an effect inside a pending boundary until the boundary resolves * @param {Effect} effect @@ -294,24 +319,7 @@ export class Boundary { this.#pending_count += d; if (this.#pending_count === 0) { - this.is_pending = false; - - // any effects that were encountered and deferred during traversal - // should be rescheduled — after the next traversal (which will happen - // immediately, due to the same update that brought us here) - // the effects will be flushed - for (const e of this.#dirty_effects) { - set_signal_status(e, DIRTY); - schedule_effect(e); - } - - for (const e of this.#maybe_dirty_effects) { - set_signal_status(e, MAYBE_DIRTY); - schedule_effect(e); - } - - this.#dirty_effects.clear(); - this.#maybe_dirty_effects.clear(); + this.#resolve(); if (this.#pending_effect) { pause_effect(this.#pending_effect, () => { @@ -465,3 +473,25 @@ export function pending() { return boundary.get_effect_pending(); } + +/** + * TODO remove this - when we fully switch to a lazy scheduling approach, + * we won't have dirtied the branches in the first place. But for now + * it is necessary to keep the tests passing + * @param {Effect} effect + * @deprecated + */ +function reset_branch(effect) { + // clean branch = nothing dirty inside, no need to traverse further + if ((effect.f & BRANCH_EFFECT) !== 0 && (effect.f & CLEAN) !== 0) { + return; + } + + set_signal_status(effect, CLEAN); + + var e = effect.first; + while (e !== null) { + reset_branch(e); + e = e.next; + } +} diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index b5a2651b2a..648059f5bf 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -859,6 +859,15 @@ export function schedule_effect(signal) { if ((flags & CLEAN) === 0) return; effect.f ^= CLEAN; } + + if ((flags & BOUNDARY_EFFECT) !== 0) { + var boundary = /** @type {Boundary} */ (effect.b); + + if (boundary.is_pending && ((signal.f & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0)) { + boundary.defer_effect(signal); + return; + } + } } queued_root_effects.push(effect);