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