From 4fa2be67ee368b37fd4653c64062b0ec9a1feb5f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 14 Oct 2025 20:00:11 -0400 Subject: [PATCH] decouple from boundaries --- .../client/visitors/CallExpression.js | 2 +- .../internal/client/dom/blocks/boundary.js | 28 ++--------- packages/svelte/src/internal/client/index.js | 2 +- .../src/internal/client/reactivity/batch.js | 47 ++++++++++++++++++- .../svelte/src/internal/client/runtime.js | 13 +---- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index c64a0d8acf..bf9a09bb74 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -51,7 +51,7 @@ export function CallExpression(node, context) { case '$state.eager': return b.call( - '$.pending', + '$.eager', b.thunk(/** @type {Expression} */ (context.visit(node.arguments[0]))) ); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index d03732acd2..ca6437e384 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -13,10 +13,8 @@ import { active_effect, active_reaction, get, - read_pending, set_active_effect, - set_active_reaction, - set_read_pending + set_active_reaction } from '../../runtime.js'; import { hydrate_next, @@ -449,10 +447,7 @@ export function get_boundary() { return /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b); } -/** - * @param {() => any} [fn] - */ -export function pending(fn) { +export function pending() { if (active_effect === null) { e.effect_pending_outside_reaction(); } @@ -460,23 +455,8 @@ export function pending(fn) { var boundary = active_effect.b; if (boundary === null) { - return fn ? fn() : 0; // TODO eventually we will need this to be global + return 0; // TODO eventually we will need this to be global } - var pending = boundary.get_effect_pending(); - - if (fn) { - var value; - var prev_read_pending = read_pending; - set_read_pending(true); - try { - value = fn(); - } finally { - set_read_pending(prev_read_pending); - } - - return value; - } else { - return pending; - } + return boundary.get_effect_pending(); } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 3c5409bcfe..471eed299d 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -103,7 +103,7 @@ export { save, track_reactivity_loss } from './reactivity/async.js'; -export { flushSync as flush } from './reactivity/batch.js'; +export { eager, flushSync as flush } from './reactivity/batch.js'; export { async_derived, user_derived as derived, diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 2956e7ed6a..c5ffea0ea2 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -17,6 +17,7 @@ import { async_mode_flag } from '../../flags/index.js'; import { deferred, define_property } from '../../shared/utils.js'; import { active_effect, + get, is_dirty, is_updating_effect, set_is_updating_effect, @@ -27,8 +28,8 @@ import * as e from '../errors.js'; import { flush_tasks, queue_micro_task } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; -import { old_values } from './sources.js'; -import { unlink_effect } from './effects.js'; +import { old_values, source, update } from './sources.js'; +import { inspect_effect, unlink_effect } from './effects.js'; /** @type {Set} */ const batches = new Set(); @@ -702,6 +703,48 @@ export function schedule_effect(signal) { queued_root_effects.push(effect); } +/** @type {Source} */ +let version; + +let eager_flushing = false; + +function eager_flush() { + try { + flushSync(() => { + update(version); + }); + } finally { + eager_flushing = false; + } +} + +/** + * @template T + * @param {() => T} fn + * @returns {T} + */ +export function eager(fn) { + const previous_batch_values = batch_values; + + try { + get((version ??= source(0))); + + inspect_effect(() => { + fn(); + + if (!eager_flushing) { + eager_flushing = true; + queueMicrotask(eager_flush); + } + }); + + batch_values = null; + return fn(); + } finally { + batch_values = previous_batch_values; + } +} + /** * Forcibly remove all current batches, to prevent cross-talk between tests */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 38f63f2f74..9eaa5cdd01 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -144,17 +144,6 @@ export function increment_write_version() { return ++write_version; } -/** - * Whether or not we should get the latest value of a signal regardless of whether or not it is pending, - * i.e. inside a boundary with pending async work in which case normally a stale value might be shown. - */ -export let read_pending = false; - -/** @param {boolean} value */ -export function set_read_pending(value) { - read_pending = value; -} - /** * Determines whether a derived or effect is dirty. * If it is MAYBE_DIRTY, will set the status to CLEAN @@ -691,7 +680,7 @@ export function get(signal) { } } - if (!read_pending && batch_values?.has(signal)) { + if (batch_values?.has(signal)) { return batch_values.get(signal); }