diff --git a/.changeset/eight-walls-mate.md b/.changeset/eight-walls-mate.md new file mode 100644 index 0000000000..a7de4e6278 --- /dev/null +++ b/.changeset/eight-walls-mate.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: simplify reaction/source ownership tracking diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index 7c6b740370..9be1f00104 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -10,7 +10,7 @@ jobs: if: github.repository == 'sveltejs/svelte' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') permissions: issues: write # to add / delete reactions - pull-requests: read # to read PR data + pull-requests: write # to read PR data, and to add labels actions: read # to check workflow status contents: read # to clone the repo steps: diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 97c8da9d33..5da1b7e188 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -1,6 +1,13 @@ /** @import { Source } from '#client' */ import { DEV } from 'esm-env'; -import { get, active_effect, active_reaction, set_active_reaction } from './runtime.js'; +import { + get, + active_effect, + update_version, + active_reaction, + set_update_version, + set_active_reaction +} from './runtime.js'; import { array_prototype, get_descriptor, @@ -41,7 +48,7 @@ export function proxy(value) { var version = source(0); var stack = DEV && tracing_mode_flag ? get_stack('CreatedAt') : null; - var reaction = active_reaction; + var parent_version = update_version; /** * Executes the proxy in the context of the reaction it was originally created in, if any @@ -49,13 +56,23 @@ export function proxy(value) { * @param {() => T} fn */ var with_parent = (fn) => { - var previous_reaction = active_reaction; - set_active_reaction(reaction); + if (update_version === parent_version) { + return fn(); + } + + // child source is being created after the initial proxy — + // prevent it from being associated with the current reaction + var reaction = active_reaction; + var version = update_version; + + set_active_reaction(null); + set_update_version(parent_version); - /** @type {T} */ var result = fn(); - set_active_reaction(previous_reaction); + set_active_reaction(reaction); + set_update_version(version); + return result; }; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 4185fa1954..522af2675c 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -10,7 +10,7 @@ import { untrack, increment_write_version, update_effect, - source_ownership, + current_sources, is_dirty, untracking, is_destroying_effect, @@ -141,7 +141,7 @@ export function set(source, value, should_proxy = false) { (!untracking || (active_reaction.f & INSPECT_EFFECT) !== 0) && is_runes() && (active_reaction.f & (DERIVED | BLOCK_EFFECT | EFFECT_ASYNC | INSPECT_EFFECT)) !== 0 && - !(source_ownership?.reaction === active_reaction && source_ownership.sources.includes(source)) + !current_sources?.includes(source) ) { e.state_unsafe_mutation(); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index d62df4fb5b..8bdd2061e3 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -87,17 +87,17 @@ export function set_active_effect(effect) { /** * When sources are created within a reaction, reading and writing * them within that reaction should not cause a re-run - * @type {null | { reaction: Reaction, sources: Source[] }} + * @type {null | Source[]} */ -export let source_ownership = null; +export let current_sources = null; /** @param {Value} value */ export function push_reaction_value(value) { if (active_reaction !== null && (!async_mode_flag || (active_reaction.f & DERIVED) !== 0)) { - if (source_ownership === null) { - source_ownership = { reaction: active_reaction, sources: [value] }; + if (current_sources === null) { + current_sources = [value]; } else { - source_ownership.sources.push(value); + current_sources.push(value); } } } @@ -135,6 +135,11 @@ let read_version = 0; export let update_version = read_version; +/** @param {number} value */ +export function set_update_version(value) { + update_version = value; +} + // If we are working with a get() chain that has no active container, // to prevent memory leaks, we skip adding the reaction. export let skip_reaction = false; @@ -239,11 +244,7 @@ function schedule_possible_effect_self_invalidation(signal, effect, root = true) var reactions = signal.reactions; if (reactions === null) return; - if ( - !async_mode_flag && - source_ownership?.reaction === active_reaction && - source_ownership.sources.includes(signal) - ) { + if (!async_mode_flag && current_sources?.includes(signal)) { return; } @@ -270,7 +271,7 @@ export function update_reaction(reaction) { var previous_untracked_writes = untracked_writes; var previous_reaction = active_reaction; var previous_skip_reaction = skip_reaction; - var previous_reaction_sources = source_ownership; + var previous_sources = current_sources; var previous_component_context = component_context; var previous_untracking = untracking; var previous_update_version = update_version; @@ -284,7 +285,7 @@ export function update_reaction(reaction) { (flags & UNOWNED) !== 0 && (untracking || !is_updating_effect || active_reaction === null); active_reaction = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null; - source_ownership = null; + current_sources = null; set_component_context(reaction.ctx); untracking = false; update_version = ++read_version; @@ -376,7 +377,7 @@ export function update_reaction(reaction) { untracked_writes = previous_untracked_writes; active_reaction = previous_reaction; skip_reaction = previous_skip_reaction; - source_ownership = previous_reaction_sources; + current_sources = previous_sources; set_component_context(previous_component_context); untracking = previous_untracking; update_version = previous_update_version; @@ -550,10 +551,7 @@ export function get(signal) { // we don't add the dependency, because that would create a memory leak var destroyed = active_effect !== null && (active_effect.f & DESTROYED) !== 0; - var is_owned_by_reaction = - source_ownership?.reaction === active_reaction && source_ownership.sources.includes(signal); - - if (!destroyed && !is_owned_by_reaction) { + if (!destroyed && !current_sources?.includes(signal)) { var deps = active_reaction.deps; if ((active_reaction.f & REACTION_IS_UPDATING) !== 0) {