From d95887deb6e1f6222b6aadef6c4493f43755fae3 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Thu, 22 Jan 2026 19:30:44 +0100 Subject: [PATCH] fix: hoist snippets above const in same block (#17516) * fix: hoist snippets above const in same block * fix: type error --- .changeset/blue-parts-double.md | 5 +++++ .../compiler/phases/3-transform/client/transform-client.js | 1 + .../src/compiler/phases/3-transform/client/types.d.ts | 2 ++ .../phases/3-transform/client/visitors/Fragment.js | 7 ++++--- .../phases/3-transform/client/visitors/RegularElement.js | 3 ++- .../phases/3-transform/client/visitors/SnippetBlock.js | 2 +- .../phases/3-transform/client/visitors/SvelteBoundary.js | 2 +- .../phases/3-transform/client/visitors/shared/component.js | 2 +- .../samples/snippet-const-same-block/_config.js | 5 +++++ .../samples/snippet-const-same-block/main.svelte | 4 ++++ 10 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 .changeset/blue-parts-double.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/main.svelte diff --git a/.changeset/blue-parts-double.md b/.changeset/blue-parts-double.md new file mode 100644 index 0000000000..223e51fdae --- /dev/null +++ b/.changeset/blue-parts-double.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: hoist snippets above const in same block diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 5c00aa591f..fc202f1bc2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -170,6 +170,7 @@ export function client_component(analysis, options) { // these are set inside the `Fragment` visitor, and cannot be used until then init: /** @type {any} */ (null), consts: /** @type {any} */ (null), + snippets: /** @type {any} */ (null), let_directives: /** @type {any} */ (null), update: /** @type {any} */ (null), after_update: /** @type {any} */ (null), diff --git a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts index d64b1d4126..4438ec015b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/client/types.d.ts @@ -49,6 +49,8 @@ export interface ComponentClientTransformState extends ClientTransformState { readonly update: Statement[]; /** Stuff that happens after the render effect (control blocks, dynamic elements, bindings, actions, etc) */ readonly after_update: Statement[]; + /** Transformed `{#snippets }` declarations */ + readonly snippets: Statement[]; /** Transformed `{@const }` declarations */ readonly consts: Statement[]; /** Transformed async `{@const }` declarations (if any) and those coming after them */ diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 18017ea557..79a443967c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -1,13 +1,13 @@ /** @import { Expression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */ -import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js'; import * as b from '#compiler/builders'; +import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../../../constants.js'; import { clean_nodes, infer_namespace } from '../../utils.js'; import { transform_template } from '../transform-template/index.js'; +import { Template } from '../transform-template/template.js'; import { process_children } from './shared/fragment.js'; import { build_render_statement, Memoizer } from './shared/utils.js'; -import { Template } from '../transform-template/template.js'; /** * @param {AST.Fragment} node @@ -60,6 +60,7 @@ export function Fragment(node, context) { const state = { ...context.state, init: [], + snippets: [], consts: [], let_directives: [], update: [], @@ -150,7 +151,7 @@ export function Fragment(node, context) { } } - body.push(...state.let_directives, ...state.consts); + body.push(...state.snippets, ...state.let_directives, ...state.consts); if (state.async_consts && state.async_consts.thunks.length > 0) { body.push(b.var(state.async_consts.id, b.call('$.run', b.array(state.async_consts.thunks)))); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 1faf44b902..720522beaf 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -329,7 +329,7 @@ export function RegularElement(node, context) { ); /** @type {typeof state} */ - const child_state = { ...state, init: [], update: [], after_update: [] }; + const child_state = { ...state, init: [], update: [], after_update: [], snippets: [] }; for (const node of hoisted) { context.visit(node, child_state); @@ -441,6 +441,7 @@ export function RegularElement(node, context) { // Wrap children in `{...}` to avoid declaration conflicts context.state.init.push( b.block([ + ...child_state.snippets, ...child_state.init, ...element_state.init, child_state.update.length > 0 ? build_render_statement(child_state) : b.empty, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js index 1af737f05b..6c3a48c5ea 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js @@ -89,6 +89,6 @@ export function SnippetBlock(node, context) { context.state.instance_level_snippets.push(declaration); } } else { - context.state.init.push(declaration); + context.state.snippets.push(declaration); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js index d64fcda2e8..f929a3bc47 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteBoundary.js @@ -77,7 +77,7 @@ export function SvelteBoundary(node, context) { /** @type {Statement[]} */ const statements = []; - context.visit(child, { ...context.state, init: statements }); + context.visit(child, { ...context.state, snippets: statements }); const snippet = /** @type {VariableDeclaration} */ (statements[0]); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index 4651f6f733..bb72794af8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -333,7 +333,7 @@ export function build_component(node, component_name, loc, context) { // can be used as props without creating conflicts context.visit(child, { ...context.state, - init: snippet_declarations + snippets: snippet_declarations }); push_prop(b.prop('init', child.expression, child.expression)); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/_config.js new file mode 100644 index 0000000000..27f013d6d1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/main.svelte new file mode 100644 index 0000000000..70c7d64f18 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-const-same-block/main.svelte @@ -0,0 +1,4 @@ +{#if true} + {@const xx = test} + {#snippet test()}{/snippet} +{/if}