Repository: sveltejs/svelte (JavaScript)

Issue:
Double declaration of $$slots when using $props() with named slots
### Describe the bug

## Bug Report: Double declaration of $$slots when using $props() with named slots

When using Svelte 5's runes and `$props()` in a component that has named slots and spreads remaining props, the compiler generates code that declares `$$slots` twice, causing a build error.

### Expected Behavior
The component should compile successfully since `$$slots` is a special Svelte variable that should be handled by the runtime.

### Actual Behavior
The compiled output tries to declare `$$slots` twice:
1. First from `sanitize_slots($$props)`
2. Then again when destructuring from `$props()`

### Additional Context
- This only occurs when using named slots (`<slot name="x">`)
- This happens when using `$props()` with runes enabled
- The issue appears to be in how the compiler handles the interaction between runes' `$props()` and Svelte's built-in slot handling

### Workarounds
There are two ways to work around this issue:

Don't use prop spreading in components with named slots:
```
<script>
  const { class: className = '' } = $props();
  // Don't use ...rest
</script>

<div class="slot-test {className}">
  <slot name="header" />
</div>
```

Don't use $props() in components that need both named slots and prop spreading:
```
<script>
  export let className = '';
  // Use traditional props instead of $props()
</script>

<div {...$$restProps} class="slot-test {className}">
  <slot name="header" />
</div>
```

### Reproduction

1. Create a component that uses runes, `$props()`, and named slots:

```svelte
<!-- SlotTest.svelte -->
<svelte:options runes={true} />

<script lang="ts">
  const { class: className = '', ...rest } = $props();
</script>

<div {...rest} class="slot-test {className}">
  {#if $$slots.header}
    <div class="header">
      <slot name="header" />
    </div>
  {/if}
  
  {#if $$slots.footer}
    <div class="footer">
      <slot name="footer" />
    </div>
  {/if}
</div>
```

2. Use the component with named slots:

```svelte
<!-- App.svelte -->
<script>
  import SlotTest from './SlotTest.svelte';
</script>

<SlotTest data-testid="test">
  <svelte:fragment slot="header">
    Header Content
  </svelte:fragment>
  <svelte:fragment slot="footer">
    Footer Content
  </svelte:fragment>
</SlotTest>
```

### Logs

```shell
error during build:
Identifier "$$slots" has already been declared

 7: export default function SlotTest($$payload, $$props) {
 8:   const $$slots = $.sanitize_slots($$props);
 9:   const { $$slots, $$events, ...props } = $$props;
              ^
```

### System Info

```shell
System:
  OS: macOS 15.4.1
  CPU: (12) arm64 Apple M2 Max
  Memory: 3.31 GB / 64.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 20.17.0 - ~/.nvm/versions/node/v20.17.0/bin/node
  Yarn: 1.22.22 - ~/.yarn/bin/yarn
  npm: 10.8.2 - ~/.nvm/versions/node/v20.17.0/bin/npm
  pnpm: 10.8.1 - ~/.nvm/versions/node/v20.17.0/bin/pnpm
  Watchman: 2025.04.07.00 - /opt/homebrew/bin/watchman
Browsers:
  Brave Browser: 130.1.71.118
  Chrome: 135.0.7049.115
  Safari: 18.4
npmPackages:
  svelte: ^5.20.2 => 5.25.10
```

### Severity

blocking an upgrade


