diff --git a/.changeset/slick-bars-train.md b/.changeset/slick-bars-train.md new file mode 100644 index 0000000000..795f8d806f --- /dev/null +++ b/.changeset/slick-bars-train.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: simplify scheduling logic diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 09a3ec5ca4..d6430547b5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -45,7 +45,14 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { var branches = new BranchManager(node); block(() => { + var batch = /** @type {Batch} */ (current_batch); + + // we null out `current_batch` because otherwise `save(...)` will incorrectly restore it — + // the batch will already have been committed by the time it resolves + batch.deactivate(); var input = get_input(); + batch.activate(); + var destroyed = false; /** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 052736c35e..b38a3131ca 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -35,7 +35,7 @@ import { queue_micro_task } from '../task.js'; import * as e from '../../errors.js'; import * as w from '../../warnings.js'; import { DEV } from 'esm-env'; -import { Batch, schedule_effect } from '../../reactivity/batch.js'; +import { Batch, current_batch, schedule_effect } 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'; @@ -218,6 +218,8 @@ export class Boundary { this.is_pending = true; this.#pending_effect = branch(() => pending(this.#anchor)); + var batch = /** @type {Batch} */ (current_batch); + queue_micro_task(() => { var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); var anchor = create_text(); @@ -236,12 +238,14 @@ export class Boundary { this.#pending_effect = null; }); - this.#resolve(); + this.#resolve(batch); } }); } #render() { + var batch = /** @type {Batch} */ (current_batch); + try { this.is_pending = this.has_pending_snippet(); this.#pending_count = 0; @@ -258,14 +262,17 @@ export class Boundary { const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); this.#pending_effect = branch(() => pending(this.#anchor)); } else { - this.#resolve(); + this.#resolve(batch); } } catch (error) { this.error(error); } } - #resolve() { + /** + * @param {Batch} batch + */ + #resolve(batch) { this.is_pending = false; // any effects that were previously deferred should be rescheduled — @@ -273,12 +280,12 @@ export class Boundary { // 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); + batch.schedule(e); } for (const e of this.#maybe_dirty_effects) { set_signal_status(e, MAYBE_DIRTY); - schedule_effect(e); + batch.schedule(e); } this.#dirty_effects.clear(); @@ -335,11 +342,12 @@ export class Boundary { * Updates the pending count associated with the currently visible pending snippet, * if any, such that we can replace the snippet with content once work is done * @param {1 | -1} d + * @param {Batch} batch */ - #update_pending_count(d) { + #update_pending_count(d, batch) { if (!this.has_pending_snippet()) { if (this.parent) { - this.parent.#update_pending_count(d); + this.parent.#update_pending_count(d, batch); } // if there's no parent, we're in a scope with no pending snippet @@ -349,7 +357,7 @@ export class Boundary { this.#pending_count += d; if (this.#pending_count === 0) { - this.#resolve(); + this.#resolve(batch); if (this.#pending_effect) { pause_effect(this.#pending_effect, () => { @@ -369,9 +377,10 @@ export class Boundary { * and controls when the current `pending` snippet (if any) is removed. * Do not call from inside the class * @param {1 | -1} d + * @param {Batch} batch */ - update_pending_count(d) { - this.#update_pending_count(d); + update_pending_count(d, batch) { + this.#update_pending_count(d, batch); this.#local_pending_count += d; diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index 23ad6f5cdc..55e61c3774 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -9,6 +9,7 @@ import { hydrating } from '../../hydration.js'; import { tick, untrack } from '../../../runtime.js'; import { is_runes } from '../../../context.js'; import { current_batch, previous_batch } from '../../../reactivity/batch.js'; +import { async_mode_flag } from '../../../../flags/index.js'; /** * @param {HTMLInputElement} input @@ -87,8 +88,9 @@ export function bind_value(input, get, set = get) { var value = get(); if (input === document.activeElement) { - // we need both, because in non-async mode, render effects run before previous_batch is set - var batch = /** @type {Batch} */ (previous_batch ?? current_batch); + // In sync mode render effects are executed during tree traversal -> needs current_batch + // In async mode render effects are flushed once batch resolved, at which point current_batch is null -> needs previous_batch + var batch = /** @type {Batch} */ (async_mode_flag ? previous_batch : current_batch); // Never rewrite the contents of a focused input. We can get here if, for example, // an update is deferred because of async work depending on the input: diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/select.js b/packages/svelte/src/internal/client/dom/elements/bindings/select.js index 46e8f524f8..21be75ba61 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/select.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/select.js @@ -4,6 +4,7 @@ import { is } from '../../../proxy.js'; import { is_array } from '../../../../shared/utils.js'; import * as w from '../../../warnings.js'; import { Batch, current_batch, previous_batch } from '../../../reactivity/batch.js'; +import { async_mode_flag } from '../../../../flags/index.js'; /** * Selects the correct option(s) (depending on whether this is a multiple select) @@ -115,8 +116,9 @@ export function bind_select_value(select, get, set = get) { var value = get(); if (select === document.activeElement) { - // we need both, because in non-async mode, render effects run before previous_batch is set - var batch = /** @type {Batch} */ (previous_batch ?? current_batch); + // In sync mode render effects are executed during tree traversal -> needs current_batch + // In async mode render effects are flushed once batch resolved, at which point current_batch is null -> needs previous_batch + var batch = /** @type {Batch} */ (async_mode_flag ? previous_batch : current_batch); // Don't update the