breaking: remove `createRoot`, adjust `mount`/`hydrate` APIs, introduce `unmount` (#10516)

* breaking: remove `createRoot`, adjust `mount`/`hydrate` APIs, introduce `unmount`

closes #9827

* Update packages/svelte/src/internal/client/runtime.js

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>

---------

Co-authored-by: Rich Harris <richard.a.harris@gmail.com>
pull/10519/head
Simon H 10 months ago committed by GitHub
parent 11b69459b9
commit 2755401034
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
breaking: remove `createRoot`, adjust `mount`/`hydrate` APIs, introduce `unmount`

@ -43,6 +43,7 @@ import {
untrack,
effect,
flushSync,
flush_sync,
safe_not_equal,
current_block,
managed_effect,
@ -64,12 +65,11 @@ import {
get_descriptors,
is_array,
is_function,
object_assign,
object_keys
object_assign
} from './utils.js';
import { is_promise } from '../common.js';
import { bind_transition, trigger_transitions } from './transitions.js';
import { STATE_SYMBOL, proxy } from './proxy.js';
import { STATE_SYMBOL } from './proxy.js';
/** @type {Set<string>} */
const all_registerd_events = new Set();
@ -2825,14 +2825,21 @@ export function spread_props(...props) {
return new Proxy({ props }, spread_props_handler);
}
// TODO 5.0 remove this
/**
* Mounts the given component to the given target and returns a handle to the component's public accessors
* as well as a `$set` and `$destroy` method to update the props of the component or destroy it.
*
* If you don't need to interact with the component after mounting, use `mount` instead to save some bytes.
* @deprecated Use `mount` or `hydrate` instead
*/
export function createRoot() {
throw new Error(
'`createRoot` has been removed. Use `mount` or `hydrate` instead. See the updated docs for more info: https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes'
);
}
/**
* Mounts a component to the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
*
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} component
* @param {{
@ -2841,48 +2848,22 @@ export function spread_props(...props) {
* events?: Events;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: false;
* }} options
* @returns {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }}
* @returns {Exports}
*/
export function createRoot(component, options) {
const props = proxy(/** @type {any} */ (options.props) || {}, false);
let [accessors, $destroy] = hydrate(component, { ...options, props });
const result =
/** @type {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }} */ ({
$set: (next) => {
object_assign(props, next);
},
$destroy
});
for (const key of object_keys(accessors || {})) {
define_property(result, key, {
get() {
// @ts-expect-error TS doesn't know key exists on accessor
return accessors[key];
},
/** @param {any} value */
set(value) {
// @ts-expect-error TS doesn't know key exists on accessor
flushSync(() => (accessors[key] = value));
},
enumerable: true
});
}
return result;
export function mount(component, options) {
init_operations();
const anchor = empty();
options.target.appendChild(anchor);
// Don't flush previous effects to ensure order of outer effects stays consistent
return flush_sync(() => _mount(component, { ...options, anchor }), false);
}
/**
* Mounts the given component to the given target and returns the accessors of the component and a function to destroy it.
*
* If you need to interact with the component after mounting, use `createRoot` instead.
* Hydrates a component on the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
*
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} component
* @param {{
@ -2891,19 +2872,65 @@ export function createRoot(component, options) {
* events?: Events;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: false;
* }} options
* @returns {[Exports, () => void]}
* @returns {Exports}
*/
export function mount(component, options) {
export function hydrate(component, options) {
init_operations();
const anchor = empty();
options.target.appendChild(anchor);
return _mount(component, { ...options, anchor });
const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild);
// Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line
const hydration_fragment = get_hydration_fragment(first_child, true);
const previous_hydration_fragment = current_hydration_fragment;
set_current_hydration_fragment(hydration_fragment);
/** @type {null | Text} */
let anchor = null;
if (hydration_fragment === null) {
anchor = empty();
container.appendChild(anchor);
}
let finished_hydrating = false;
try {
// Don't flush previous effects to ensure order of outer effects stays consistent
return flush_sync(() => {
const instance = _mount(component, { ...options, anchor });
// flush_sync will run this callback and then synchronously run any pending effects,
// which don't belong to the hydration phase anymore - therefore reset it here
set_current_hydration_fragment(null);
finished_hydrating = true;
return instance;
}, false);
} catch (error) {
if (!finished_hydrating && options.recover !== false && hydration_fragment !== null) {
// eslint-disable-next-line no-console
console.error(
'ERR_SVELTE_HYDRATION_MISMATCH' +
(DEV
? ': Hydration failed because the initial UI does not match what was rendered on the server.'
: ''),
error
);
remove(hydration_fragment);
first_child.remove();
hydration_fragment.at(-1)?.nextSibling?.remove();
set_current_hydration_fragment(null);
return mount(component, options);
} else {
throw error;
}
} finally {
set_current_hydration_fragment(previous_hydration_fragment);
}
}
/**
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} component
* @param {{
@ -2915,7 +2942,7 @@ export function mount(component, options) {
* intro?: boolean;
* recover?: false;
* }} options
* @returns {[Exports, () => void]}
* @returns {Exports}
*/
function _mount(component, options) {
const registered_events = new Set();
@ -2934,7 +2961,7 @@ function _mount(component, options) {
options.context;
}
// @ts-expect-error the public typings are not what the actual function looks like
accessors = component(options.anchor, options.props || {});
accessors = component(options.anchor, options.props || {}) || {};
if (options.context) {
pop();
}
@ -2981,80 +3008,38 @@ function _mount(component, options) {
event_handle(array_from(all_registerd_events));
root_event_handles.add(event_handle);
return [
accessors,
() => {
for (const event_name of registered_events) {
container.removeEventListener(event_name, bound_event_listener);
}
root_event_handles.delete(event_handle);
const dom = block.d;
if (dom !== null) {
remove(dom);
}
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e));
mounted_components.set(accessors, () => {
for (const event_name of registered_events) {
container.removeEventListener(event_name, bound_event_listener);
}
root_event_handles.delete(event_handle);
const dom = block.d;
if (dom !== null) {
remove(dom);
}
];
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e));
});
return accessors;
}
/**
* Hydrates the given component to the given target and returns the accessors of the component and a function to destroy it.
*
* If you need to interact with the component after hydrating, use `createRoot` instead.
*
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>>} component
* @param {{
* target: Node;
* props?: Props;
* events?: Events;
* context?: Map<any, any>;
* intro?: boolean;
* recover?: false;
* }} options
* @returns {[Exports, () => void]}
* References of the accessors of all components that were `mount`ed or `hydrate`d.
* Uses a `WeakMap` to avoid memory leaks.
*/
export function hydrate(component, options) {
init_operations();
const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild);
// Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line
const hydration_fragment = get_hydration_fragment(first_child, true);
const previous_hydration_fragment = current_hydration_fragment;
let mounted_components = new WeakMap();
try {
/** @type {null | Text} */
let anchor = null;
if (hydration_fragment === null) {
anchor = empty();
container.appendChild(anchor);
}
set_current_hydration_fragment(hydration_fragment);
return _mount(component, { ...options, anchor });
} catch (error) {
if (options.recover !== false && hydration_fragment !== null) {
// eslint-disable-next-line no-console
console.error(
'ERR_SVELTE_HYDRATION_MISMATCH' +
(DEV
? ': Hydration failed because the initial UI does not match what was rendered on the server.'
: ''),
error
);
remove(hydration_fragment);
first_child.remove();
hydration_fragment.at(-1)?.nextSibling?.remove();
set_current_hydration_fragment(null);
return mount(component, options);
} else {
throw error;
}
} finally {
set_current_hydration_fragment(previous_hydration_fragment);
/**
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
* @param {Record<string, any>} component
*/
export function unmount(component) {
const destroy = mounted_components.get(component);
if (DEV && !destroy) {
// eslint-disable-next-line no-console
console.warn('Tried to unmount a component that was not mounted.');
}
destroy?.();
}
/**

@ -749,9 +749,22 @@ export function flush_local_pre_effects(context) {
* @returns {void}
*/
export function flushSync(fn) {
flush_sync(fn);
}
/**
* Internal version of `flushSync` with the option to not flush previous effects.
* Returns the result of the passed function, if given.
* @param {() => any} [fn]
* @param {boolean} [flush_previous]
* @returns {any}
*/
export function flush_sync(fn, flush_previous = true) {
const previous_scheduler_mode = current_scheduler_mode;
const previous_queued_pre_and_render_effects = current_queued_pre_and_render_effects;
const previous_queued_effects = current_queued_effects;
let result;
try {
infinite_loop_guard();
/** @type {import('./types.js').EffectSignal[]} */
@ -762,10 +775,12 @@ export function flushSync(fn) {
current_scheduler_mode = FLUSH_SYNC;
current_queued_pre_and_render_effects = pre_and_render_effects;
current_queued_effects = effects;
flush_queued_effects(previous_queued_pre_and_render_effects);
flush_queued_effects(previous_queued_effects);
if (flush_previous) {
flush_queued_effects(previous_queued_pre_and_render_effects);
flush_queued_effects(previous_queued_effects);
}
if (fn !== undefined) {
fn();
result = fn();
}
if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) {
flushSync();
@ -782,6 +797,8 @@ export function flushSync(fn) {
current_queued_pre_and_render_effects = previous_queued_pre_and_render_effects;
current_queued_effects = previous_queued_effects;
}
return result;
}
/**

@ -14,6 +14,7 @@ import * as $ from '../internal/index.js';
* @param {import('../main/public.js').ComponentConstructorOptions<Props> & {
* component: import('../main/public.js').SvelteComponent<Props, Events, Slots>;
* immutable?: boolean;
* hydrate?: boolean;
* recover?: false;
* }} options
* @returns {import('../main/public.js').SvelteComponent<Props, Events, Slots> & Exports}
@ -53,28 +54,28 @@ class Svelte4Component {
/** @type {any} */
#events = {};
/** @type {ReturnType<typeof $.createRoot>} */
/** @type {Record<string, any>} */
#instance;
/**
* @param {import('../main/public.js').ComponentConstructorOptions & {
* component: any;
* immutable?: boolean;
* hydrate?: boolean;
* recover?: false;
* }} options
*/
constructor(options) {
this.#instance = $.createRoot(options.component, {
const props = $.proxy({ ...(options.props || {}), $$events: this.#events }, false);
this.#instance = (options.hydrate ? $.hydrate : $.mount)(options.component, {
target: options.target,
props: { ...options.props, $$events: this.#events },
props,
context: options.context,
intro: options.intro,
recover: options.recover
});
for (const key of Object.keys(this.#instance)) {
if (key === '$set' || key === '$destroy') continue;
define_property(this, key, {
get() {
return this.#instance[key];
@ -86,6 +87,13 @@ class Svelte4Component {
enumerable: true
});
}
this.#instance.$set = /** @param {Record<string, any>} next */ (next) => {
Object.assign(props, next);
};
this.#instance.$destroy = () => {
$.unmount(this.#instance);
};
}
/** @param {Record<string, any>} props */

@ -235,10 +235,11 @@ function init_update_callbacks(context) {
// (except probably untrack — do we want to expose that, if there's also a rune?)
export {
flushSync,
createRoot,
mount,
hydrate,
tick,
unmount,
untrack,
unstate
unstate,
createRoot
} from '../internal/index.js';

@ -1,7 +1,6 @@
import { on_destroy } from '../internal/server/index.js';
export {
createRoot,
createEventDispatcher,
flushSync,
getAllContexts,
@ -11,7 +10,9 @@ export {
hydrate,
setContext,
tick,
untrack
unmount,
untrack,
createRoot
} from './main-client.js';
/** @returns {void} */

@ -4,7 +4,7 @@ import * as fs from 'node:fs';
import { assert } from 'vitest';
import { compile_directory, try_read_file } from '../helpers.js';
import { assert_html_equal } from '../html_equal.js';
import { createRoot } from 'svelte';
import { mount, unmount } from 'svelte';
import { suite, type BaseTest } from '../suite.js';
import type { CompileOptions, Warning } from '#compiler';
@ -55,7 +55,7 @@ const { test, run } = suite<CssTest>(async (config, cwd) => {
if (expected.html !== null) {
const target = window.document.createElement('main');
const { $destroy } = createRoot(ClientComponent, { props: config.props ?? {}, target });
const component = mount(ClientComponent, { props: config.props ?? {}, target });
const html = target.innerHTML;
@ -63,7 +63,7 @@ const { test, run } = suite<CssTest>(async (config, cwd) => {
assert_html_equal(html, expected.html);
$destroy();
unmount(component);
window.document.head.innerHTML = ''; // remove added styles
// TODO enable SSR tests

@ -0,0 +1,2 @@
<!--ssr:0--><noscript>JavaScript is required for this site.</noscript>
<h1>Hello!</h1><p>Count: 1</p><!--ssr:0-->

@ -84,6 +84,7 @@ const { test, run } = suite<HydrationTest>(async (config, cwd) => {
const component = createClassComponent({
component: (await import(`${cwd}/_output/client/main.svelte.js`)).default,
target,
hydrate: true,
props: config.props
});

@ -5,11 +5,12 @@ export default test({
async test({ assert, target }) {
target.innerHTML = '<my-app prop/>';
await tick();
await tick();
await tick();
/** @type {any} */
const el = target.querySelector('my-app');
await tick();
assert.ok(el.wasCreated);
assert.ok(el.propsInitialized);
}

@ -24,12 +24,13 @@ export default async function (target) {
component: SvelteComponent,
target,
props: config.props,
intro: config.intro
intro: config.intro,
hydrate: __HYDRATE__
},
config.options || {}
);
const component = createClassComponent(options);
const component = __CE_TEST__ ? null : createClassComponent(options);
/**
* @param {() => boolean} fn
@ -50,14 +51,19 @@ export default async function (target) {
if (config.test) {
await config.test({
assert,
component,
get component() {
if (!component) {
throw new Error('test property `component` is not available in custom element tests');
}
return component;
},
componentCtor: SvelteComponent,
target,
window,
waitUntil: wait_until
});
component.$destroy();
component?.$destroy();
if (unhandled_rejection) {
throw unhandled_rejection;

@ -67,6 +67,10 @@ async function run_test(
const build_result = await build({
entryPoints: [`${__dirname}/driver.js`],
write: false,
define: {
__HYDRATE__: String(hydrate),
__CE_TEST__: String(test_dir.includes('custom-elements-samples'))
},
alias: {
__MAIN_DOT_SVELTE__: path.resolve(test_dir, 'main.svelte'),
__CONFIG__: path.resolve(test_dir, '_config.js'),

@ -1,5 +1,6 @@
<script>
import { onMount, onDestroy, tick, createRoot } from 'svelte';
import { onMount, onDestroy, tick } from 'svelte';
import { createClassComponent } from 'svelte/legacy'
export let component;
@ -14,7 +15,8 @@ function mountComponent(doc) {
if (content) content.$destroy();
if (doc && component) {
const { component, ...props } = $$props;
content = createRoot(component, { target: doc.body, props });
// When this test is migrated to runes, use mount/unmount and $state for updating props instead
content = createClassComponent({ component, target: doc.body, props });
}
}

@ -15,6 +15,7 @@ export default test({
component.visible = false;
raf.tick(26);
assert.equal(div.style.opacity, '0.16666');
// The exact number doesn't matter here, this test is about ensuring that transitions work in iframes
assert.equal(Number(div.style.opacity).toFixed(4), '0.8333');
}
});

@ -258,7 +258,8 @@ async function run_test_variant(
target,
immutable: config.immutable,
intro: config.intro,
recover: false
recover: false,
hydrate: variant === 'hydrate'
});
// eslint-disable-next-line no-console

@ -1,11 +1,11 @@
import { asClassComponent, createClassComponent } from 'svelte/legacy';
import {
createRoot,
SvelteComponent,
type ComponentEvents,
type ComponentProps,
type ComponentType,
mount
mount,
hydrate
} from 'svelte';
// --------------------------------------------------------------------------- legacy: classes
@ -119,7 +119,7 @@ mount(NewComponent, {
recover: false
});
const instance = createRoot(NewComponent, {
hydrate(NewComponent, {
target: null as any as Document | Element | ShadowRoot | Text | Comment,
props: {
prop: 'foo',
@ -133,14 +133,6 @@ const instance = createRoot(NewComponent, {
intro: false,
recover: false
});
instance.$set({
prop: 'foo',
// @ts-expect-error
x: ''
});
instance.$set({});
instance.$destroy();
instance.anExport === 1;
// --------------------------------------------------------------------------- interop
@ -175,5 +167,6 @@ asLegacyComponent.$$prop_def.x = '';
asLegacyComponent.anExport;
const x: typeof asLegacyComponent = createClassComponent({
target: null as any,
hydrate: true,
component: newComponent
});

@ -324,50 +324,36 @@ declare module 'svelte' {
*/
type NotFunction<T> = T extends Function ? never : T;
/**
* Mounts the given component to the given target and returns a handle to the component's public accessors
* as well as a `$set` and `$destroy` method to update the props of the component or destroy it.
*
* If you don't need to interact with the component after mounting, use `mount` instead to save some bytes.
*
* */
export function createRoot<Props extends Record<string, any>, Exports extends Record<string, any> | undefined, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
target: Node;
props?: Props | undefined;
events?: Events | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
recover?: false | undefined;
}): Exports & {
$destroy: () => void;
$set: (props: Partial<Props>) => void;
};
* @deprecated Use `mount` or `hydrate` instead
*/
export function createRoot(): void;
/**
* Mounts the given component to the given target and returns the accessors of the component and a function to destroy it.
*
* If you need to interact with the component after mounting, use `createRoot` instead.
* Mounts a component to the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
*
* */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any> | undefined, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
target: Node;
props?: Props | undefined;
events?: Events | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
}): [Exports, () => void];
}): Exports;
/**
* Hydrates the given component to the given target and returns the accessors of the component and a function to destroy it.
*
* If you need to interact with the component after hydrating, use `createRoot` instead.
* Hydrates a component on the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
*
* */
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any> | undefined, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
export function hydrate<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>>(component: ComponentType<SvelteComponent<Props, Events, any>>, options: {
target: Node;
props?: Props | undefined;
events?: Events | undefined;
context?: Map<any, any> | undefined;
intro?: boolean | undefined;
recover?: false | undefined;
}): [Exports, () => void];
}): Exports;
/**
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
* */
export function unmount(component: Record<string, any>): void;
/**
* Synchronously flushes any pending state changes and those that result from it.
* */
@ -1737,6 +1723,7 @@ declare module 'svelte/legacy' {
export function createClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(options: ComponentConstructorOptions<Props> & {
component: SvelteComponent<Props, Events, Slots>;
immutable?: boolean | undefined;
hydrate?: boolean | undefined;
recover?: false | undefined;
}): SvelteComponent<Props, Events, Slots> & Exports;
/**

@ -1,8 +1,9 @@
// @ts-ignore
import { hydrate } from 'svelte';
import { hydrate, unmount } from 'svelte';
// @ts-ignore you need to create this file
import App from './App.svelte';
// @ts-ignore
[window.unmount] = hydrate(App, {
const component = hydrate(App, {
target: document.getElementById('root')!
});
// @ts-ignore
window.unmount = () => unmount(component);

@ -6,23 +6,71 @@ While Svelte 5 is a complete rewrite, we have done our best to ensure that most
## Components are no longer classes
In Svelte 3 and 4, components are classes. In Svelte 5 they are functions and should be instantiated differently. If you need to manually instantiate components, you should use `mount` or `createRoot` (imported from `svelte`) instead. If you see this error using SvelteKit, try updating to the latest version of SvelteKit first, which adds support for Svelte 5. If you're using Svelte without SvelteKit, you'll likely have a `main.js` file (or similar) which you need to adjust:
In Svelte 3 and 4, components are classes. In Svelte 5 they are functions and should be instantiated differently. If you need to manually instantiate components, you should use `mount` or `hydrate` (imported from `svelte`) instead. If you see this error using SvelteKit, try updating to the latest version of SvelteKit first, which adds support for Svelte 5. If you're using Svelte without SvelteKit, you'll likely have a `main.js` file (or similar) which you need to adjust:
```diff
+ import { createRoot } from 'svelte';
+ import { mount } from 'svelte';
import App from './App.svelte'
- const app = new App({ target: document.getElementById("app") });
+ const app = createRoot(App, { target: document.getElementById("app") });
+ const app = mount(App, { target: document.getElementById("app") });
export default app;
```
`createRoot` returns an object with a `$set` and `$destroy` method on it. It does not come with an `$on` method you may know from the class component API. Instead, pass them via the `events` property on the options argument. If you don't need to interact with the component instance after creating it, you can use `mount` instead, which saves some bytes.
`mount` and `hydrate` have the exact same API. The difference is that `hydrate` will pick up the Svelte's server-rendered HTML inside its target and hydrate it. Both return an object with the exports of the component and potentially property accessors (if compiled with `accesors: true`). They do not come with the `$on`, `$set` and `$destroy` methods you may know from the class component API. These are its replacements:
For `$on`, instead of listening to events, pass them via the `events` property on the options argument.
```diff
+ import { mount } from 'svelte';
import App from './App.svelte'
- const app = new App({ target: document.getElementById("app") });
- app.$on('event', callback);
+ const app = mount(App, { target: document.getElementById("app"), events: { event: callback } });
```
> Note that using `events` is discouraged — instead, [use callbacks](https://svelte-5-preview.vercel.app/docs/event-handlers)
As a stop-gap-solution, you can also use `createClassComponent` or `asClassComponent` (imported from `svelte/legacy`) instead to keep the same API after instantiating. 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).
For `$set`, use `$state` instead to create a reactive property object and manipulate it. If you're doing this inside a `.js` or `.ts` file, adjust the ending to include `.svelte`, i.e. `.svelte.js` or `.svelte.ts`.
```diff
+ import { mount } from 'svelte';
import App from './App.svelte'
- const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
- app.$set('event', { foo: 'baz' });
+ const props = $state({ foo: 'bar' });
+ const app = mount(App, { target: document.getElementById("app"), props });
+ props.foo = 'baz';
```
For `$destroy`, use `unmount` instead.
```diff
+ import { mount, unmount } from 'svelte';
import App from './App.svelte'
- const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
- app.$destroy();
+ const app = mount(App, { target: document.getElementById("app") });
+ unmount(app);
```
As a stop-gap-solution, you can also use `createClassComponent` or `asClassComponent` (imported from `svelte/legacy`) instead to keep the same API known from Svelte 4 after instantiating.
```diff
+ import { createClassComponent } from 'svelte/legacy';
import App from './App.svelte'
- const app = new App({ target: document.getElementById("app") });
+ const app = createClassComponent({ component: App, target: document.getElementById("app") });
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).
### Server API changes

Loading…
Cancel
Save