Skip to content
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/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