Skip to content

Commit dca8861

Browse files
authored
feat: better error for bind:this legacy API usage (#11498)
1 parent 85d6805 commit dca8861

File tree

7 files changed

+52
-36
lines changed

7 files changed

+52
-36
lines changed

.changeset/three-buses-sleep.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: better error for `bind:this` legacy API usage

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
> A component is attempting to bind to a non-bindable property `%key%` belonging to %component% (i.e. `<%name% bind:%key%={...}>`). To mark a property as bindable: `let { %key% = $bindable() } = $props()`
1212
13+
## component_api_changed
14+
15+
> %parent% called `%method%` on an instance of %component%, which 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
16+
1317
## each_key_duplicate
1418

1519
> Keyed each block has duplicate key at indexes %a% and %b%

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

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ export function client_component(source, analysis, options) {
232232
group_binding_declarations.push(b.const(group.name, b.array([])));
233233
}
234234

235+
/** @type {Array<import('estree').Property | import('estree').SpreadElement>} */
235236
const component_returned_object = analysis.exports.map(({ name, alias }) => {
236237
const expression = serialize_get_binding(b.id(name), instance_state);
237238

@@ -310,41 +311,7 @@ export function client_component(source, analysis, options) {
310311
)
311312
);
312313
} else if (options.dev) {
313-
component_returned_object.push(
314-
b.init(
315-
'$set',
316-
b.thunk(
317-
b.block([
318-
b.throw_error(
319-
`The component shape you get when doing bind:this changed. Updating its properties via $set is no longer valid in Svelte 5. ` +
320-
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
321-
)
322-
])
323-
)
324-
),
325-
b.init(
326-
'$on',
327-
b.thunk(
328-
b.block([
329-
b.throw_error(
330-
`The component shape you get when doing bind:this changed. Listening to events via $on is no longer valid in Svelte 5. ` +
331-
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
332-
)
333-
])
334-
)
335-
),
336-
b.init(
337-
'$destroy',
338-
b.thunk(
339-
b.block([
340-
b.throw_error(
341-
`The component shape you get when doing bind:this changed. Destroying such a component via $destroy is no longer valid in Svelte 5. ` +
342-
'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information'
343-
)
344-
])
345-
)
346-
)
347-
);
314+
component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
348315
}
349316

350317
const push_args = [b.id('$$props'), b.literal(analysis.runes)];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as e from '../errors.js';
2+
import { current_component_context } from '../runtime.js';
3+
import { get_component } from './ownership.js';
4+
5+
export function legacy_api() {
6+
const component = current_component_context?.function;
7+
8+
/** @param {string} method */
9+
function error(method) {
10+
// @ts-expect-error
11+
const parent = get_component()?.filename ?? 'Something';
12+
e.component_api_changed(parent, method, component.filename);
13+
}
14+
15+
return {
16+
$destroy: () => error('$destroy()'),
17+
$on: () => error('$on(...)'),
18+
$set: () => error('$set(...)')
19+
};
20+
}

packages/svelte/src/internal/client/dev/ownership.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function get_stack() {
3737
* Determines which `.svelte` component is responsible for a given state change
3838
* @returns {Function | null}
3939
*/
40-
function get_component() {
40+
export function get_component() {
4141
// first 4 lines are svelte internals; adjust this number if we change the internal call stack
4242
const stack = get_stack()?.slice(4);
4343
if (!stack) return null;

packages/svelte/src/internal/client/errors.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ export function bind_not_bindable(key, component, name) {
5656
}
5757
}
5858

59+
/**
60+
* %parent% called `%method%` on an instance of %component%, which 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
61+
* @param {string} parent
62+
* @param {string} method
63+
* @param {string} component
64+
* @returns {never}
65+
*/
66+
export function component_api_changed(parent, method, component) {
67+
if (DEV) {
68+
const error = new Error(`${"component_api_changed"}\n${`${parent} called \`${method}\` on an instance of ${component}, which 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`}`);
69+
70+
error.name = 'Svelte error';
71+
throw error;
72+
} else {
73+
// TODO print a link to the documentation
74+
throw new Error("component_api_changed");
75+
}
76+
}
77+
5978
/**
6079
* Keyed each block has duplicate key `%value%` at indexes %a% and %b%
6180
* @param {string} a

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export {
66
mark_module_end,
77
add_owner_effect
88
} from './dev/ownership.js';
9+
export { legacy_api } from './dev/legacy.js';
910
export { inspect } from './dev/inspect.js';
1011
export { await_block as await } from './dom/blocks/await.js';
1112
export { if_block as if } from './dom/blocks/if.js';

0 commit comments

Comments
 (0)