diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 3043380f05..46c13d1a6f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -371,11 +371,7 @@ export function client_component(analysis, options) { const body = b.function_declaration( b.id('$$body'), [b.id('$$anchor'), b.id('$$props')], - b.block([ - b.var('$$unsuspend', b.call('$.suspend')), - ...component_block.body, - b.stmt(b.call('$$unsuspend')) - ]) + b.block([...component_block.body, b.stmt(b.call('$.exit'))]) ); body.async = true; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js index 3c724174bb..9189ed4b88 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js @@ -1,6 +1,7 @@ /** @import { AwaitExpression, Expression } from 'estree' */ /** @import { Context } from '../types' */ import * as b from '../../../../utils/builders.js'; +import { get_rune } from '../../../scope.js'; /** * @param {AwaitExpression} node @@ -13,9 +14,17 @@ export function AwaitExpression(node, context) { return context.next(); } - return b.call( + const inside_derived = context.path.some( + (n) => n.type === 'CallExpression' && get_rune(n, context.state.scope) === '$derived' + ); + + const expression = b.call( b.await( b.call('$.save', node.argument && /** @type {Expression} */ (context.visit(node.argument))) ) ); + + return inside_derived + ? expression + : b.await(b.call('$.script_suspend', b.arrow([], expression, true))); } diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 3fe143f38d..9532b1c2e4 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -273,13 +273,15 @@ export function capture() { var previous_reaction = active_reaction; var previous_component_context = component_context; - return function restore() { + return function restore(should_exit = true) { set_active_effect(previous_effect); set_active_reaction(previous_reaction); set_component_context(previous_component_context); // prevent the active effect from outstaying its welcome - queue_post_micro_task(exit); + if (should_exit) { + queue_post_micro_task(exit); + } }; } @@ -307,6 +309,21 @@ export function suspend() { }; } +/** + * @template T + * @param {() => Promise} fn + */ +export async function script_suspend(fn) { + const restore = capture(); + const unsuspend = suspend(); + try { + return await fn(); + } finally { + restore(false); + unsuspend(); + } +} + /** * @template T * @param {Promise} promise diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index d178591342..cf164fde26 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -130,7 +130,7 @@ export { update_store, mark_store_binding } from './reactivity/store.js'; -export { boundary, exit, save, suspend } from './dom/blocks/boundary.js'; +export { boundary, exit, save, suspend, script_suspend } from './dom/blocks/boundary.js'; export { set_text } from './render.js'; export { get, diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 66e98b0666..94d20fb0e1 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -29,6 +29,7 @@ import { inspect_effects, internal_set, set_inspect_effects, source } from './so import { get_stack } from '../dev/tracing.js'; import { tracing_mode_flag } from '../../flags/index.js'; import { capture, suspend } from '../dom/blocks/boundary.js'; +import { flush_boundary_micro_tasks } from '../dom/task.js'; /** * @template V @@ -99,12 +100,19 @@ export function async_derived(fn) { var current_deps = new Set(async_deps); - var effect = block(async () => { + block(async () => { + var effect = /** @type {Effect} */ (active_effect); var current = (promise = fn()); 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; 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 888d2a4e99..b2add47161 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-derived/Child.svelte @@ -2,6 +2,14 @@ let { promise, num } = $props(); let value = $derived((await promise) * num); + + $effect(() => { + console.log('should run'); + }); + + $effect(() => { + console.log(value, num); + });

{value}

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 abeea8becb..6a46846744 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-derived/_config.js @@ -17,12 +17,14 @@ export default test({ }; }, - async test({ assert, target, component }) { + async test({ assert, target, component, logs }) { d.resolve(42); await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); await tick(); flushSync(); assert.htmlEqual(target.innerHTML, '

42

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

84

'); @@ -42,7 +46,11 @@ export default test({ d.resolve(43); 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]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-top-level/_config.js b/packages/svelte/tests/runtime-runes/samples/async-top-level/_config.js index fb2dbb0e66..b593155946 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-top-level/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-top-level/_config.js @@ -19,6 +19,8 @@ export default test({ async test({ assert, target }) { d.resolve('hello'); await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); await tick(); flushSync(); assert.htmlEqual(target.innerHTML, '

hello

');