From 9a488d6b25d41d594ee73582e56aff123eec49e9 Mon Sep 17 00:00:00 2001
From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com>
Date: Sun, 19 Oct 2025 18:35:38 -0700
Subject: [PATCH] fix: place `let:` declarations before `{@const}` declarations
 (#16985)
* fix: place `let:` declarations before `{@const}` declarations
* lint
* fix
---
 .changeset/brown-insects-burn.md              |  5 +++
 .../3-transform/client/transform-client.js    |  1 +
 .../phases/3-transform/client/types.d.ts      |  2 +
 .../3-transform/client/visitors/Fragment.js   |  3 +-
 .../client/visitors/LetDirective.js           | 38 ++++++++++---------
 .../client/visitors/RegularElement.js         |  2 +-
 .../client/visitors/SlotElement.js            |  2 +-
 .../client/visitors/SvelteFragment.js         |  2 +-
 .../client/visitors/shared/component.js       |  4 +-
 .../let-directive-and-const-tag/_config.js    |  5 +++
 .../component.svelte                          |  1 +
 .../let-directive-and-const-tag/main.svelte   |  7 ++++
 12 files changed, 49 insertions(+), 23 deletions(-)
 create mode 100644 .changeset/brown-insects-burn.md
 create mode 100644 packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/_config.js
 create mode 100644 packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/component.svelte
 create mode 100644 packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/main.svelte
diff --git a/.changeset/brown-insects-burn.md b/.changeset/brown-insects-burn.md
new file mode 100644
index 0000000000..ceccc3fd9b
--- /dev/null
+++ b/.changeset/brown-insects-burn.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: place `let:` declarations before `{@const}` declarations
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 2629379f63..cd3fb7a64d 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
@@ -172,6 +172,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),
+		let_directives: /** @type {any} */ (null),
 		update: /** @type {any} */ (null),
 		after_update: /** @type {any} */ (null),
 		template: /** @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 932d353671..b9a8691a6b 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
@@ -54,6 +54,8 @@ export interface ComponentClientTransformState extends ClientTransformState {
 	readonly after_update: Statement[];
 	/** Transformed `{@const }` declarations */
 	readonly consts: Statement[];
+	/** Transformed `let:` directives */
+	readonly let_directives: Statement[];
 	/** Memoized expressions */
 	readonly memoizer: Memoizer;
 	/** The HTML template string */
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 85d8e3caff..bee4fcaab4 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
@@ -63,6 +63,7 @@ export function Fragment(node, context) {
 		...context.state,
 		init: [],
 		consts: [],
+		let_directives: [],
 		update: [],
 		after_update: [],
 		memoizer: new Memoizer(),
@@ -150,7 +151,7 @@ export function Fragment(node, context) {
 		}
 	}
 
-	body.push(...state.consts);
+	body.push(...state.let_directives, ...state.consts);
 
 	if (has_await) {
 		body.push(b.if(b.call('$.aborted'), b.return()));
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
index f33febeeb2..c134b4e1e7 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/LetDirective.js
@@ -21,22 +21,24 @@ export function LetDirective(node, context) {
 			};
 		}
 
-		return b.const(
-			name,
-			b.call(
-				'$.derived',
-				b.thunk(
-					b.block([
-						b.let(
-							/** @type {Expression} */ (node.expression).type === 'ObjectExpression'
-								? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
-									b.object_pattern(node.expression.properties)
-								: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
-									b.array_pattern(node.expression.elements),
-							b.member(b.id('$$slotProps'), node.name)
-						),
-						b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
-					])
+		context.state.let_directives.push(
+			b.const(
+				name,
+				b.call(
+					'$.derived',
+					b.thunk(
+						b.block([
+							b.let(
+								/** @type {Expression} */ (node.expression).type === 'ObjectExpression'
+									? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
+										b.object_pattern(node.expression.properties)
+									: // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine
+										b.array_pattern(node.expression.elements),
+								b.member(b.id('$$slotProps'), node.name)
+							),
+							b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node))))
+						])
+					)
 				)
 			)
 		);
@@ -46,6 +48,8 @@ export function LetDirective(node, context) {
 			read: (node) => b.call('$.get', node)
 		};
 
-		return b.const(name, create_derived(context.state, b.member(b.id('$$slotProps'), node.name)));
+		context.state.let_directives.push(
+			b.const(name, create_derived(context.state, b.member(b.id('$$slotProps'), node.name)))
+		);
 	}
 }
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 e35b7cbe5a..ab119e8f80 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
@@ -106,7 +106,7 @@ export function RegularElement(node, context) {
 
 			case 'LetDirective':
 				// visit let directives before everything else, to set state
-				lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
+				context.visit(attribute, { ...context.state, let_directives: lets });
 				break;
 
 			case 'OnDirective':
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
index a5c0974738..b87a13253b 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SlotElement.js
@@ -49,7 +49,7 @@ export function SlotElement(node, context) {
 				}
 			}
 		} else if (attribute.type === 'LetDirective') {
-			lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
+			context.visit(attribute, { ...context.state, let_directives: lets });
 		}
 	}
 
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteFragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteFragment.js
index 65cc170ce5..e3b46a4eef 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteFragment.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SvelteFragment.js
@@ -9,7 +9,7 @@
 export function SvelteFragment(node, context) {
 	for (const attribute of node.attributes) {
 		if (attribute.type === 'LetDirective') {
-			context.state.init.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
+			context.visit(attribute);
 		}
 	}
 
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 5c8ce897f4..5ca941fd70 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
@@ -101,7 +101,7 @@ export function build_component(node, component_name, context) {
 	if (slot_scope_applies_to_itself) {
 		for (const attribute of node.attributes) {
 			if (attribute.type === 'LetDirective') {
-				lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
+				context.visit(attribute, { ...context.state, let_directives: lets });
 			}
 		}
 	}
@@ -109,7 +109,7 @@ export function build_component(node, component_name, context) {
 	for (const attribute of node.attributes) {
 		if (attribute.type === 'LetDirective') {
 			if (!slot_scope_applies_to_itself) {
-				lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute, states.default)));
+				context.visit(attribute, { ...states.default, let_directives: lets });
 			}
 		} else if (attribute.type === 'OnDirective') {
 			if (!attribute.expression) {
diff --git a/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/_config.js b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/_config.js
new file mode 100644
index 0000000000..2f7a7863a7
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+	html: 'foo'
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/component.svelte b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/component.svelte
new file mode 100644
index 0000000000..44e700bdd4
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/component.svelte
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/main.svelte b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/main.svelte
new file mode 100644
index 0000000000..abca25bab2
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/let-directive-and-const-tag/main.svelte
@@ -0,0 +1,7 @@
+
+
+    {@const thing = data}
+    {thing}
+
\ No newline at end of file