diff --git a/.changeset/mighty-mice-call.md b/.changeset/mighty-mice-call.md new file mode 100644 index 0000000000..340b33bd4b --- /dev/null +++ b/.changeset/mighty-mice-call.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep batches alive until all async work is complete diff --git a/.changeset/short-banks-yell.md b/.changeset/short-banks-yell.md new file mode 100644 index 0000000000..34d5ba66d3 --- /dev/null +++ b/.changeset/short-banks-yell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't preserve reactivity context across function boundaries diff --git a/.changeset/witty-seas-learn.md b/.changeset/witty-seas-learn.md new file mode 100644 index 0000000000..aa94c7c35f --- /dev/null +++ b/.changeset/witty-seas-learn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure guards (eg. if, each, key) run before their contents diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 47fe37c44d..52be997374 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -306,7 +306,7 @@ export function analyze_module(source, options) { fragment: null, parent_element: null, reactive_statement: null, - in_derived: false + derived_function_depth: -1 }, visitors ); @@ -703,7 +703,7 @@ export function analyze_component(root, source, options) { state_fields: new Map(), function_depth: scope.function_depth, reactive_statement: null, - in_derived: false + derived_function_depth: -1 }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); @@ -771,7 +771,7 @@ export function analyze_component(root, source, options) { expression: null, state_fields: new Map(), function_depth: scope.function_depth, - in_derived: false + derived_function_depth: -1 }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index ae9c5911f6..bad6c7d613 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -29,9 +29,9 @@ export interface AnalysisState { reactive_statement: null | ReactiveStatement; /** - * True if we're directly inside a `$derived(...)` expression (but not `$derived.by(...)`) + * Set when we're inside a `$derived(...)` expression (but not `$derived.by(...)`) or `@const` */ - in_derived: boolean; + derived_function_depth: number; } export type Context = import('zimmerframe').Context< diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js index 9018623570..14757af4a3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js @@ -15,7 +15,10 @@ export function AwaitExpression(node, context) { // b) awaits that precede other expressions in template or `$derived(...)` if ( tla || - (is_reactive_expression(context.path, context.state.in_derived) && + (is_reactive_expression( + context.path, + context.state.derived_function_depth === context.state.function_depth + ) && !is_last_evaluated_expression(context.path, node)) ) { context.state.analysis.pickled_awaits.add(node); @@ -53,9 +56,7 @@ export function AwaitExpression(node, context) { * @param {boolean} in_derived */ export function is_reactive_expression(path, in_derived) { - if (in_derived) { - return true; - } + if (in_derived) return true; let i = path.length; @@ -67,6 +68,7 @@ export function is_reactive_expression(path, in_derived) { parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration' ) { + // No reactive expression found between function and await return false; } @@ -83,11 +85,16 @@ export function is_reactive_expression(path, in_derived) { * @param {AST.SvelteNode[]} path * @param {Expression | SpreadElement | Property} node */ -export function is_last_evaluated_expression(path, node) { +function is_last_evaluated_expression(path, node) { let i = path.length; while (i--) { - const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]); + const parent = path[i]; + + if (parent.type === 'ConstTag') { + // {@const ...} tags are treated as deriveds and its contents should all get the preserve-reactivity treatment + return false; + } // @ts-expect-error we could probably use a neater/more robust mechanism if (parent.metadata) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 76d9cecd9a..4b66abe1d1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -248,7 +248,7 @@ export function CallExpression(node, context) { context.next({ ...context.state, function_depth: context.state.function_depth + 1, - in_derived: true, + derived_function_depth: context.state.function_depth + 1, expression }); diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index 5849d828a3..77ea654905 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -38,6 +38,8 @@ export function ConstTag(node, context) { context.visit(declaration.init, { ...context.state, expression: node.metadata.expression, - in_derived: true + // We're treating this like a $derived under the hood + function_depth: context.state.function_depth + 1, + derived_function_depth: context.state.function_depth + 1 }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js index 7a85b4a93a..dfb1d54040 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js @@ -64,12 +64,6 @@ export function VariableDeclarator(node, context) { } } - if (rune === '$derived') { - context.visit(node.id); - context.visit(/** @type {Expression} */ (node.init), { ...context.state, in_derived: true }); - return; - } - if (rune === '$props') { if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') { e.props_invalid_identifier(node); diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 24ff4793ea..a308868336 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -1,5 +1,5 @@ /** @import { Effect, TemplateNode, Value } from '#client' */ -import { DESTROYED } from '#client/constants'; +import { DESTROYED, STALE_REACTION } from '#client/constants'; import { DEV } from 'esm-env'; import { component_context, diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 17ba2c9ed4..ff829cc445 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -29,7 +29,14 @@ import * as e from '../errors.js'; import { flush_tasks, queue_micro_task } from '../dom/task.js'; import { DEV } from 'esm-env'; import { invoke_error_boundary } from '../error-handling.js'; -import { old_values, source, update } from './sources.js'; +import { + flush_inspect_effects, + inspect_effects, + old_values, + set_inspect_effects, + source, + update +} from './sources.js'; import { inspect_effect, unlink_effect } from './effects.js'; /** @@ -76,6 +83,8 @@ let is_flushing = false; export let is_flushing_sync = false; export class Batch { + committed = false; + /** * The current values of any sources that are updated in this batch * They keys of this map are identical to `this.#previous` @@ -88,7 +97,7 @@ export class Batch { * They keys of this map are identical to `this.#current` * @type {Map} */ - #previous = new Map(); + previous = new Map(); /** * When the batch is committed (and the DOM is updated), we need to remove old branches @@ -166,16 +175,7 @@ export class Batch { this.#traverse_effect_tree(root, target); } - // if there is no outstanding async work, commit - if (this.#pending === 0) { - if (this.is_fork) { - this.#fork_deferred?.resolve(); - } else { - // commit before flushing effects, since that may result in - // another batch being created - this.#commit(); - } - } + this.#resolve(); if (this.#blocking_pending > 0 || this.is_fork) { this.#defer_effects(target.effects); @@ -291,8 +291,8 @@ export class Batch { * @param {any} value */ capture(source, value) { - if (!this.#previous.has(source)) { - this.#previous.set(source, value); + if (!this.previous.has(source)) { + this.previous.set(source, value); } this.current.set(source, source.v); @@ -334,16 +334,29 @@ export class Batch { } } - /** - * Append and remove branches to/from the DOM - */ + #resolve() { + if (this.#blocking_pending === 0 && !this.is_fork) { + // append/remove branches + for (const fn of this.#callbacks) fn(); + this.#callbacks.clear(); + } + + if (this.#pending === 0) { + if (this.is_fork) { + this.#fork_deferred?.resolve(); + } else { + this.#commit(); + } + } + } + #commit() { // If there are other pending batches, they now need to be 'rebased' — // in other words, we re-run block/async effects with the newly // committed state, unless the batch in question has a more // recent value for a given source if (batches.size > 1) { - this.#previous.clear(); + this.previous.clear(); var previous_batch_values = batch_values; var is_earlier = true; @@ -412,6 +425,7 @@ export class Batch { batch_values = previous_batch_values; } + this.committed = true; batches.delete(this); this.#deferred?.resolve(); @@ -503,7 +517,7 @@ export class Batch { for (const batch of batches) { if (batch === this) continue; - for (const [source, previous] of batch.#previous) { + for (const [source, previous] of batch.previous) { if (!batch_values.has(source)) { batch_values.set(source, previous); } @@ -625,7 +639,7 @@ function infinite_loop_guard() { } } -/** @type {Effect[] | null} */ +/** @type {Set | null} */ export let eager_block_effects = null; /** @@ -642,7 +656,7 @@ function flush_queued_effects(effects) { var effect = effects[i++]; if ((effect.f & (DESTROYED | INERT)) === 0 && is_dirty(effect)) { - eager_block_effects = []; + eager_block_effects = new Set(); update_effect(effect); @@ -665,15 +679,34 @@ function flush_queued_effects(effects) { // If update_effect() has a flushSync() in it, we may have flushed another flush_queued_effects(), // which already handled this logic and did set eager_block_effects to null. - if (eager_block_effects?.length > 0) { - // TODO this feels incorrect! it gets the tests passing + if (eager_block_effects?.size > 0) { old_values.clear(); for (const e of eager_block_effects) { - update_effect(e); + // Skip eager effects that have already been unmounted + if ((e.f & (DESTROYED | INERT)) !== 0) continue; + + // Run effects in order from ancestor to descendant, else we could run into nullpointers + /** @type {Effect[]} */ + const ordered_effects = [e]; + let ancestor = e.parent; + while (ancestor !== null) { + if (eager_block_effects.has(ancestor)) { + eager_block_effects.delete(ancestor); + ordered_effects.push(ancestor); + } + ancestor = ancestor.parent; + } + + for (let j = ordered_effects.length - 1; j >= 0; j--) { + const e = ordered_effects[j]; + // Skip eager effects that have already been unmounted + if ((e.f & (DESTROYED | INERT)) !== 0) continue; + update_effect(e); + } } - eager_block_effects = []; + eager_block_effects.clear(); } } } @@ -828,17 +861,47 @@ export function fork(fn) { const batch = Batch.ensure(); batch.is_fork = true; - fn(); - await batch.fork_settled(); + flushSync(fn); + const deferred_inspect_effects = inspect_effects; - // TODO revert state changes + // revert state changes + for (const [source, value] of batch.previous) { + source.v = value; + } + + await batch.fork_settled(); fulfil({ commit: () => { - // TODO reapply state changes - batch.is_fork = false; - batch.activate(); - batch.revive(); + if (!batches.has(batch)) { + throw new Error('Cannot commit this batch'); // TODO better error + } + + // delete all other forks + for (const b of batches) { + if (b !== batch && b.is_fork) { + batches.delete(b); + } + } + + for (const [source, value] of batch.current) { + source.v = value; + } + + const previous_inspect_effects = inspect_effects; + + try { + if (DEV && deferred_inspect_effects.size > 0) { + set_inspect_effects(deferred_inspect_effects); + flush_inspect_effects(); + } + + batch.is_fork = false; + batch.activate(); + batch.revive(); + } finally { + set_inspect_effects(previous_inspect_effects); + } }, discard: () => { batches.delete(batch); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 1989220abe..06ae0f6d7a 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -127,7 +127,17 @@ export function async_derived(fn, location) { // If this code is changed at some point, make sure to still access the then property // of fn() to read any signals it might access, so that we track them as dependencies. // We call `unset_context` to undo any `save` calls that happen inside `fn()` - Promise.resolve(fn()).then(d.resolve, d.reject).then(unset_context); + Promise.resolve(fn()) + .then(d.resolve, d.reject) + .then(() => { + if (batch === current_batch && batch.committed) { + // if the batch was rejected as stale, we need to cleanup + // after any `$.save(...)` calls inside `fn()` + batch.deactivate(); + } + + unset_context(); + }); } catch (error) { d.reject(error); unset_context(); diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index cd0c28016d..1f719ec6c3 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -234,7 +234,7 @@ export function internal_set(source, value) { } } - if (DEV && inspect_effects.size > 0 && !inspect_effects_deferred) { + if (DEV && !batch.is_fork && inspect_effects.size > 0 && !inspect_effects_deferred) { flush_inspect_effects(); } } @@ -336,7 +336,7 @@ function mark_reactions(signal, status) { } else if (not_dirty) { if ((flags & BLOCK_EFFECT) !== 0) { if (eager_block_effects !== null) { - eager_block_effects.push(/** @type {Effect} */ (reaction)); + eager_block_effects.add(/** @type {Effect} */ (reaction)); } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-resolve/_config.js b/packages/svelte/tests/runtime-runes/samples/async-block-resolve/_config.js new file mode 100644 index 0000000000..ee403290bc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-resolve/_config.js @@ -0,0 +1,63 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, shift] = target.querySelectorAll('button'); + + shift.click(); + await tick(); + + shift.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

even

+

0

+ ` + ); + + increment.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

even

+

0

+ ` + ); + + shift.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

