breaking: remove Component type, keep using SvelteComponent instead (#9413)

I came to the conclusion that when we're making up arbitrary types, we might as well keep the old class. That way:
- one less thing to worry about (language tools and other tooling basically can continue to spit out SvelteComponent )
- we can more clearly mark $set , the constructor etc as being deprecated and no longer functioning unless you use that legacy compatibility mode
- much more ergonomic to type for the user:
  - const someInstance: SvelteComponent<..> instead of const someInstance: ReturnType<typeof Component<..>>
  - If you're using generics, you can do export class MyComponent<T> extends SvelteComponent<{ prop: T }> {} instead of having to type out the whole function in a way that I'm not even sure how to do with generics
pull/9424/head
Simon H 1 year ago committed by GitHub
parent 2ae953934d
commit 9a99554379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: remove Component type, keep using SvelteComponent instead

@ -27,7 +27,7 @@ await createBundle({
[`${pkg.name}/animate`]: `${dir}/src/animate/public.d.ts`,
[`${pkg.name}/compiler`]: `${dir}/src/compiler/index.js`,
[`${pkg.name}/easing`]: `${dir}/src/easing/index.js`,
[`${pkg.name}/legacy`]: `${dir}/src/legacy/public.d.ts`,
[`${pkg.name}/legacy`]: `${dir}/src/legacy/legacy-client.js`,
[`${pkg.name}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,

@ -3020,7 +3020,7 @@ export function unwrap(value) {
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').Component<Props, Exports, Events>} component
* @param {import('../../main/public.js').SvelteComponent<Props, Events>} component
* @param {{
* target: Node;
* props?: Props;
@ -3139,7 +3139,7 @@ export function createRoot(component, options) {
* @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Events
* @param {import('../../main/public.js').Component<Props, Exports, Events>} component
* @param {import('../../main/public.js').SvelteComponent<Props, Events>} component
* @param {{
* target: Node;
* props?: Props;

@ -11,12 +11,12 @@ import * as $ from '../internal/index.js';
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {import('./public.js').ComponentConstructorOptions<Props> & {
* component: import('../main/public.js').Component<Props, Exports, Events, Slots>;
* @param {import('../main/public.js').ComponentConstructorOptions<Props> & {
* component: import('../main/public.js').SvelteComponent<Props, Events, Slots>;
* immutable?: boolean;
* recover?: false;
* }} options
* @returns {import('./public.js').SvelteComponent<Props & Exports, Events, Slots>}
* @returns {import('../main/public.js').SvelteComponent<Props, Events, Slots> & Exports}
*/
export function createClassComponent(options) {
// @ts-expect-error $$prop_def etc are not actually defined
@ -33,8 +33,8 @@ export function createClassComponent(options) {
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {import('../main/public.js').Component<Props, Exports, Events, Slots>} component
* @returns {typeof import('./public.js').SvelteComponent<Props & Exports, Events, Slots>}
* @param {import('../main/public.js').SvelteComponent<Props, Events, Slots>} component
* @returns {typeof import('../main/public.js').SvelteComponent<Props, Events, Slots> & Exports}
*/
export function asClassComponent(component) {
// @ts-expect-error $$prop_def etc are not actually defined
@ -57,7 +57,7 @@ class Svelte4Component {
#instance;
/**
* @param {import('./public.js').ComponentConstructorOptions & {
* @param {import('../main/public.js').ComponentConstructorOptions & {
* component: any;
* immutable?: boolean;
* recover?: false;

@ -6,7 +6,7 @@ import { render } from '../internal/server/index.js';
export { createClassComponent };
/**
* Takes the component function and returns a Svelte 4 compatible component constructor.
* Takes a Svelte 5 component and returns a Svelte 4 compatible component constructor.
*
* @deprecated Use this only as a temporary solution to migrate your imperative component code to Svelte 5.
*
@ -15,8 +15,8 @@ export { createClassComponent };
* @template {Record<string, any>} Events
* @template {Record<string, any>} Slots
*
* @param {import('../main/public.js').Component<Props, Exports, Events, Slots>} component
* @returns {typeof import('./public.js').SvelteComponent<Props & Exports, Events, Slots>}
* @param {import('../main/public.js').SvelteComponent<Props, Events, Slots>} component
* @returns {typeof import('../main/public.js').SvelteComponent<Props, Events, Slots> & Exports}
*/
export function asClassComponent(component) {
const component_constructor = as_class_component(component);
@ -30,8 +30,9 @@ export function asClassComponent(component) {
html: result.html
};
};
// @ts-expect-error this is present for SSR
// this is present for SSR
component_constructor.render = _render;
// @ts-ignore
return component_constructor;
}

@ -1,93 +0,0 @@
/**
* @deprecated Use `Component` instead. See TODO for more information.
*/
export interface ComponentConstructorOptions<
Props extends Record<string, any> = Record<string, any>
> {
target: Element | Document | ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}
/**
* @deprecated use `Component` instead. See TODO for more information.
*
* Base class for Svelte components in Svelte 4. Svelte 5+ components implement
* the `Component` interface instead. This class is only provided for backwards
* compatibility with Svelte 4 typings and doesn't have any runtime equivalent.
*
* Can be used to create strongly typed Svelte components.
*
* #### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { SvelteComponent } from "svelte";
* export class MyComponent extends SvelteComponent<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*/
export class SvelteComponent<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> {
[prop: string]: any;
constructor(options: ComponentConstructorOptions<Props>);
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
$$prop_def: Props;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
$$events_def: Events;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
$$slot_def: Slots;
$destroy(): void;
$on<K extends Extract<keyof Events, string>>(
type: K,
callback: (e: Events[K]) => void
): () => void;
$set(props: Partial<Props>): void;
}
/**
* @deprecated Use `Component` instead. See TODO for more information.
*/
export class SvelteComponentTyped<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponent<Props, Events, Slots> {}
export * from './legacy-client.js';

@ -1,5 +1,5 @@
declare module '*.svelte' {
export { Component as default } from 'svelte';
export { SvelteComponent as default } from 'svelte';
}
/**

@ -1,18 +1,24 @@
// This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are).
// Once we convert to JSDoc make it a d.ts file.
import type {
ComponentConstructorOptions,
SvelteComponent,
SvelteComponentTyped
} from '../legacy/public.js';
// For Svelte 6 we can think about only exporting these from svelte/legacy
export { SvelteComponent, SvelteComponentTyped, ComponentConstructorOptions };
/**
* @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore.
* Use `mount` or `createRoot` instead to instantiate components.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
*/
export interface ComponentConstructorOptions<
Props extends Record<string, any> = Record<string, any>
> {
target: Element | Document | ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}
/**
* Base interface for Svelte components.
*
* Can be used to create strongly typed Svelte components.
*
* #### Example:
@ -21,8 +27,8 @@ export { SvelteComponent, SvelteComponentTyped, ComponentConstructorOptions };
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import type { Component } from "svelte";
* export type MyComponent = Component<{foo: string}>
* import { SvelteComponent } from "svelte";
* export class MyComponent extends SvelteComponent<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
@ -33,27 +39,87 @@ export { SvelteComponent, SvelteComponentTyped, ComponentConstructorOptions };
* </script>
* <MyComponent foo={'bar'} />
* ```
*
* This was the base class for Svelte components in Svelte 4. Svelte 5+ components
* are completely different under the hood. You should only use this type for typing,
* not actually instantiate components with `new` - use `mount` or `createRoot` instead.
* See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
* for more info.
*/
export interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> | undefined = undefined,
Events extends Record<string, any> = {},
Slots extends Record<string, any> = {}
export class SvelteComponent<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement;
[prop: string]: any;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
constructor(props: Props);
/**
* @deprecated This constructor only exists when using the `asClassComponent` compatibility helper, which
* is a stop-gap solution. Migrate towards using `mount` or `createRoot` instead. See
* https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more info.
*/
constructor(options: ComponentConstructorOptions<Props>);
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
* */
$$prop_def: Props;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
$$events_def: Events;
/**
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*
* */
$$slot_def: Slots;
/**
* ## DO NOT USE THIS
* This only exists for typing purposes and has no runtime value.
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
z_$$(
props: Props,
events: Events,
slots: Slots
): Exports extends undefined ? Props | undefined : Exports & Partial<Props>;
$destroy(): void;
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$on<K extends Extract<keyof Events, string>>(
type: K,
callback: (e: Events[K]) => void
): () => void;
/**
* @deprecated This method only exists when using one of the legacy compatibility helpers, which
* is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/
$set(props: Partial<Props>): void;
}
/**
* @deprecated Use `SvelteComponent` instead. See TODO for more information.
*/
export class SvelteComponentTyped<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponent<Props, Events, Slots> {}
/**
* Convenience type to get the events the given component expects. Example:
* ```html
@ -69,10 +135,10 @@ export interface Component<
* <Component on:close={handleCloseEvent} />
* ```
*/
export type ComponentEvents<Comp extends Component<any, any, any, any> | SvelteComponent> =
Comp extends SvelteComponent<any, infer Events>
? Events
: Comp extends Component<any, any, infer Events, any>
export type ComponentEvents<Comp extends SvelteComponent> = Comp extends SvelteComponent<
any,
infer Events
>
? Events
: never;
@ -87,16 +153,12 @@ export type ComponentEvents<Comp extends Component<any, any, any, any> | SvelteC
* </script>
* ```
*/
export type ComponentProps<Comp extends Component<any, any, any, any> | SvelteComponent> =
Comp extends SvelteComponent<infer Props>
? Props
: Comp extends Component<infer Props, any, any, any>
export type ComponentProps<Comp extends SvelteComponent> = Comp extends SvelteComponent<infer Props>
? Props
: never;
/**
* Convenience type to get the type of a Svelte component. Not necessary when using the `Component` type,
* but useful when using the deprecated `SvelteComponent` type and for example in combination with
* Convenience type to get the type of a Svelte component. Useful for example in combination with
* dynamic components using `<svelte:component>`.
*
* Example:
@ -114,17 +176,14 @@ export type ComponentProps<Comp extends Component<any, any, any, any> | SvelteCo
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Comp extends Component<any, any, any, any> | SvelteComponent> =
Comp extends SvelteComponent
? (new (
export type ComponentType<Comp extends SvelteComponent> = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
>
) => Comp) & {
) => Comp) & {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement;
}
: Comp;
};
interface DispatchOptions {
cancelable?: boolean;

@ -1,7 +1,7 @@
import { SvelteComponent, asClassComponent, createClassComponent } from 'svelte/legacy';
import { asClassComponent, createClassComponent } from 'svelte/legacy';
import {
createRoot,
type Component,
SvelteComponent,
type ComponentEvents,
type ComponentProps,
type ComponentType
@ -15,11 +15,11 @@ class LegacyComponent extends SvelteComponent<
{ slot: { slotProps: boolean } }
> {}
// @ts-expect-error
const legacyComponent = new LegacyComponent({
target: null as any as Document | Element | ShadowRoot,
props: {
prop: 'foo',
// @ts-expect-error
x: ''
}
});
@ -48,35 +48,34 @@ const legacyComponentEvents2: ComponentEvents<LegacyComponent> = {
// --------------------------------------------------------------------------- new: functions
type NewComponent = Component<
class NewComponent extends SvelteComponent<
{ prop: string },
{ anExport: number },
{ event: MouseEvent },
{ slot: { slotProps: boolean } }
>;
const newComponent: NewComponent = {
z_$$: (props, events, slots) => {
props.prop;
// @ts-expect-error
props.x;
> {
anExport: string = '';
}
events.event;
// @ts-expect-error
events.x;
slots.slot;
// @ts-expect-error
slots.x;
// @ts-expect-error
new NewComponent({
prop: 'foo',
x: ''
});
return {
anExport: 1,
prop: props.prop
};
}
};
const newComponent: NewComponent = new NewComponent({
prop: 'foo'
});
newComponent.$$events_def.event;
// @ts-expect-error
newComponent.$$events_def.x;
newComponent.$$slot_def.slot;
// @ts-expect-error
newComponent.$$slot_def.x;
newComponent.anExport === '';
// @ts-expect-error
newComponent.anExport === 1;
const newComponentType: ComponentType<NewComponent> = newComponent;
const newComponentType: ComponentType<NewComponent> = NewComponent;
const newComponentProps1: ComponentProps<NewComponent> = {
prop: '',
@ -124,20 +123,28 @@ instance.anExport === 1;
// --------------------------------------------------------------------------- interop
const AsLegacyComponent = asClassComponent(newComponent);
const asLegacyComponent = new AsLegacyComponent({
// @ts-expect-error
new AsLegacyComponent({
target: null as any,
props: {
prop: '',
// @ts-expect-error
x: ''
}
});
const asLegacyComponent = new AsLegacyComponent({
target: null as any,
props: {
prop: ''
}
});
asLegacyComponent.$on('event', (e) => e.clientX);
// @ts-expect-error
asLegacyComponent.$on('event', (e) => e.foo);
// @ts-expect-error
asLegacyComponent.$on('bar', (e) => e);
asLegacyComponent.$$prop_def.prop = '';
asLegacyComponent.anExport = '';
// @ts-expect-error
asLegacyComponent.$$prop_def.anExport = 1;
// @ts-expect-error
asLegacyComponent.$$prop_def.prop = 1;

Loading…
Cancel
Save