diff --git a/.changeset/honest-rabbits-glow.md b/.changeset/honest-rabbits-glow.md new file mode 100644 index 000000000000..92494aeb0900 --- /dev/null +++ b/.changeset/honest-rabbits-glow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: cleanup `each` items in legacy mode diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 99bdc0000cc7..d3dce2065927 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -2,7 +2,12 @@ import { DEV } from 'esm-env'; import { is_promise } from '../../../shared/utils.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; -import { internal_set, mutable_source, source } from '../../reactivity/sources.js'; +import { + internal_set, + mutable_source, + remove_from_legacy_sources, + source +} from '../../reactivity/sources.js'; import { flushSync, set_active_effect, set_active_reaction } from '../../runtime.js'; import { hydrate_next, @@ -62,6 +67,11 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { var error_source = (runes ? source : mutable_source)(undefined); var resolved = false; + function clean_sources() { + remove_from_legacy_sources(input_source); + remove_from_legacy_sources(error_source); + } + /** * @param {PENDING | THEN | CATCH} state * @param {boolean} restore @@ -79,17 +89,29 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) { try { if (state === PENDING && pending_fn) { if (pending_effect) resume_effect(pending_effect); - else pending_effect = branch(() => pending_fn(anchor)); + else + pending_effect = branch(() => { + pending_fn(anchor); + return clean_sources; + }); } if (state === THEN && then_fn) { if (then_effect) resume_effect(then_effect); - else then_effect = branch(() => then_fn(anchor, input_source)); + else + then_effect = branch(() => { + then_fn(anchor, input_source); + return clean_sources; + }); } if (state === CATCH && catch_fn) { if (catch_effect) resume_effect(catch_effect); - else catch_effect = branch(() => catch_fn(anchor, error_source)); + else + catch_effect = branch(() => { + catch_fn(anchor, error_source); + return clean_sources; + }); } if (state !== PENDING && pending_effect) { diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 954dcb221461..4c3971e90370 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -32,7 +32,12 @@ import { pause_effect, resume_effect } from '../../reactivity/effects.js'; -import { source, mutable_source, internal_set } from '../../reactivity/sources.js'; +import { + source, + mutable_source, + internal_set, + remove_from_legacy_sources +} from '../../reactivity/sources.js'; import { array_from, is_array } from '../../../shared/utils.js'; import { INERT } from '#client/constants'; import { queue_micro_task } from '../task.js'; @@ -549,7 +554,16 @@ function create_item( current_each_item = item; try { - item.e = branch(() => render_fn(anchor, v, i, get_collection), hydrating); + item.e = branch(() => { + render_fn(anchor, v, i, get_collection); + // we only need to clean this up if the item is reactive and mutable + // because that's when we add the source to the context preventing garbage collection of it + if (reactive && mutable) { + return () => { + remove_from_legacy_sources(item.v); + }; + } + }, hydrating); item.e.prev = prev && prev.e; item.e.next = next && next.e; diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 40a3e4e77f14..13b4a6f112f9 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -112,6 +112,20 @@ export function mutable_source(initial_value, immutable = false) { return s; } +/** + * @param {Source} source + */ +export function remove_from_legacy_sources(source) { + if ( + legacy_mode_flag && + component_context !== null && + component_context.l !== null && + component_context.l.s !== null + ) { + component_context.l.s = component_context.l.s.filter((s) => s !== source); + } +} + /** * @template V * @param {Value} source