diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index e7034a332d..5018887d7f 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -5,23 +5,26 @@ export const BLOCK_EFFECT = 1 << 4; export const BRANCH_EFFECT = 1 << 5; export const ROOT_EFFECT = 1 << 6; export const BOUNDARY_EFFECT = 1 << 7; -export const UNOWNED = 1 << 8; -export const DISCONNECTED = 1 << 9; -export const CLEAN = 1 << 10; -export const DIRTY = 1 << 11; -export const MAYBE_DIRTY = 1 << 12; -export const INERT = 1 << 13; -export const DESTROYED = 1 << 14; -export const EFFECT_RAN = 1 << 15; +export const TEMPLATE_EFFECT = 1 << 8; +export const UNOWNED = 1 << 9; +export const DISCONNECTED = 1 << 10; +export const CLEAN = 1 << 11; +export const DIRTY = 1 << 12; +export const MAYBE_DIRTY = 1 << 13; +export const INERT = 1 << 14; +export const DESTROYED = 1 << 15; +export const EFFECT_RAN = 1 << 16; /** 'Transparent' effects do not create a transition boundary */ -export const EFFECT_TRANSPARENT = 1 << 16; +export const EFFECT_TRANSPARENT = 1 << 17; /** Svelte 4 legacy mode props need to be handled with deriveds and be recognized elsewhere, hence the dedicated flag */ -export const LEGACY_DERIVED_PROP = 1 << 17; -export const INSPECT_EFFECT = 1 << 18; -export const HEAD_EFFECT = 1 << 19; -export const EFFECT_HAS_DERIVED = 1 << 20; +export const LEGACY_DERIVED_PROP = 1 << 18; +export const INSPECT_EFFECT = 1 << 19; +export const HEAD_EFFECT = 1 << 20; +export const EFFECT_HAS_DERIVED = 1 << 21; -export const REACTION_IS_UPDATING = 1 << 21; +// Flags used for async +export const IS_ASYNC = 1 << 22; +export const REACTION_IS_UPDATING = 1 << 23; export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 9a77aae368..f8793abe94 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -281,8 +281,6 @@ export function capture() { // prevent the active effect from outstaying its welcome if (should_exit) { queue_post_micro_task(exit); - } else { - debugger } }; } diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 94d20fb0e1..6310b175d1 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -6,6 +6,7 @@ import { DESTROYED, DIRTY, EFFECT_HAS_DERIVED, + IS_ASYNC, MAYBE_DIRTY, UNOWNED } from '../constants.js'; @@ -19,7 +20,8 @@ import { increment_write_version, set_active_effect, component_context, - handle_error + handle_error, + get } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; import * as e from '../errors.js'; @@ -100,19 +102,15 @@ export function async_derived(fn) { var current_deps = new Set(async_deps); + var derived_promise = derived(fn); + block(async () => { var effect = /** @type {Effect} */ (active_effect); - var current = (promise = fn()); + var current = (promise = get(derived_promise)); var restore = capture(); var unsuspend = suspend(); - // Ensure the effect tree is paused/resume otherwise user-effects will - // not run correctly - if (effect.deps !== null) { - flush_boundary_micro_tasks(); - } - try { var v = await promise; @@ -161,7 +159,7 @@ export function async_derived(fn) { // TODO we should probably null out active effect here, // rather than inside `restore()` } - }, EFFECT_HAS_DERIVED); + }, IS_ASYNC); return promise.then(() => value); } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 8be44462ad..0ee2352a2d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -37,7 +37,9 @@ import { HEAD_EFFECT, MAYBE_DIRTY, EFFECT_HAS_DERIVED, - BOUNDARY_EFFECT + BOUNDARY_EFFECT, + IS_ASYNC, + TEMPLATE_EFFECT } from '../constants.js'; import { set } from './sources.js'; import * as e from '../errors.js'; @@ -145,7 +147,7 @@ function create_effect(type, fn, sync, push = true) { effect.first === null && effect.nodes_start === null && effect.teardown === null && - (effect.f & (EFFECT_HAS_DERIVED | BOUNDARY_EFFECT)) === 0; + (effect.f & (EFFECT_HAS_DERIVED | BOUNDARY_EFFECT | IS_ASYNC)) === 0; if (!inert && !is_root && push) { if (parent_effect !== null) { @@ -385,7 +387,7 @@ function create_template_effect(fn, deriveds) { }); } - block(effect); + block(effect, TEMPLATE_EFFECT); } /** diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 9ed1731522..020130fefa 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -26,7 +26,9 @@ import { LEGACY_DERIVED_PROP, DISCONNECTED, BOUNDARY_EFFECT, - REACTION_IS_UPDATING + REACTION_IS_UPDATING, + IS_ASYNC, + TEMPLATE_EFFECT } from './constants.js'; import { flush_idle_tasks, @@ -102,6 +104,7 @@ export function set_active_effect(effect) { /* @__PURE__ */ setInterval(() => { if (active_effect !== null || active_reaction !== null) { + // eslint-disable-next-line no-debugger debugger; } }); @@ -819,6 +822,7 @@ export function schedule_effect(signal) { function process_effects(effect, collected_effects) { var current_effect = effect.first; var effects = []; + var suspended = false; main_loop: while (current_effect !== null) { var flags = current_effect.f; @@ -827,13 +831,24 @@ function process_effects(effect, collected_effects) { var sibling = current_effect.next; if (!is_skippable_branch && (flags & INERT) === 0) { + // We only want to skip suspended effects if they are not branches or block effects, + // with the exception of template effects, which are technically block effects but also + // have a special flag `TEMPLATE_EFFECT` that we can use to identify them + var skip_suspended = + suspended && + (flags & BRANCH_EFFECT) === 0 && + ((flags & BLOCK_EFFECT) === 0 || (flags & TEMPLATE_EFFECT) !== 0); + if ((flags & RENDER_EFFECT) !== 0) { if (is_branch) { current_effect.f ^= CLEAN; - } else { + } else if (!skip_suspended) { try { if (check_dirtiness(current_effect)) { update_effect(current_effect); + if ((flags & IS_ASYNC) !== 0 && !suspended) { + suspended = true; + } } } catch (error) { handle_error(error, current_effect, null, current_effect.ctx); @@ -846,7 +861,7 @@ function process_effects(effect, collected_effects) { current_effect = child; continue; } - } else if ((flags & EFFECT) !== 0) { + } else if ((flags & EFFECT) !== 0 && !skip_suspended) { effects.push(current_effect); } } @@ -858,6 +873,10 @@ function process_effects(effect, collected_effects) { if (effect === parent) { break main_loop; } + // TODO: we need to know that this boundary has a valid `pending` + if (suspended && (parent.f & BOUNDARY_EFFECT) !== 0) { + suspended = false; + } var parent_sibling = parent.next; if (parent_sibling !== null) { current_effect = parent_sibling; diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte index b2add47161..6031c28305 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte @@ -4,12 +4,12 @@ let value = $derived((await promise) * num); $effect(() => { - console.log('should run'); + console.log(`$effect ${value} ${num}`); }); - $effect(() => { - console.log(value, num); + $effect.pre(() => { + console.log(`$effect.pre ${value} ${num}`); }); -

