Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-planets-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

feat: make `<svelte:component>` unnecessary in runes mode
5 changes: 4 additions & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,10 @@ const common_visitors = {
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
);

node.metadata.dynamic = binding !== null && binding.kind !== 'normal';
node.metadata.dynamic =
context.state.analysis.runes && // Svelte 4 required you to use svelte:component to switch components
binding !== null &&
(binding.kind !== 'normal' || node.name.includes('.'));
},
RenderTag(node, context) {
context.next({ ...context.state, render_tag: node });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,16 @@ function serialize_inline_component(node, component_name, context, anchor = cont

/** @param {Expression} node_id */
let fn = (node_id) => {
return b.call(component_name, node_id, props_expression);
return b.call(
// TODO We can remove this ternary once we remove legacy mode, since in runes mode dynamic components
// will be handled separately through the `$.component` function, and then the component name will
// always be referenced through just the identifier here.
node.type === 'SvelteComponent'
? component_name
: /** @type {Expression} */ (context.visit(b.member_id(component_name))),
node_id,
props_expression
);
};

if (bind_this !== null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Component1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Component2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test } from '../../test';
import { flushSync } from 'svelte';

export default test({
html: '<button>switch</button> Component1 Component1',
async test({ assert, target }) {
const btn = target.querySelector('button');

btn?.click();
flushSync();

assert.htmlEqual(target.innerHTML, '<button>switch</button> Component2 Component2');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script>
import Component1 from './Component1.svelte';
import Component2 from './Component2.svelte';

let Component = $state(Component1);
let Object = {
get component() {
return Component;
}
};
</script>

<button onclick={() => (Component = Component2)}>switch</button>

<Component />
<Object.component />

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,30 @@ In Svelte 4, doing the following triggered reactivity:

This is because the Svelte compiler treated the assignment to `foo.value` as an instruction to update anything that referenced `foo`. In Svelte 5, reactivity is determined at runtime rather than compile time, so you should define `value` as a reactive `$state` field on the `Foo` class. Wrapping `new Foo()` with `$state(...)` will have no effect — only vanilla objects and arrays are made deeply reactive.

### `<svelte:component>` is no longer necessary

In Svelte 4, components are _static_ — if you render `<Thing>`, and the value of `Thing` changes, [nothing happens](https://svelte.dev/repl/7f1fa24f0ab44c1089dcbb03568f8dfa?version=4.2.18). To make it dynamic you must use `<svelte:component>`.

This is [no longer true in Svelte 5](/#H4sIAAAAAAAAE4WQwU7DMAyGX8VESANpXe8lq9Q8AzfGobQujZQmWeJOQlXenaQB1sM0bnG-379_e2GDVOhZ9bYw3U7IKtZYy_aMvmwq_AUVYay9mV2XfrjvnLRUn_SJ5GSNI2hgcGaC3aFsDrlh97LB4g-LLY4ChQSvo9SfcIRHTy3h03NEvLzO0Nyjwo7gQ-q-urRqxuOy9oQ1AjeWpNHwQ5pQN7zMf7e4CLXY8Dhpdc-THooCaESP0DoEPM8ydqEmKIqkzUnL9MxrVJ2JG-qkoFH631xREg82mV4OEntWkZsx7K_3vXtdm_LbuwbiHwNx2-A9fANfmchv7QEAAA==):

```svelte
<script>
import A from './A.svelte';
import B from './B.svelte';

let Thing = $state();
</script>

<select bind:value={Thing}>
<option value={A}>A</option>
<option value={B}>B</option>
</select>

<!-- these are equivalent -->
<Thing />
<svelte:component this={Thing} />
Copy link

@connectkushal connectkushal Nov 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got confused by this initially and suggest that this should be a comment as to highlight that it is no longer needed. Adds more clarity.

```

## Other breaking changes

### Stricter `@const` assignment validation
Expand Down