diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index b524391380..95347d8f12 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -389,7 +389,7 @@ export const javascript_visitors_runes = { const args = /** @type {import('estree').Expression[]} */ ( node.arguments.map((arg) => context.visit(arg)) ); - return b.call('$.user_root_effect', ...args); + return b.call('$.effect_root', ...args); } if (rune === '$inspect' || rune === '$inspect().with') { diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index cf02a67555..9e6d818264 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -2,7 +2,7 @@ export const DERIVED = 1 << 1; export const EFFECT = 1 << 2; export const PRE_EFFECT = 1 << 3; export const RENDER_EFFECT = 1 << 4; -export const BLOCK_EFFECT = 1 << 4; +export const BLOCK_EFFECT = 1 << 5; export const MANAGED = 1 << 6; export const UNOWNED = 1 << 7; export const CLEAN = 1 << 8; @@ -12,6 +12,7 @@ export const INERT = 1 << 11; export const DESTROYED = 1 << 12; export const IS_ELSEIF = 1 << 13; export const EFFECT_RAN = 1 << 14; +export const ROOT_EFFECT = 1 << 15; export const UNINITIALIZED = Symbol(); export const STATE_SYMBOL = Symbol('$state'); diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 6aaf142a27..6a5aa696ee 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -24,7 +24,8 @@ import { INERT, IS_ELSEIF, EFFECT_RAN, - BLOCK_EFFECT + BLOCK_EFFECT, + ROOT_EFFECT } from '../constants.js'; import { set } from './sources.js'; import { noop } from '../../common.js'; @@ -90,7 +91,7 @@ function create_effect(type, fn, sync, init = true) { * @returns {boolean} */ export function effect_active() { - return current_effect ? (current_effect.f & MANAGED) === 0 : false; + return current_effect ? (current_effect.f & (MANAGED | ROOT_EFFECT)) === 0 : false; } /** @@ -147,13 +148,21 @@ export function user_pre_effect(fn) { * @param {() => void | (() => void)} fn * @returns {() => void} */ -export function user_root_effect(fn) { - const effect = render_effect(fn, true); +export function effect_root(fn) { + const effect = create_effect(ROOT_EFFECT, () => untrack(fn), true); return () => { destroy_effect(effect); }; } +/** + * TODO this is placeholder, just so we can get all the tests passing. eventually `_mount` should just create an effect root + * @param {() => void | (() => void)} fn + */ +export function fake_effect_root(fn) { + return create_effect(RENDER_EFFECT | ROOT_EFFECT, () => untrack(fn), true); +} + /** * @param {() => void | (() => void)} fn * @returns {import('#client').Effect} @@ -207,24 +216,20 @@ export function pre_effect(fn) { /** * @param {(() => void)} fn - * @param {boolean} managed * @returns {import('#client').Effect} */ -export function render_effect(fn, managed = false) { - let flags = RENDER_EFFECT; - if (managed) flags |= MANAGED; - - return create_effect(flags, fn, true); +export function render_effect(fn) { + return create_effect(RENDER_EFFECT, fn, true); } /** @param {(() => void)} fn */ export function block(fn) { - return create_effect(BLOCK_EFFECT, fn, true); + return create_effect(RENDER_EFFECT | BLOCK_EFFECT, fn, true); } /** @param {(() => void)} fn */ export function branch(fn) { - return create_effect(EFFECT | RENDER_EFFECT | MANAGED, fn, true); + return create_effect(RENDER_EFFECT | MANAGED, fn, true); } /** diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 65fe8a9a63..d5b982b2fa 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -8,8 +8,8 @@ import { } from './dom/operations.js'; import { PassiveDelegatedEvents } from '../../constants.js'; import { remove } from './dom/reconciler.js'; -import { flush_sync, push, pop, current_component_context } from './runtime.js'; -import { render_effect, destroy_effect } from './reactivity/effects.js'; +import { flush_sync, push, pop, current_component_context, untrack } from './runtime.js'; +import { render_effect, destroy_effect, fake_effect_root } from './reactivity/effects.js'; import { hydrate_anchor, hydrate_nodes, @@ -19,6 +19,7 @@ import { } from './dom/hydration.js'; import { array_from } from './utils.js'; import { handle_event_propagation } from './dom/elements/events.js'; +import { ROOT_EFFECT } from './constants.js'; /** @type {Set} */ export const all_registered_events = new Set(); @@ -205,26 +206,28 @@ function _mount(Component, options) { // @ts-expect-error will be defined because the render effect runs synchronously let component = undefined; - const effect = render_effect(() => { + // TODO should this just be a root effect, rather than having the destroy logic live separately? + const effect = fake_effect_root(() => { if (options.context) { push({}); - /** @type {import('../client/types.js').ComponentContext} */ (current_component_context).c = - options.context; - } - if (!options.props) { - options.props = /** @type {Props} */ ({}); + var ctx = /** @type {import('#client').ComponentContext} */ (current_component_context); + ctx.c = options.context; } + + options.props ||= /** @type {Props} */ ({}); + if (options.events) { // We can't spread the object or else we'd lose the state proxy stuff, if it is one /** @type {any} */ (options.props).$$events = options.events; } - component = - // @ts-expect-error the public typings are not what the actual function looks like - Component(options.anchor, options.props) || {}; + + // @ts-expect-error the public typings are not what the actual function looks like + component = Component(options.anchor, options.props) || {}; + if (options.context) { pop(); } - }, true); + }); const bound_event_listener = handle_event_propagation.bind(null, container); const bound_document_event_listener = handle_event_propagation.bind(null, document); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 22f7293ab9..12d2444883 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -22,7 +22,8 @@ import { INERT, MANAGED, STATE_SYMBOL, - BLOCK_EFFECT + BLOCK_EFFECT, + ROOT_EFFECT } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; @@ -366,7 +367,7 @@ export function destroy_children(signal) { // we don't want to destroy root effects when their parent effects are // updated? seems leaky), but it looks like there are other managed // effects aside from the immediate children of blocks - if ((effect.f & MANAGED) === 0) { + if ((effect.f & ROOT_EFFECT) === 0) { destroy_effect(effect); } } diff --git a/packages/svelte/src/reactivity/map.test.ts b/packages/svelte/src/reactivity/map.test.ts index 979e8ded05..e758f453c5 100644 --- a/packages/svelte/src/reactivity/map.test.ts +++ b/packages/svelte/src/reactivity/map.test.ts @@ -1,4 +1,4 @@ -import { pre_effect, user_root_effect } from '../internal/client/reactivity/effects.js'; +import { pre_effect, effect_root } from '../internal/client/reactivity/effects.js'; import { flushSync } from '../main/main-client.js'; import { ReactiveMap } from './map.js'; import { assert, test } from 'vitest'; @@ -14,7 +14,7 @@ test('map.values()', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { pre_effect(() => { log.push(map.size); }); @@ -50,7 +50,7 @@ test('map.get(...)', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { pre_effect(() => { log.push('get 1', map.get(1)); }); @@ -86,7 +86,7 @@ test('map.has(...)', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { pre_effect(() => { log.push('has 1', map.has(1)); }); @@ -129,7 +129,7 @@ test('map handling of undefined values', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { map.set(1, undefined); pre_effect(() => { diff --git a/packages/svelte/src/reactivity/set.test.ts b/packages/svelte/src/reactivity/set.test.ts index e93ac4e565..def9c64d3b 100644 --- a/packages/svelte/src/reactivity/set.test.ts +++ b/packages/svelte/src/reactivity/set.test.ts @@ -1,4 +1,4 @@ -import { pre_effect, user_root_effect } from '../internal/client/reactivity/effects.js'; +import { pre_effect, effect_root } from '../internal/client/reactivity/effects.js'; import { flushSync } from '../main/main-client.js'; import { ReactiveSet } from './set.js'; import { assert, test } from 'vitest'; @@ -8,7 +8,7 @@ test('set.values()', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { pre_effect(() => { log.push(set.size); }); @@ -40,7 +40,7 @@ test('set.has(...)', () => { const log: any = []; - const cleanup = user_root_effect(() => { + const cleanup = effect_root(() => { pre_effect(() => { log.push('has 1', set.has(1)); }); diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index f3a3054d28..ef2bc96fc3 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -24,7 +24,7 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) { let execute: any; const signal = render_effect(() => { execute = fn(runes); - }, true); + }); $.pop(); execute(); destroy_effect(signal);