{value}

+

{value}{console.log(`template ${value} ${num}`)}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js index 6a46846744..fb013938bb 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js @@ -25,7 +25,6 @@ export default test({ await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); - await tick(); flushSync(); assert.htmlEqual(target.innerHTML, '

42

'); @@ -34,7 +33,6 @@ export default test({ await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); - await Promise.resolve(); await tick(); assert.htmlEqual(target.innerHTML, '

84

'); @@ -47,10 +45,21 @@ export default test({ await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); - await Promise.resolve(); await tick(); assert.htmlEqual(target.innerHTML, '

86

'); - assert.deepEqual(logs, ['should run', 42, 1, 84, 2, 86, 2]); + assert.deepEqual(logs, [ + 'outside boundary 1', + '$effect.pre 42 1', + 'template 42 1', + '$effect 42 1', + 'outside boundary 2', + '$effect.pre 84 2', // TODO: why is this observed during tests, but not during runtime? + 'template 84 2', // TODO: why is this observed during tests, but not during runtime? + '$effect 84 2', // TODO: why is this observed during tests, but not during runtime? + '$effect.pre 86 2', + 'template 86 2', + '$effect 86 2' + ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived/main.svelte index 3b56c3a316..e90bbf720e 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-derived/main.svelte @@ -11,3 +11,5 @@

pending

{/snippet} + +{console.log(`outside boundary ${num}`)}