From 456c8c4e64cdc661e1318e8693fa411aff6abb74 Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Fri, 26 Sep 2025 21:37:05 -0700
Subject: [PATCH] fix: check boundary `pending` attribute at runtime on server
---
.changeset/lazy-cooks-return.md | 5 ++
.../server/visitors/SvelteBoundary.js | 57 ++++++++++++++-----
packages/svelte/src/compiler/phases/scope.js | 5 ++
.../_expected.html | 1 +
.../async-nullish-pending-snippet/main.svelte | 6 ++
5 files changed, 60 insertions(+), 14 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 b914c96f4e..0fdf0c5bad 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
@@ -2,7 +2,13 @@
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import * as b from '#compiler/builders';
-import { block_close, block_open, block_open_else, build_attribute_value } from './shared/utils.js';
+import {
+ block_close,
+ block_open,
+ block_open_else,
+ build_attribute_value,
+ build_template
+} from './shared/utils.js';
/**
* @param {AST.SvelteBoundary} node
@@ -13,6 +19,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(
@@ -21,20 +32,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));
context.state.template.push(block_open, block, block_close);
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index 887bc47c56..b8339b07bc 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -260,6 +260,11 @@ class Evaluation {
break;
}
+ if (binding.initial?.type === 'SnippetBlock') {
+ this.is_defined = true;
+ 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