From 964004a1b0816294d5e864067ea1bf38ec4085a5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 15 Jan 2025 16:17:16 -0500 Subject: [PATCH] preserve context --- .../client/visitors/AwaitExpression.js | 13 +++-- .../svelte/src/internal/client/constants.js | 2 + .../internal/client/dom/blocks/boundary.js | 29 ++++++----- .../svelte/src/internal/client/runtime.js | 51 ++++++++++++++----- playgrounds/sandbox/vite.config.js | 2 +- 5 files changed, 65 insertions(+), 32 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js index 8d819b7ed2..809a7b43f8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js @@ -7,10 +7,15 @@ import * as b from '../../../../utils/builders.js'; * @param {ComponentContext} context */ export function AwaitExpression(node, context) { - return b.await( - b.call( - '$.preserve_context', - node.argument && /** @type {Expression} */ (context.visit(node.argument)) + return b.call( + b.member( + b.await( + b.call( + '$.preserve_context', + node.argument && /** @type {Expression} */ (context.visit(node.argument)) + ) + ), + 'read' ) ); } diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index a4840ce4eb..e7034a332d 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -21,6 +21,8 @@ export const INSPECT_EFFECT = 1 << 18; export const HEAD_EFFECT = 1 << 19; export const EFFECT_HAS_DERIVED = 1 << 20; +export const REACTION_IS_UPDATING = 1 << 21; + export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); export const LEGACY_PROPS = Symbol('legacy props'); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 38f9503878..ccfdfc9067 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -261,26 +261,27 @@ export function create_suspense() { /** * @template T * @param {Promise} promise - * @returns {Promise} + * @returns {Promise<{ read: () => T }>} */ export async function preserve_context(promise) { - if (!active_effect) { - return promise; - } - var previous_effect = active_effect; var previous_reaction = active_reaction; var previous_component_context = component_context; const [suspend, unsuspend] = create_suspense(); - try { - suspend(); - return await promise; - } finally { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - set_component_context(previous_component_context); - unsuspend(); - } + suspend(); + + const value = await promise; + + return { + read() { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + set_component_context(previous_component_context); + + unsuspend(); + return value; + } + }; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 55a8ccf32d..508cfd4da7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -25,7 +25,8 @@ import { ROOT_EFFECT, LEGACY_DERIVED_PROP, DISCONNECTED, - BOUNDARY_EFFECT + BOUNDARY_EFFECT, + REACTION_IS_UPDATING } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; @@ -435,6 +436,7 @@ export function update_reaction(reaction) { read_version++; try { + reaction.f |= REACTION_IS_UPDATING; var result = /** @type {Function} */ (0, reaction.fn)(); var deps = reaction.deps; @@ -488,6 +490,7 @@ export function update_reaction(reaction) { return result; } finally { + reaction.f ^= REACTION_IS_UPDATING; new_deps = previous_deps; skipped_deps = previous_skipped_deps; untracked_writes = previous_untracked_writes; @@ -776,7 +779,7 @@ export function schedule_effect(signal) { var flags = effect.f; if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) { - if ((flags & CLEAN) === 0) return + if ((flags & CLEAN) === 0) return; effect.f ^= CLEAN; } } @@ -938,18 +941,40 @@ export function get(signal) { if (derived_sources !== null && derived_sources.includes(signal)) { e.state_unsafe_local_read(); } + var deps = active_reaction.deps; - if (signal.rv < read_version) { - signal.rv = read_version; - // If the signal is accessing the same dependencies in the same - // order as it did last time, increment `skipped_deps` - // rather than updating `new_deps`, which creates GC cost - if (new_deps === null && deps !== null && deps[skipped_deps] === signal) { - skipped_deps++; - } else if (new_deps === null) { - new_deps = [signal]; - } else { - new_deps.push(signal); + + if ((active_reaction.f & REACTION_IS_UPDATING) !== 0) { + // we're in the effect init/update cycle + if (signal.rv < read_version) { + signal.rv = read_version; + + // If the signal is accessing the same dependencies in the same + // order as it did last time, increment `skipped_deps` + // rather than updating `new_deps`, which creates GC cost + if (new_deps === null && deps !== null && deps[skipped_deps] === signal) { + skipped_deps++; + } else if (new_deps === null) { + new_deps = [signal]; + } else { + new_deps.push(signal); + } + } + } else { + // we're adding a dependency outside the init/update cycle + // (i.e. after an `await`) + // TODO we probably want to disable this for user effects, + // otherwise it's a breaking change, albeit a desirable one? + if (deps === null) { + deps = [signal]; + } else if (!deps.includes(signal)) { + deps.push(signal); + } + + if (signal.reactions === null) { + signal.reactions = [active_reaction]; + } else if (!signal.reactions.includes(active_reaction)) { + signal.reactions.push(active_reaction); } } } else if (is_derived && /** @type {Derived} */ (signal).deps === null) { diff --git a/playgrounds/sandbox/vite.config.js b/playgrounds/sandbox/vite.config.js index 51bfd0a212..c6c07ce7c6 100644 --- a/playgrounds/sandbox/vite.config.js +++ b/playgrounds/sandbox/vite.config.js @@ -11,7 +11,7 @@ export default defineConfig({ inspect(), svelte({ compilerOptions: { - hmr: true + hmr: false } }) ],