feat: better error for `bind:this` legacy API usage (#11498)

pull/11512/head
Rich Harris 8 months ago committed by GitHub
parent 85d680582b
commit dca8861c5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: better error for `bind:this` legacy API usage

@ -10,6 +10,10 @@
> 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()` > 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()`
## component_api_changed
> %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
## each_key_duplicate ## each_key_duplicate
> Keyed each block has duplicate key at indexes %a% and %b% > Keyed each block has duplicate key at indexes %a% and %b%

@ -232,6 +232,7 @@ export function client_component(source, analysis, options) {
group_binding_declarations.push(b.const(group.name, b.array([]))); group_binding_declarations.push(b.const(group.name, b.array([])));
} }
/** @type {Array<import('estree').Property | import('estree').SpreadElement>} */
const component_returned_object = analysis.exports.map(({ name, alias }) => { const component_returned_object = analysis.exports.map(({ name, alias }) => {
const expression = serialize_get_binding(b.id(name), instance_state); const expression = serialize_get_binding(b.id(name), instance_state);
@ -310,41 +311,7 @@ export function client_component(source, analysis, options) {
) )
); );
} else if (options.dev) { } else if (options.dev) {
component_returned_object.push( component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
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)]; const push_args = [b.id('$$props'), b.literal(analysis.runes)];

@ -0,0 +1,20 @@
import * as e from '../errors.js';
import { current_component_context } from '../runtime.js';
import { get_component } from './ownership.js';
export function legacy_api() {
const component = current_component_context?.function;
/** @param {string} method */
function error(method) {
// @ts-expect-error
const parent = get_component()?.filename ?? 'Something';
e.component_api_changed(parent, method, component.filename);
}
return {
$destroy: () => error('$destroy()'),
$on: () => error('$on(...)'),
$set: () => error('$set(...)')
};
}

@ -37,7 +37,7 @@ function get_stack() {
* Determines which `.svelte` component is responsible for a given state change * Determines which `.svelte` component is responsible for a given state change
* @returns {Function | null} * @returns {Function | null}
*/ */
function get_component() { export function get_component() {
// first 4 lines are svelte internals; adjust this number if we change the internal call stack // first 4 lines are svelte internals; adjust this number if we change the internal call stack
const stack = get_stack()?.slice(4); const stack = get_stack()?.slice(4);
if (!stack) return null; if (!stack) return null;

@ -56,6 +56,25 @@ export function bind_not_bindable(key, component, name) {
} }
} }
/**
* %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
* @param {string} parent
* @param {string} method
* @param {string} component
* @returns {never}
*/
export function component_api_changed(parent, method, component) {
if (DEV) {
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`}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("component_api_changed");
}
}
/** /**
* Keyed each block has duplicate key `%value%` at indexes %a% and %b% * Keyed each block has duplicate key `%value%` at indexes %a% and %b%
* @param {string} a * @param {string} a

@ -6,6 +6,7 @@ export {
mark_module_end, mark_module_end,
add_owner_effect add_owner_effect
} from './dev/ownership.js'; } from './dev/ownership.js';
export { legacy_api } from './dev/legacy.js';
export { inspect } from './dev/inspect.js'; export { inspect } from './dev/inspect.js';
export { await_block as await } from './dom/blocks/await.js'; export { await_block as await } from './dom/blocks/await.js';
export { if_block as if } from './dom/blocks/if.js'; export { if_block as if } from './dom/blocks/if.js';

Loading…
Cancel
Save