diff --git a/.changeset/lemon-geese-post.md b/.changeset/lemon-geese-post.md new file mode 100644 index 0000000000..42d01461c5 --- /dev/null +++ b/.changeset/lemon-geese-post.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't eagerly access not-yet-initialized functions in template diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 7256073096..1f3c7b2256 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -82,12 +82,16 @@ export class Memoizer { async_values() { if (this.#async.length === 0) return; - return b.array(this.#async.map((memo) => b.thunk(memo.expression, true))); + // use `b.arrow` rather than `b.thunk` so that deferred async/template effects + // always read live bindings rather than a possibly stale snapshot. + return b.array(this.#async.map((memo) => b.arrow([], memo.expression, true))); } sync_values() { if (this.#sync.length === 0) return; - return b.array(this.#sync.map((memo) => b.thunk(memo.expression))); + // use `b.arrow` rather than `b.thunk` so that deferred async/template effects + // always read live bindings rather than a possibly stale snapshot. + return b.array(this.#sync.map((memo) => b.arrow([], memo.expression))); } } diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 223676d22f..7508caf3e7 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -36,6 +36,13 @@ export function assignment_pattern(left, right) { * @returns {ESTree.ArrowFunctionExpression} */ export function arrow(params, body, async = false) { + // optimize `async () => await x()`, but not `async () => await x(await y)` + if (async && body.type === 'AwaitExpression') { + if (!has_await_expression(body.argument)) { + return arrow(params, body.argument); + } + } + return { type: 'ArrowFunctionExpression', params, @@ -462,13 +469,6 @@ export function thunk(expression, async = false) { * @returns {ESTree.Expression} */ export function unthunk(expression) { - // optimize `async () => await x()`, but not `async () => await x(await y)` - if (expression.async && expression.body.type === 'AwaitExpression') { - if (!has_await_expression(expression.body.argument)) { - return unthunk(arrow(expression.params, expression.body.argument)); - } - } - if ( expression.async === false && expression.body.type === 'CallExpression' && diff --git a/packages/svelte/tests/runtime-runes/samples/async-late-value-init/_config.js b/packages/svelte/tests/runtime-runes/samples/async-late-value-init/_config.js new file mode 100644 index 0000000000..efc8fe565d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-late-value-init/_config.js @@ -0,0 +1,9 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, 'aaa 1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-late-value-init/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-late-value-init/main.svelte new file mode 100644 index 0000000000..daa539a9b2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-late-value-init/main.svelte @@ -0,0 +1,10 @@ + + +{name} +{aa()} diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index d84b674f88..eafadfc2ea 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -32,7 +32,7 @@ export default function Main($$anchor) { $.set_attribute(div_1, 'foobar', $0); $.set_attribute(svg_1, 'viewBox', $1); }, - [y, y] + [() => y(), () => y()] ); $.append($$anchor, fragment); diff --git a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js index 464435cb0a..4148a546c8 100644 --- a/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/text-nodes-deriveds/_expected/client/index.svelte.js @@ -19,6 +19,6 @@ export default function Text_nodes_deriveds($$anchor) { var text = $.child(p); $.reset(p); - $.template_effect(($0, $1) => $.set_text(text, `${$0 ?? ''}${$1 ?? ''}`), [text1, text2]); + $.template_effect(($0, $1) => $.set_text(text, `${$0 ?? ''}${$1 ?? ''}`), [() => text1(), () => text2()]); $.append($$anchor, p); } \ No newline at end of file