Skip to content

Commit 8be7dd5

Browse files
feat: make <svelte:component> unnecessary in runes mode (#12646)
* feat: make `<svelte:component>` unnecessary in runes mode In Svelte 4, writing `<Component />` meant that the component instance is static. If you made the variable `Component` a reactive state variable and updated the component value, the component would not be reinstantiated with the new value - you had to use `<svelte:component>` for that. One reason was that having a dynamic component was more overhead, which is no longer the case in Svelte 5. We can therefore reduce the potential API surface area (by maybe deprecating `<svelte:component>` in the future) by allowing Svelte to recognize when a component variable is potentially dynamic. It turned out that this was already mostly the case. This PR fixes one case where it wasn't, and fixes another where this was wrongfully applied in legacy mode. * we already have this function * add interactive demos * changeset --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 00e8ebd commit 8be7dd5

File tree

11 files changed

+75
-73
lines changed

11 files changed

+75
-73
lines changed

.changeset/warm-planets-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: make `<svelte:component>` unnecessary in runes mode

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,10 @@ const common_visitors = {
15511551
node.name.includes('.') ? node.name.slice(0, node.name.indexOf('.')) : node.name
15521552
);
15531553

1554-
node.metadata.dynamic = binding !== null && binding.kind !== 'normal';
1554+
node.metadata.dynamic =
1555+
context.state.analysis.runes && // Svelte 4 required you to use svelte:component to switch components
1556+
binding !== null &&
1557+
(binding.kind !== 'normal' || node.name.includes('.'));
15551558
},
15561559
RenderTag(node, context) {
15571560
context.next({ ...context.state, render_tag: node });

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,16 @@ function serialize_inline_component(node, component_name, context, anchor = cont
933933

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

939948
if (bind_this !== null) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Component1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Component2
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
4+
export default test({
5+
html: '<button>switch</button> Component1 Component1',
6+
async test({ assert, target }) {
7+
const btn = target.querySelector('button');
8+
9+
btn?.click();
10+
flushSync();
11+
12+
assert.htmlEqual(target.innerHTML, '<button>switch</button> Component2 Component2');
13+
}
14+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
import Component1 from './Component1.svelte';
3+
import Component2 from './Component2.svelte';
4+
5+
let Component = $state(Component1);
6+
let Object = {
7+
get component() {
8+
return Component;
9+
}
10+
};
11+
</script>
12+
13+
<button onclick={() => (Component = Component2)}>switch</button>
14+
15+
<Component />
16+
<Object.component />

packages/svelte/tests/validator/samples/component-dynamic/_config.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/svelte/tests/validator/samples/component-dynamic/input.svelte

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/svelte/tests/validator/samples/component-dynamic/warnings.json

Lines changed: 0 additions & 38 deletions
This file was deleted.

sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,30 @@ In Svelte 4, doing the following triggered reactivity:
198198

199199
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.
200200

201+
### `<svelte:component>` is no longer necessary
202+
203+
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>`.
204+
205+
This is [no longer true in Svelte 5](/#H4sIAAAAAAAAE4WQwU7DMAyGX8VESANpXe8lq9Q8AzfGobQujZQmWeJOQlXenaQB1sM0bnG-379_e2GDVOhZ9bYw3U7IKtZYy_aMvmwq_AUVYay9mV2XfrjvnLRUn_SJ5GSNI2hgcGaC3aFsDrlh97LB4g-LLY4ChQSvo9SfcIRHTy3h03NEvLzO0Nyjwo7gQ-q-urRqxuOy9oQ1AjeWpNHwQ5pQN7zMf7e4CLXY8Dhpdc-THooCaESP0DoEPM8ydqEmKIqkzUnL9MxrVJ2JG-qkoFH631xREg82mV4OEntWkZsx7K_3vXtdm_LbuwbiHwNx2-A9fANfmchv7QEAAA==):
206+
207+
```svelte
208+
<script>
209+
import A from './A.svelte';
210+
import B from './B.svelte';
211+
212+
let Thing = $state();
213+
</script>
214+
215+
<select bind:value={Thing}>
216+
<option value={A}>A</option>
217+
<option value={B}>B</option>
218+
</select>
219+
220+
<!-- these are equivalent -->
221+
<Thing />
222+
<svelte:component this={Thing} />
223+
```
224+
201225
## Other breaking changes
202226

203227
### Stricter `@const` assignment validation

0 commit comments

Comments
 (0)