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