inherits the scope above the component unless the component is a named slot itself, because slots are hella weird
- scopes.set(child, is_default_slot ? state.scope : scope);
- visit(child, { scope: is_default_slot ? state.scope : scope });
- } else {
- if (child.type === 'ExpressionTag') {
- // expression tag is a special case — we don't visit it directly, but via process_children,
- // so we need to set the scope on the expression rather than the tag itself
- scopes.set(child.expression, scope);
- } else {
- scopes.set(child, scope);
- }
-
- visit(child, { scope });
- }
- }
+ Component: (node, context) => {
+ context.state.scope.reference(b.id(node.name), context.path);
+ Component(node, context);
},
+ SvelteSelf: Component,
+ SvelteComponent: Component,
// updates
AssignmentExpression(node, { state, next }) {
@@ -532,7 +516,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
references_within.add(id);
}
}
- scopes.set(node.expression, state.scope);
// context and children are a new scope
const scope = state.scope.child();
diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts
index 47dc9d7eb607..f7d111a267d6 100644
--- a/packages/svelte/src/compiler/types/index.d.ts
+++ b/packages/svelte/src/compiler/types/index.d.ts
@@ -271,7 +271,6 @@ export interface Binding {
* - `snippet`: A snippet parameter
* - `store_sub`: A $store value
* - `legacy_reactive`: A `$:` declaration
- * - `legacy_reactive_import`: An imported binding that is mutated inside the component
*/
kind:
| 'normal'
@@ -284,8 +283,7 @@ export interface Binding {
| 'each'
| 'snippet'
| 'store_sub'
- | 'legacy_reactive'
- | 'legacy_reactive_import';
+ | 'legacy_reactive';
declaration_kind: DeclarationKind;
/**
* What the value was initialized with.
diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts
index 86341d07d007..20fdb25ee418 100644
--- a/packages/svelte/src/compiler/types/template.d.ts
+++ b/packages/svelte/src/compiler/types/template.d.ts
@@ -16,6 +16,7 @@ import type {
ChainExpression,
SimpleCallExpression
} from 'estree';
+import type { Scope } from '../phases/scope';
export interface BaseNode {
type: string;
@@ -275,6 +276,7 @@ interface BaseElement extends BaseNode {
export interface Component extends BaseElement {
type: 'Component';
metadata: {
+ scopes: Record
;
dynamic: boolean;
};
}
@@ -311,6 +313,9 @@ export interface SvelteComponent extends BaseElement {
type: 'SvelteComponent';
name: 'svelte:component';
expression: Expression;
+ metadata: {
+ scopes: Record;
+ };
}
interface SvelteDocument extends BaseElement {
@@ -356,6 +361,9 @@ export interface SvelteOptionsRaw extends BaseElement {
export interface SvelteSelf extends BaseElement {
type: 'SvelteSelf';
name: 'svelte:self';
+ metadata: {
+ scopes: Record;
+ };
}
interface SvelteWindow extends BaseElement {
diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js
index 95b5d2fb2d7e..47bbdb945fb4 100644
--- a/packages/svelte/src/compiler/utils/ast.js
+++ b/packages/svelte/src/compiler/utils/ast.js
@@ -563,3 +563,15 @@ export function build_fallback(expression, fallback) {
? b.await(b.call('$.fallback', expression, b.thunk(fallback, true), b.true))
: b.call('$.fallback', expression, b.thunk(fallback), b.true);
}
+
+/**
+ * @param {ESTree.AssignmentOperator} operator
+ * @param {ESTree.Identifier | ESTree.MemberExpression} left
+ * @param {ESTree.Expression} right
+ */
+export function build_assignment_value(operator, left, right) {
+ return operator === '='
+ ? right
+ : // turn something like x += 1 into x = x + 1
+ b.binary(/** @type {ESTree.BinaryOperator} */ (operator.slice(0, -1)), left, right);
+}
diff --git a/packages/svelte/src/compiler/utils/slot.js b/packages/svelte/src/compiler/utils/slot.js
new file mode 100644
index 000000000000..1c05ec8ee6c3
--- /dev/null
+++ b/packages/svelte/src/compiler/utils/slot.js
@@ -0,0 +1,20 @@
+/** @import { SvelteNode } from '#compiler' */
+import { is_element_node } from '../phases/nodes.js';
+import { is_text_attribute } from './ast.js';
+
+/**
+ * @param {SvelteNode} node
+ */
+export function determine_slot(node) {
+ if (!is_element_node(node)) return null;
+
+ for (const attribute of node.attributes) {
+ if (attribute.type !== 'Attribute') continue;
+ if (attribute.name !== 'slot') continue;
+ if (!is_text_attribute(attribute)) continue;
+
+ return /** @type {string} */ (attribute.value[0].data);
+ }
+
+ return null;
+}
diff --git a/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/_config.js b/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/_config.js
new file mode 100644
index 000000000000..a00605a634a5
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/_config.js
@@ -0,0 +1,27 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: `
+
+
+
+ 1, 2, 3
+ `,
+
+ test({ assert, target }) {
+ let buttons = target.querySelectorAll('button');
+
+ flushSync(() => buttons[2].click());
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+
+
+ 1, 2, 4
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/main.svelte b/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/main.svelte
new file mode 100644
index 000000000000..0abded02ff93
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/each-blocks-update/main.svelte
@@ -0,0 +1,9 @@
+
+
+{#each arr as n}
+
+{/each}
+
+{arr.join(', ')}
diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js
new file mode 100644
index 000000000000..9400b5271840
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/client/index.svelte.js
@@ -0,0 +1,12 @@
+/* index.svelte.js generated by Svelte VERSION */
+import * as $ from "svelte/internal/client";
+
+let a = $.source(1);
+let b = $.source(2);
+
+export function update(array) {
+ (
+ $.set(a, $.proxy(array[0])),
+ $.set(b, $.proxy(array[1]))
+ );
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js
new file mode 100644
index 000000000000..846ed4845858
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/_expected/server/index.svelte.js
@@ -0,0 +1,9 @@
+/* index.svelte.js generated by Svelte VERSION */
+import * as $ from "svelte/internal/server";
+
+let a = 1;
+let b = 2;
+
+export function update(array) {
+ [a, b] = array;
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/destructured-assignments/index.svelte.js b/packages/svelte/tests/snapshot/samples/destructured-assignments/index.svelte.js
new file mode 100644
index 000000000000..9c0da7558a50
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/destructured-assignments/index.svelte.js
@@ -0,0 +1,6 @@
+let a = $state(1);
+let b = $state(2);
+
+export function update(array) {
+ [a, b] = array;
+}
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 969f89222da3..df012254f64f 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -920,7 +920,6 @@ declare module 'svelte/compiler' {
* - `snippet`: A snippet parameter
* - `store_sub`: A $store value
* - `legacy_reactive`: A `$:` declaration
- * - `legacy_reactive_import`: An imported binding that is mutated inside the component
*/
kind:
| 'normal'
@@ -933,8 +932,7 @@ declare module 'svelte/compiler' {
| 'each'
| 'snippet'
| 'store_sub'
- | 'legacy_reactive'
- | 'legacy_reactive_import';
+ | 'legacy_reactive';
declaration_kind: DeclarationKind;
/**
* What the value was initialized with.
@@ -1733,6 +1731,7 @@ declare module 'svelte/compiler' {
interface Component extends BaseElement {
type: 'Component';
metadata: {
+ scopes: Record;
dynamic: boolean;
};
}
@@ -1769,6 +1768,9 @@ declare module 'svelte/compiler' {
type: 'SvelteComponent';
name: 'svelte:component';
expression: Expression;
+ metadata: {
+ scopes: Record;
+ };
}
interface SvelteDocument extends BaseElement {
@@ -1814,6 +1816,9 @@ declare module 'svelte/compiler' {
interface SvelteSelf extends BaseElement {
type: 'SvelteSelf';
name: 'svelte:self';
+ metadata: {
+ scopes: Record;
+ };
}
interface SvelteWindow extends BaseElement {