Discussion:
[More simple reproduction](https://svelte.dev/playground/hello-world?version=5.28.2#H4sIAAAAAAAAA23MQQrCMBAF0KuEsYsK0uI2VsGdd7AuYjOBQExCZrRKyN1NRVy5m3n8_zN4dUOQcELngphDclq0qC2jXsMGjHVIIM8Z-BWX3ALVv61jjB090PFiV0X4z6fgGT3XGRhoSjbyYfRCVCYWMYVIYi-az9Gud6Mf-l9q9HlljWgacoGpMyGUbe6tKXWW8ckgOd2xXOqnrJut1yCNcoTlDUalyZTZAAAA) - look at the JS output for the server.
```
Identifier "$$slots" has already been declared

const { $$slots, $$events, ...props } = $$props;
```

I am actually building it fine in Sveltekit and seeing it in Storybook. But when I import it into another Sveltekit that's the same version that's when I'm seeing the already been declared error.

```
{"environment":"development","level":"ERROR","message":"Identifier '$$slots' has already been declared\n Status: undefined\n Stack: SyntaxError: Identifier '$$slots' has already been declared\n    at new AsyncFunction (<anonymous>)\n    at ESModulesEvaluator.runInlinedModule (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1049:11)\n    at SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1271:82)\n    at SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/chunks/dep-Bid9ssRr.js:30916:35)\n    at SSRCompatModuleRunner.cachedRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1166:28)\n    at request (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1215:79)\n    at async eval (/Users/tvaeth/Development/public-web-renderer/packages/design-system/dist/index.js:8:1)\n    at async ESModulesEvaluator.runInlinedModule (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1049:5)\n    at async SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1271:61)\n    at async SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/chunks/dep-Bid9ssRr.js:30916:23)","timestamp":"2025-04-26T15:16:41.306Z","speleo.trace_id":""}
{"environment":"development","level":"ERROR","message":"Identifier '$$slots' has already been declared\n Status: undefined\n Stack: SyntaxError: Identifier '$$slots' has already been declared\n    at new AsyncFunction (<anonymous>)\n    at ESModulesEvaluator.runInlinedModule (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1049:11)\n    at SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1271:82)\n    at SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/chunks/dep-Bid9ssRr.js:30916:35)\n    at SSRCompatModuleRunner.cachedRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1166:28)\n    at request (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1215:79)\n    at async eval (/Users/tvaeth/Development/public-web-renderer/packages/design-system/dist/index.js:8:1)\n    at async ESModulesEvaluator.runInlinedModule (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1049:5)\n    at async SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/module-runner.js:1271:61)\n    at async SSRCompatModuleRunner.directRequest (file:///Users/tvaeth/Development/public-web-renderer/node_modules/.pnpm/vite@6.2.6_@types+node@20.17.1_sass@1.69.5_terser@5.39.0_tsx@4.19.1_yaml@2.7.0/node_modules/vite/dist/node/chunks/dep-Bid9ssRr.js:30916:23)","timestamp":"2025-04-26T15:16:41.306Z","speleo.trace_id":""}
```



Patch:
diff --git a/.changeset/ten-plants-carry.md b/.changeset/ten-plants-carry.md
new file mode 100644
index 000000000000..91ae5889cf22
--- /dev/null
+++ b/.changeset/ten-plants-carry.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't redeclare `$$slots`
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
index 1f2bd3e2b1db..b76455b5c149 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/VariableDeclaration.js
@@ -1,6 +1,7 @@
 /** @import { VariableDeclaration, VariableDeclarator, Expression, CallExpression, Pattern, Identifier } from 'estree' */
 /** @import { Binding } from '#compiler' */
 /** @import { Context } from '../types.js' */
+/** @import { ComponentAnalysis } from '../../../types.js' */
 /** @import { Scope } from '../../../scope.js' */
 import { build_fallback, extract_paths } from '../../../../utils/ast.js';
 import * as b from '#compiler/builders';
@@ -50,20 +51,26 @@ export function VariableDeclaration(node, context) {
 						}
 					}
 				});
+
+				// if `$$slots` is declared separately, deconflict
+				const slots_name = /** @type {ComponentAnalysis} */ (context.state.analysis).uses_slots
+					? b.id('$$slots_')
+					: b.id('$$slots');
+
 				if (id.type === 'ObjectPattern' && has_rest) {
 					// If a rest pattern is used within an object pattern, we need to ensure we don't expose $$slots or $$events
 					id.properties.splice(
 						id.properties.length - 1,
 						0,
 						// @ts-ignore
-						b.prop('init', b.id('$$slots'), b.id('$$slots')),
+						b.prop('init', b.id('$$slots'), slots_name),
 						b.prop('init', b.id('$$events'), b.id('$$events'))
 					);
 				} else if (id.type === 'Identifier') {
 					// If $props is referenced as an identifier, we need to ensure we don't expose $$slots or $$events as properties
 					// on the identifier reference
 					id = b.object_pattern([
-						b.prop('init', b.id('$$slots'), b.id('$$slots')),
+						b.prop('init', b.id('$$slots'), slots_name),
 						b.prop('init', b.id('$$events'), b.id('$$events')),
 						b.rest(b.id(id.name))
 					]);


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/props-and-slots/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-and-slots/Child.svelte
new file mode 100644
index 000000000000..a2e7d6d8a46a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-and-slots/Child.svelte
@@ -0,0 +1,9 @@
+<script>
+	const props = $props();
+</script>
+
+<p>{Object.keys(props)}</p>
+
+{#if $$slots.foo}
+	<p>foo exists</p>
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/props-and-slots/_config.js b/packages/svelte/tests/runtime-runes/samples/props-and-slots/_config.js
new file mode 100644
index 000000000000..3f6fb4143c27
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-and-slots/_config.js
@@ -0,0 +1,8 @@
+import { test } from '../../test';
+
+export default test({
+	html: `
+		<p>a</p>
+		<p>foo exists</p>
+	`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/props-and-slots/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-and-slots/main.svelte
new file mode 100644
index 000000000000..3535da713272
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/props-and-slots/main.svelte
@@ -0,0 +1,7 @@
+<script>
+	import Child from './Child.svelte';
+</script>
+
+<Child a="b">
+	<div slot="foo">foo</div>
+</Child>