From 38ef714f1a2fa0e74b60e74ca46a8977ddac26b7 Mon Sep 17 00:00:00 2001 From: Kamil Jakubus Date: Thu, 20 Aug 2026 23:30:19 +0200 Subject: [PATCH] fix: scope SSR boundary failed snippets to their boundary (#18593) Each `` with: ```svelte {#snippet failed(error)} ... {/snippet} ``` generated a function named failed. Sibling boundaries placed both functions in the same SSR scope: ```js { function failed() {} function failed() {} // Identifier `failed` has already been declared } ``` Resulting in a `[PARSE_ERROR] Identifier `failed` has already been declared` error during build time. A let declaration such as `{const x = 0}` can create a nested lexical block, exposing the collision. The fix gives each boundary its own scope: ```js { function failed() {} $$renderer.boundary({ failed }, ...); } { function failed() {} $$renderer.boundary({ failed }, ...); } `` --- .changeset/young-papers-hide.md | 5 +++++ .../3-transform/server/visitors/SvelteBoundary.js | 13 +++++++++---- .../_expected.html | 1 + .../boundary-duplicate-failed-snippets/main.svelte | 11 +++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/young-papers-hide.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/main.svelte diff --git a/.changeset/young-papers-hide.md b/.changeset/young-papers-hide.md new file mode 100644 index 0000000000..7df1f114b9 --- /dev/null +++ b/.changeset/young-papers-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: scope SSR boundary failed snippets to their boundary 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 47a07a1312..feef4bd1a9 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 @@ -1,4 +1,4 @@ -/** @import { BlockStatement } from 'estree' */ +/** @import { BlockStatement, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import * as b from '#compiler/builders'; @@ -84,6 +84,9 @@ export function SvelteBoundary(node, context) { } const props = b.object([]); + /** @type {Statement[]} */ + const init = []; + if (failed_attribute && !failed_snippet) { const failed_callee = build_attribute_value( failed_attribute.value, @@ -95,13 +98,15 @@ export function SvelteBoundary(node, context) { props.properties.push(b.init('failed', failed_callee)); } else if (failed_snippet) { - context.visit(failed_snippet, context.state); + context.visit(failed_snippet, { ...context.state, init }); props.properties.push(b.init('failed', failed_snippet.expression)); } - context.state.template.push( - b.stmt(b.call('$$renderer.boundary', props, b.arrow([b.id('$$renderer')], children_body))) + const boundary = b.stmt( + b.call('$$renderer.boundary', props, b.arrow([b.id('$$renderer')], children_body)) ); + + context.state.template.push(init.length > 0 ? b.block([...init, boundary]) : boundary); } /** diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/_expected.html b/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/_expected.html new file mode 100644 index 0000000000..7c89b545c5 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/_expected.html @@ -0,0 +1 @@ +
diff --git a/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/main.svelte b/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/main.svelte new file mode 100644 index 0000000000..126c9ea304 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/boundary-duplicate-failed-snippets/main.svelte @@ -0,0 +1,11 @@ +
+ {const x = 0} + + + {#snippet failed()}{/snippet} + + + + {#snippet failed()}{/snippet} + +