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