diff --git a/.changeset/brown-insects-burn.md b/.changeset/brown-insects-burn.md
new file mode 100644
index 000000000000..ceccc3fd9b0a
--- /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 2629379f63d9..cd3fb7a64d45 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 932d35367162..b9a8691a6b5d 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 85d8e3caffb2..bee4fcaab455 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 f33febeeb281..c134b4e1e726 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 e35b7cbe5a17..ab119e8f8049 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 a5c09747386f..b87a13253b1d 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 65cc170ce5b7..e3b46a4eef73 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 5c8ce897f4b3..5ca941fd703b 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 000000000000..2f7a7863a774
--- /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 000000000000..44e700bdd473
--- /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 000000000000..abca25bab27b
--- /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