Skip to content

Commit

Permalink
feat: better error for bind:this legacy API usage (#11498)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed May 7, 2024
1 parent 85d6805 commit dca8861
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/three-buses-sleep.md
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: better error for `bind:this` legacy API usage
4 changes: 4 additions & 0 deletions packages/svelte/messages/client-errors/errors.md
Expand Up @@ -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()`
## 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

> Keyed each block has duplicate key at indexes %a% and %b%
Expand Down
Expand Up @@ -232,6 +232,7 @@ export function client_component(source, analysis, options) {
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 expression = serialize_get_binding(b.id(name), instance_state);

Expand Down Expand Up @@ -310,41 +311,7 @@ export function client_component(source, analysis, options) {
)
);
} else if (options.dev) {
component_returned_object.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'
)
])
)
)
);
component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
}

const push_args = [b.id('$$props'), b.literal(analysis.runes)];
Expand Down
20 changes: 20 additions & 0 deletions packages/svelte/src/internal/client/dev/legacy.js
@@ -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(...)')
};
}
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dev/ownership.js
Expand Up @@ -37,7 +37,7 @@ function get_stack() {
* Determines which `.svelte` component is responsible for a given state change
* @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
const stack = get_stack()?.slice(4);
if (!stack) return null;
Expand Down
19 changes: 19 additions & 0 deletions packages/svelte/src/internal/client/errors.js
Expand Up @@ -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%
* @param {string} a
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/internal/client/index.js
Expand Up @@ -6,6 +6,7 @@ export {
mark_module_end,
add_owner_effect
} from './dev/ownership.js';
export { legacy_api } from './dev/legacy.js';
export { inspect } from './dev/inspect.js';
export { await_block as await } from './dom/blocks/await.js';
export { if_block as if } from './dom/blocks/if.js';
Expand Down

0 comments on commit dca8861

Please sign in to comment.