diff --git a/packages/svelte/src/internal/client/custom-element.js b/packages/svelte/src/internal/client/custom-element.js index 0bd64cdf50..2d5a8fe08c 100644 --- a/packages/svelte/src/internal/client/custom-element.js +++ b/packages/svelte/src/internal/client/custom-element.js @@ -1,6 +1,5 @@ import { createClassComponent } from '../../legacy/legacy-client.js'; -import { destroy_signal } from './runtime.js'; -import { render_effect } from './reactivity/effects.js'; +import { destroy_effect, render_effect } from './reactivity/effects.js'; import { open, close } from './render.js'; import { define_property } from './utils.js'; @@ -199,7 +198,7 @@ if (typeof HTMLElement === 'function') { Promise.resolve().then(() => { if (!this.$$cn) { this.$$c.$destroy(); - destroy_signal(this.$$me); + destroy_effect(this.$$me); this.$$c = undefined; } }); diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index 4f24a4d4ae..72472bf86f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -1,14 +1,8 @@ import { is_promise } from '../../../common.js'; import { hydrate_block_anchor } from '../../hydration.js'; import { remove } from '../../reconciler.js'; -import { - current_block, - destroy_signal, - execute_effect, - flushSync, - push_destroy_fn -} from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { current_block, execute_effect, flushSync, push_destroy_fn } from '../../runtime.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js'; @@ -74,7 +68,7 @@ export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) { remove(render.d); render.d = null; } - destroy_signal(render.e); + destroy_effect(render.e); render.e = null; } } @@ -191,7 +185,7 @@ export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) { } const effect = render.e; if (effect !== null) { - destroy_signal(effect); + destroy_effect(effect); } render = render.p; } diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 93899a016d..595bfd2745 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -16,8 +16,8 @@ import { } from '../../hydration.js'; import { clear_text_content, empty, map_get, map_set } from '../../operations.js'; import { insert, remove } from '../../reconciler.js'; -import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { current_block, execute_effect, push_destroy_fn } from '../../runtime.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; import { source, mutable_source, set } from '../../reactivity/sources.js'; import { trigger_transitions } from '../../transitions.js'; import { is_array, is_frozen } from '../../utils.js'; @@ -133,7 +133,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re remove(fallback.d); fallback.d = null; } - destroy_signal(fallback.e); + destroy_effect(fallback.e); fallback.e = null; } } @@ -273,13 +273,13 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re } const effect = fallback.e; if (effect !== null) { - destroy_signal(effect); + destroy_effect(effect); } fallback = fallback.p; } // Clear the array reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); - destroy_signal(/** @type {import('../../types.js').Effect} */ (render)); + destroy_effect(/** @type {import('#client').Effect} */ (render)); }); block.e = each; @@ -904,7 +904,7 @@ export function destroy_each_item_block( if (!controlled && dom !== null) { remove(dom); } - destroy_signal(/** @type {import('../../types.js').Effect} */ (block.e)); + destroy_effect(/** @type {import('#client').Effect} */ (block.e)); } /** diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 57fad94171..e85d8018e5 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -6,8 +6,8 @@ import { set_current_hydration_fragment } from '../../hydration.js'; import { remove } from '../../reconciler.js'; -import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { current_block, execute_effect, push_destroy_fn } from '../../runtime.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; /** @returns {import('../../types.js').IfBlock} */ @@ -174,8 +174,8 @@ export function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) if (alternate_dom !== null) { remove(alternate_dom); } - destroy_signal(consequent_effect); - destroy_signal(alternate_effect); + destroy_effect(consequent_effect); + destroy_effect(alternate_effect); }); block.e = if_effect; } diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index d4cfd175b7..a44e908003 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -1,8 +1,8 @@ import { UNINITIALIZED, KEY_BLOCK } from '../../constants.js'; import { hydrate_block_anchor } from '../../hydration.js'; import { remove } from '../../reconciler.js'; -import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/effects.js'; +import { current_block, execute_effect, push_destroy_fn } from '../../runtime.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; import { safe_not_equal } from '../../reactivity/equality.js'; @@ -58,7 +58,7 @@ export function key_block(anchor_node, key, render_fn) { remove(render.d); render.d = null; } - destroy_signal(render.e); + destroy_effect(render.e); render.e = null; } } @@ -131,7 +131,7 @@ export function key_block(anchor_node, key, render_fn) { } const effect = render.e; if (effect !== null) { - destroy_signal(effect); + destroy_effect(effect); } render = render.p; } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index ef59b4b940..0a7b49f85c 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -1,8 +1,18 @@ import { DEV } from 'esm-env'; -import { CLEAN, DERIVED, UNINITIALIZED, UNOWNED } from '../constants.js'; -import { current_block, current_consumer, current_effect } from '../runtime.js'; +import { CLEAN, DERIVED, DESTROYED, UNINITIALIZED, UNOWNED } from '../constants.js'; +import { + IS_EFFECT, + current_block, + current_consumer, + current_effect, + destroy_references, + remove_consumers, + set_signal_status +} from '../runtime.js'; import { push_reference } from './effects.js'; import { default_equals, safe_equal } from './equality.js'; +import { is_array } from '../utils.js'; +import { run_all } from '../../common.js'; /** * @template V @@ -52,3 +62,27 @@ export function derived_safe_equal(fn) { signal.e = safe_equal; return signal; } + +/** + * @param {import('./types.js').Derived} signal + * @returns {void} + */ +export function destroy_derived(signal) { + const teardown = /** @type {null | (() => void)} */ (signal.v); + const destroy = signal.y; + const flags = signal.f; + destroy_references(signal); + remove_consumers(signal, 0); + signal.i = signal.r = signal.y = signal.x = signal.b = signal.d = signal.c = null; + set_signal_status(signal, DESTROYED); + if (destroy !== null) { + if (is_array(destroy)) { + run_all(destroy); + } else { + destroy(); + } + } + if (teardown !== null && (flags & IS_EFFECT) !== 0) { + teardown(); + } +} diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 0c9da24f94..969f3aeb9f 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -1,17 +1,22 @@ import { DEV } from 'esm-env'; import { + IS_EFFECT, current_block, current_component_context, current_effect, - destroy_signal, + destroy_references, flush_local_render_effects, get, is_runes, + remove_consumers, schedule_effect, + set_signal_status, untrack } from '../runtime.js'; -import { DIRTY, MANAGED, RENDER_EFFECT, EFFECT, PRE_EFFECT } from '../constants.js'; +import { DIRTY, MANAGED, RENDER_EFFECT, EFFECT, PRE_EFFECT, DESTROYED } from '../constants.js'; import { set } from './sources.js'; +import { is_array } from '../utils.js'; +import { run_all } from '../../common.js'; /** * @param {import('#client').Reaction} target_signal @@ -116,7 +121,7 @@ export function user_effect(fn) { export function user_root_effect(fn) { const effect = render_effect(fn, current_block, true); return () => { - destroy_signal(effect); + destroy_effect(effect); }; } @@ -241,3 +246,27 @@ export function render_effect(fn, block = current_block, managed = false, sync = } return create_effect(flags, /** @type {any} */ (fn), sync, block, true); } + +/** + * @param {import('./types.js').Effect} signal + * @returns {void} + */ +export function destroy_effect(signal) { + const teardown = /** @type {null | (() => void)} */ (signal.v); + const destroy = signal.y; + const flags = signal.f; + destroy_references(signal); + remove_consumers(signal, 0); + signal.i = signal.r = signal.y = signal.x = signal.b = signal.d = signal.c = null; + set_signal_status(signal, DESTROYED); + if (destroy !== null) { + if (is_array(destroy)) { + run_all(destroy); + } else { + destroy(); + } + } + if (teardown !== null && (flags & IS_EFFECT) !== 0) { + teardown(); + } +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 75c361d7a9..6385ff02c7 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -35,7 +35,6 @@ import { remove } from './reconciler.js'; import { - destroy_signal, push_destroy_fn, execute_effect, untrack, @@ -55,7 +54,8 @@ import { effect, managed_effect, pre_effect, - user_effect + user_effect, + destroy_effect } from './reactivity/effects.js'; import { current_hydration_fragment, @@ -728,11 +728,11 @@ export function bind_playback_rate(media, get_value, update) { // Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser. // For hydration we could do it immediately but the additional code is not worth the lost microtask. - /** @type {import('./types.js').Reaction | undefined} */ + /** @type {import('./types.js').Effect | undefined} */ let render; let destroyed = false; const effect = managed_effect(() => { - destroy_signal(effect); + destroy_effect(effect); if (destroyed) return; if (get_value() == null) { callback(); @@ -750,7 +750,7 @@ export function bind_playback_rate(media, get_value, update) { render_effect(() => () => { destroyed = true; if (render) { - destroy_signal(render); + destroy_effect(render); } }); } @@ -1672,7 +1672,7 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) { block.d = null; element = null; } - destroy_signal(render_effect_signal); + destroy_effect(render_effect_signal); }); block.e = element_effect; } @@ -1712,7 +1712,7 @@ export function component(anchor_node, component_fn, render_fn) { remove(render.d); render.d = null; } - destroy_signal(render.e); + destroy_effect(render.e); render.e = null; } } @@ -1788,7 +1788,7 @@ export function component(anchor_node, component_fn, render_fn) { } const effect = render.e; if (effect !== null) { - destroy_signal(effect); + destroy_effect(effect); } render = render.p; } @@ -2659,7 +2659,7 @@ function _mount(Component, options) { if (dom !== null) { remove(dom); } - destroy_signal(/** @type {import('./types.js').Effect} */ (block.e)); + destroy_effect(/** @type {import('./types.js').Effect} */ (block.e)); }); return component; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index cdf5090d26..f90cd1fc0d 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -10,7 +10,7 @@ import { object_prototype } from './utils.js'; import { unstate } from './proxy.js'; -import { pre_effect } from './reactivity/effects.js'; +import { destroy_effect, pre_effect } from './reactivity/effects.js'; import { EACH_BLOCK, IF_BLOCK, @@ -31,8 +31,9 @@ import { import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; import { mutate, set, source } from './reactivity/sources.js'; +import { destroy_derived } from './reactivity/deriveds.js'; -const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT; +export const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -354,7 +355,7 @@ function remove_consumer(signal, dependency) { * @param {number} start_index * @returns {void} */ -function remove_consumers(signal, start_index) { +export function remove_consumers(signal, start_index) { const dependencies = signal.d; if (dependencies !== null) { const active_dependencies = start_index === 0 ? null : dependencies.slice(0, start_index); @@ -373,13 +374,19 @@ function remove_consumers(signal, start_index) { * @param {import('./types.js').Reaction} signal * @returns {void} */ -function destroy_references(signal) { +export function destroy_references(signal) { const references = signal.r; signal.r = null; if (references !== null) { let i; for (i = 0; i < references.length; i++) { - destroy_signal(references[i]); + var reference = references[i]; + if ((reference.f & DERIVED) !== 0) { + // TODO make signal.r only contain deriveds or effects + destroy_derived(/** @type {import('#client').Derived} */ (reference)); + } else { + destroy_effect(/** @type {import('#client').Effect} */ (reference)); + } } } } @@ -898,30 +905,6 @@ export function mark_signal_consumers(signal, to_status, force_schedule) { } } -/** - * @param {import('./types.js').Reaction} signal - * @returns {void} - */ -export function destroy_signal(signal) { - const teardown = /** @type {null | (() => void)} */ (signal.v); - const destroy = signal.y; - const flags = signal.f; - destroy_references(signal); - remove_consumers(signal, 0); - signal.i = signal.r = signal.y = signal.x = signal.b = signal.d = signal.c = null; - set_signal_status(signal, DESTROYED); - if (destroy !== null) { - if (is_array(destroy)) { - run_all(destroy); - } else { - destroy(); - } - } - if (teardown !== null && (flags & IS_EFFECT) !== 0) { - teardown(); - } -} - /** * Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency. * diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 3261c42833..d9c191a421 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -12,11 +12,15 @@ import { import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js'; import { schedule_raf_task } from './dom/task.js'; import { append_child, empty } from './operations.js'; -import { effect, managed_effect, managed_pre_effect } from './reactivity/effects.js'; +import { + destroy_effect, + effect, + managed_effect, + managed_pre_effect +} from './reactivity/effects.js'; import { current_block, current_effect, - destroy_signal, execute_effect, mark_subtree_inert, untrack @@ -589,7 +593,7 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo } const effect = managed_pre_effect(() => { - destroy_signal(effect); + destroy_effect(effect); dom.inert = false; if (show_intro && !already_mounted) { @@ -666,9 +670,9 @@ export function trigger_transitions(transitions, target_direction, from) { if (outros.length > 0) { // Defer the outros to a microtask const e = managed_pre_effect(() => { - destroy_signal(e); + destroy_effect(e); const e2 = managed_effect(() => { - destroy_signal(e2); + destroy_effect(e2); run_all(outros); }); }, false); diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 8791a434d4..003c17bdbc 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,6 +1,11 @@ import { describe, assert, it } from 'vitest'; import * as $ from '../../src/internal/client/runtime'; -import { effect, render_effect, user_effect } from '../../src/internal/client/reactivity/effects'; +import { + destroy_effect, + effect, + render_effect, + user_effect +} from '../../src/internal/client/reactivity/effects'; import { source, set } from '../../src/internal/client/reactivity/sources'; import type { Derived } from '../../src/internal/client/types'; import { proxy } from '../../src/internal/client/proxy'; @@ -27,7 +32,7 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) { ); $.pop(); execute(); - $.destroy_signal(signal); + destroy_effect(signal); }; } @@ -261,7 +266,7 @@ describe('signals', () => { // Ensure we're not leaking consumers assert.deepEqual(count.c?.length, 1); assert.deepEqual(log, [0, 2, 'limit', 0]); - $.destroy_signal(effect); + destroy_effect(effect); // Ensure we're not leaking consumers assert.deepEqual(count.c, null); };