odd

+

loading...

+ ` + ); + + shift.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

odd

+

1

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-block-resolve/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-block-resolve/main.svelte new file mode 100644 index 0000000000..73fe83889a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-block-resolve/main.svelte @@ -0,0 +1,36 @@ + + + + + + + {#if await push(count) % 2 === 0} +

even

+ {:else} +

odd

+ {/if} + + {#key count} + +

{await push(count)}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
+ {/key} + + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte index 682f7a0631..758ebc0004 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/Child.svelte @@ -1,7 +1,11 @@ diff --git a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js index 81548a25ea..0908b6a9fe 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-effect-after-await/_config.js @@ -3,7 +3,8 @@ import { test } from '../../test'; export default test({ async test({ assert, logs }) { + assert.deepEqual(logs, []); await tick(); - assert.deepEqual(logs, ['hello']); + assert.deepEqual(logs, ['before', 'after']); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js index 50bb414afc..62fa0446ef 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/_config.js @@ -3,23 +3,27 @@ import { test } from '../../test'; export default test({ async test({ assert, target }) { // We gotta wait a bit more in this test because of the macrotasks in App.svelte - function macrotask(t = 3) { + function sleep(t = 50) { return new Promise((r) => setTimeout(r, t)); } - await macrotask(); + await sleep(); assert.htmlEqual(target.innerHTML, ' 1 | '); const [input] = target.querySelectorAll('input'); input.value = '1'; input.dispatchEvent(new Event('input', { bubbles: true })); - await macrotask(); + await sleep(); assert.htmlEqual(target.innerHTML, ' 1 | '); input.value = '12'; input.dispatchEvent(new Event('input', { bubbles: true })); - await macrotask(6); + await sleep(); assert.htmlEqual(target.innerHTML, ' 3 | 12'); + input.value = ''; + input.dispatchEvent(new Event('input', { bubbles: true })); + await sleep(); + assert.htmlEqual(target.innerHTML, ' 4 | '); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte index dc4a157928..2a36942ff2 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-resolve-stale/main.svelte @@ -1,28 +1,32 @@ + + + +{#if v === "one"} +
if1 matched! {console.log('one')}
+{:else if v === "two"} +
if2 matched! {console.log('two')}
+{:else} +
nothing matched {console.log('else')}
+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js new file mode 100644 index 0000000000..881c1545ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; +import { flushSync } from 'svelte'; + +export default test({ + mode: ['client'], + async test({ target, assert }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + + assert.equal(target.textContent?.trim(), 'Trigger'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte new file mode 100644 index 0000000000..4514bd114e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-if-nested/main.svelte @@ -0,0 +1,18 @@ + + +{#if centerRow?.nested} + {#if centerRow?.nested?.optional != undefined && centerRow.nested.optional > 0} + op: {centerRow.nested.optional}
+ {:else} + req: {centerRow.nested.required}
+ {/if} +{/if} + + diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte new file mode 100644 index 0000000000..b7322e7530 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/Component.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js new file mode 100644 index 0000000000..9706855fb4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/_config.js @@ -0,0 +1,13 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target, logs }) { + const button = target.querySelector('button'); + + button?.click(); + flushSync(); + assert.deepEqual(logs, ['pre', 'running b', 'pre', 'pre']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte new file mode 100644 index 0000000000..4ebb13eca3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/guard-nested-if-pre/main.svelte @@ -0,0 +1,18 @@ + + +{#if p || !p} + {#if p} + + {/if} +{/if} + + diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_config.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js new file mode 100644 index 0000000000..7a97850175 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js @@ -0,0 +1,52 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +export default function Async_in_derived($$anchor, $$props) { + $.push($$props, true); + + $.async_body($$anchor, async ($$anchor) => { + let yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))(); + let yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))(); + + let no1 = $.derived(async () => { + return await 1; + }); + + let no2 = $.derived(() => async () => { + return await 1; + }); + + if ($.aborted()) return; + + var fragment = $.comment(); + var node = $.first_child(fragment); + + { + var consequent = ($$anchor) => { + $.async_body($$anchor, async ($$anchor) => { + const yes1 = (await $.save($.async_derived(async () => (await $.save(1))())))(); + const yes2 = (await $.save($.async_derived(async () => foo((await $.save(1))()))))(); + + const no1 = $.derived(() => (async () => { + return await 1; + })()); + + const no2 = $.derived(() => (async () => { + return await 1; + })()); + + if ($.aborted()) return; + }); + }; + + $.if(node, ($$render) => { + if (true) $$render(consequent); + }); + } + + $.append($$anchor, fragment); + }); + + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js new file mode 100644 index 0000000000..69eca5a383 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js @@ -0,0 +1,40 @@ +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/server'; + +export default function Async_in_derived($$renderer, $$props) { + $$renderer.component(($$renderer) => { + $$renderer.async(async ($$renderer) => { + let yes1 = (await $.save(1))(); + let yes2 = foo((await $.save(1))()); + + let no1 = (async () => { + return await 1; + })(); + + let no2 = async () => { + return await 1; + }; + + $$renderer.async(async ($$renderer) => { + if (true) { + $$renderer.push(''); + + const yes1 = (await $.save(1))(); + const yes2 = foo((await $.save(1))()); + + const no1 = (async () => { + return await 1; + })(); + + const no2 = (async () => { + return await 1; + })(); + } else { + $$renderer.push(''); + } + }); + + $$renderer.push(``); + }); + }); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/index.svelte b/packages/svelte/tests/snapshot/samples/async-in-derived/index.svelte new file mode 100644 index 0000000000..bda88fd3ae --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-in-derived/index.svelte @@ -0,0 +1,21 @@ + + +{#if true} + {@const yes1 = await 1} + {@const yes2 = foo(await 1)} + {@const no1 = (async () => { + return await 1; + })()} + {@const no2 = (async () => { + return await 1; + })()} +{/if}