Skip to content

fix: add $set and $on methods in legacy compat mode #10642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 26, 2024
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/tough-radios-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: add `$set` and `$on` methods in legacy compat mode
6 changes: 5 additions & 1 deletion packages/svelte/src/compiler/phases/2-analyze/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,11 @@ export function analyze_component(root, options) {
uses_component_bindings: false,
custom_element: options.customElement,
inject_styles: options.css === 'injected' || !!options.customElement,
accessors: options.customElement ? true : !!options.accessors,
accessors: options.customElement
? true
: !!options.accessors ||
// because $set method needs accessors
!!options.legacy?.componentApi,
reactive_statements: new Map(),
binding_groups: new Map(),
slot_names: new Set(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,60 @@ export function client_component(source, analysis, options) {
}
}

if (options.legacy.componentApi) {
properties.push(
b.init('$set', b.id('$.update_legacy_props')),
b.init(
'$on',
b.arrow(
[b.id('$$event_name'), b.id('$$event_cb')],
b.call(
'$.add_legacy_event_listener',
b.id('$$props'),
b.id('$$event_name'),
b.id('$$event_cb')
)
)
)
);
} else if (options.dev) {
properties.push(
b.init(
'$set',
b.thunk(
b.block([
b.throw_error(
`The component shape you get when doing bind:this changed. Updating its properties via $set is no longer valid in Svelte 5. ` +
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
)
])
)
),
b.init(
'$on',
b.thunk(
b.block([
b.throw_error(
`The component shape you get when doing bind:this changed. Listening to events via $on is no longer valid in Svelte 5. ` +
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
)
])
)
),
b.init(
'$destroy',
b.thunk(
b.block([
b.throw_error(
`The component shape you get when doing bind:this changed. Destroying such a component via $destroy is no longer valid in Svelte 5. ` +
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
)
])
)
)
);
}

const push_args = [b.id('$$props'), b.literal(analysis.runes)];
if (options.dev) push_args.push(b.id(analysis.name));

Expand Down
27 changes: 27 additions & 0 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -2929,3 +2929,30 @@ export function bubble_event($$props, event) {
fn.call(this, event);
}
}

/**
* Used to simulate `$on` on a component instance when `legacy.componentApi` is `true`
* @param {Record<string, any>} $$props
* @param {string} event_name
* @param {Function} event_callback
*/
export function add_legacy_event_listener($$props, event_name, event_callback) {
$$props.$$events ||= {};
$$props.$$events[event_name] ||= [];
$$props.$$events[event_name].push(event_callback);
}

/**
* Used to simulate `$set` on a component instance when `legacy.componentApi` is `true`.
* Needs component accessors so that it can call the setter of the prop. Therefore doesn't
* work for updating props in `$$props` or `$$restProps`.
* @this {Record<string, any>}
* @param {Record<string, any>} $$new_props
*/
export function update_legacy_props($$new_props) {
for (const key in $$new_props) {
if (key in this) {
this[key] = $$new_props[key];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { tick } from 'svelte';
import { test } from '../../test';

export default test({
compileOptions: {
legacy: {
componentApi: true
}
},
html: '<button>0</button>',
async test({ assert, target }) {
const button = target.querySelector('button');
await button?.click();
await tick();
assert.htmlEqual(target.innerHTML, '<button>1</button>');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script>
import Sub from './sub.svelte';
import { onMount } from 'svelte';

let count = 0;
let component;

onMount(() => {
component.$on('increment', (e) => {
count += e.detail;
component.$set({ count });
});
});
</script>

<Sub bind:this={component} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script>
import { createEventDispatcher } from 'svelte';

export let count = 0;
const dispatch = createEventDispatcher();
</script>

<button on:click={() => dispatch('increment', 1)}>{count}</button>
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import App from './App.svelte'
export default app;
```

If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component).
If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`.

### Server API changes

Expand Down