From 499973454af1081e19d7eaae87658f3b76d3d091 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:03:50 -0700 Subject: [PATCH] fix: check boundary `pending` attribute at runtime on server (#16855) * fix: check boundary `pending` attribute at runtime on server * fix --------- Co-authored-by: Rich Harris --- .changeset/lazy-cooks-return.md | 5 ++ .../server/visitors/SvelteBoundary.js | 50 ++++++++++++++----- packages/svelte/src/compiler/phases/scope.js | 6 +++ .../_expected.html | 1 + .../async-nullish-pending-snippet/main.svelte | 6 +++ 5 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 .changeset/lazy-cooks-return.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte diff --git a/.changeset/lazy-cooks-return.md b/.changeset/lazy-cooks-return.md new file mode 100644 index 0000000000..d2f346f76d --- /dev/null +++ b/.changeset/lazy-cooks-return.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: check boundary `pending` attribute at runtime on server diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js index 39e6912be0..b272870ac3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js @@ -7,6 +7,7 @@ import { block_open, block_open_else, build_attribute_value, + build_template, create_async_block } from './shared/utils.js'; @@ -19,6 +20,11 @@ export function SvelteBoundary(node, context) { const pending_attribute = /** @type {AST.Attribute} */ ( node.attributes.find((node) => node.type === 'Attribute' && node.name === 'pending') ); + const is_pending_attr_nullish = + pending_attribute && + typeof pending_attribute.value === 'object' && + !Array.isArray(pending_attribute.value) && + !context.state.scope.evaluate(pending_attribute.value.expression).is_defined; const pending_snippet = /** @type {AST.SnippetBlock} */ ( node.fragment.nodes.find( @@ -27,20 +33,38 @@ export function SvelteBoundary(node, context) { ); if (pending_attribute || pending_snippet) { - const pending = pending_attribute - ? b.call( - build_attribute_value( - pending_attribute.value, - context, - (expression) => expression, - false, - true - ), - b.id('$$renderer') + if (pending_attribute && is_pending_attr_nullish && !pending_snippet) { + const callee = build_attribute_value( + pending_attribute.value, + context, + (expression) => expression, + false, + true + ); + const pending = b.call(callee, b.id('$$renderer')); + const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); + context.state.template.push( + b.if( + callee, + b.block(build_template([block_open_else, pending, block_close])), + b.block(build_template([block_open, block, block_close])) ) - : /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); - - context.state.template.push(block_open_else, pending, block_close); + ); + } else { + const pending = pending_attribute + ? b.call( + build_attribute_value( + pending_attribute.value, + context, + (expression) => expression, + false, + true + ), + b.id('$$renderer') + ) + : /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); + context.state.template.push(block_open_else, pending, block_close); + } } else { const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); const statement = node.fragment.metadata.has_await diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 887bc47c56..0c6b64dd04 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -260,6 +260,12 @@ class Evaluation { break; } + if (binding.initial?.type === 'SnippetBlock') { + this.is_defined = true; + this.is_known = false; + break; + } + if (!binding.updated && binding.initial !== null && !is_prop) { binding.scope.evaluate(/** @type {Expression} */ (binding.initial), this.values); break; diff --git a/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html new file mode 100644 index 0000000000..1cc0b4b00f --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/_expected.html @@ -0,0 +1 @@ +awaited \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte new file mode 100644 index 0000000000..b4e8dbcb75 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-nullish-pending-snippet/main.svelte @@ -0,0 +1,6 @@ + + + {await 'awaited'} + \ No newline at end of file