diff --git a/.changeset/nervous-flies-laugh.md b/.changeset/nervous-flies-laugh.md
new file mode 100644
index 000000000000..88c7694bcdc4
--- /dev/null
+++ b/.changeset/nervous-flies-laugh.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't replace rest props with `$$props` for excluded props
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
index f56a665de8b5..7a85b4a93aa1 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js
@@ -46,6 +46,21 @@ export function VariableDeclarator(node, context) {
: path.is_rest
? 'rest_prop'
: 'prop';
+ if (rune === '$props' && binding.kind === 'rest_prop' && node.id.type === 'ObjectPattern') {
+ const { properties } = node.id;
+ /** @type {string[]} */
+ const exclude_props = [];
+ for (const property of properties) {
+ if (property.type === 'RestElement') {
+ continue;
+ }
+ const key = /** @type {Identifier | Literal & { value: string | number }} */ (
+ property.key
+ );
+ exclude_props.push(key.type === 'Identifier' ? key.name : key.value.toString());
+ }
+ (binding.metadata ??= {}).exclude_props = exclude_props;
+ }
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js
index b01ed01bd706..b43ec7891ea7 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Identifier.js
@@ -32,7 +32,11 @@ export function Identifier(node, context) {
grand_parent?.type !== 'AssignmentExpression' &&
grand_parent?.type !== 'UpdateExpression'
) {
- return b.id('$$props');
+ const key = /** @type {Identifier} */ (parent.property);
+
+ if (!binding.metadata?.exclude_props?.includes(key.name)) {
+ return b.id('$$props');
+ }
}
}
diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js
index 0c6b64dd044d..ffccaffba393 100644
--- a/packages/svelte/src/compiler/phases/scope.js
+++ b/packages/svelte/src/compiler/phases/scope.js
@@ -122,7 +122,7 @@ export class Binding {
/**
* Additional metadata, varies per binding type
- * @type {null | { inside_rest?: boolean; is_template_declaration?: boolean }}
+ * @type {null | { inside_rest?: boolean; is_template_declaration?: boolean; exclude_props?: string[] }}
*/
metadata = null;
diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js
new file mode 100644
index 000000000000..66a42c08dbc7
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ assert.equal(target.textContent, ' false');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte
new file mode 100644
index 000000000000..40561218db35
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/component.svelte
@@ -0,0 +1,4 @@
+
+{rest.name} {'name' in rest}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte
new file mode 100644
index 000000000000..d9f6d8a21c46
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/rest-props-excludes-properties/main.svelte
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file