Skip to content
5 changes: 5 additions & 0 deletions .changeset/flat-ghosts-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: make `legacy.componentApi` option more visible
4 changes: 4 additions & 0 deletions packages/svelte/messages/client-errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

> %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

## component_api_invalid_new

> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. 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
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ export function client_component(source, analysis, options) {
body.unshift(b.imports([['createClassComponent', '$$_createClassComponent']], 'svelte/legacy'));
component_block.body.unshift(
b.if(
b.binary('===', b.id('new.target'), b.id(analysis.name)),
b.id('new.target'),
b.return(
b.call(
'$$_createClassComponent',
Expand All @@ -504,15 +504,7 @@ export function client_component(source, analysis, options) {
)
);
} else if (options.dev) {
component_block.body.unshift(
b.if(
b.binary('===', b.id('new.target'), b.id(analysis.name)),
b.throw_error(
`Instantiating a component with \`new\` 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_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target'))));
}

if (state.events.size > 0) {
Expand Down
8 changes: 6 additions & 2 deletions packages/svelte/src/internal/client/dev/hmr.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { block, branch, destroy_effect } from '../reactivity/effects.js';
import { set_should_intro } from '../render.js';
import { get } from '../runtime.js';
import { check_target } from './legacy.js';

/**
* @template {(anchor: Comment, props: any) => any} Component
Expand All @@ -11,7 +12,7 @@ export function hmr(source) {
* @param {Comment} anchor
* @param {any} props
*/
return (anchor, props) => {
return function (anchor, props) {
let instance = {};

/** @type {import("#client").Effect} */
Expand All @@ -31,7 +32,10 @@ export function hmr(source) {
// preserve getters/setters
Object.defineProperties(
instance,
Object.getOwnPropertyDescriptors(component(anchor, props))
Object.getOwnPropertyDescriptors(
// @ts-expect-error
new.target ? new component(anchor, props) : component(anchor, props)
)
);
set_should_intro(true);
});
Expand Down
7 changes: 7 additions & 0 deletions packages/svelte/src/internal/client/dev/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import * as e from '../errors.js';
import { current_component_context } from '../runtime.js';
import { get_component } from './ownership.js';

/** @param {Function & { filename: string }} target */
export function check_target(target) {
if (target) {
e.component_api_invalid_new(target.filename ?? 'a component', target.name);
}
}

export function legacy_api() {
const component = current_component_context?.function;

Expand Down
18 changes: 18 additions & 0 deletions packages/svelte/src/internal/client/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ export function component_api_changed(parent, method, component) {
}
}

/**
* Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
* @param {string} component
* @param {string} name
* @returns {never}
*/
export function component_api_invalid_new(component, name) {
if (DEV) {
const error = new Error(`${"component_api_invalid_new"}\n${`Attempted to instantiate ${component} with \`new ${name}\`, which is no longer valid in Svelte 5. If this component is not under your control, set the \`legacy.componentApi\` compiler option to keep it working. 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_invalid_new");
}
}

/**
* Keyed each block has duplicate key `%value%` at indexes %a% and %b%
* @param {string} a
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
mark_module_end,
add_owner_effect
} from './dev/ownership.js';
export { legacy_api } from './dev/legacy.js';
export { check_target, 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
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ 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). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`.
If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility, which means code using `new Component(...)` keeps working without adjustments (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`.

```js
/// svelte.config.js
export default {
compilerOptions: {
legacy: { componentApi: true }
}
};
```

### Server API changes

Expand Down