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}/animate`]: `${dir}/src/animate/public.d.ts`,
[`${pkg.name}/compiler`]: `${dir}/src/compiler/index.js`, [`${pkg.name}/compiler`]: `${dir}/src/compiler/index.js`,
[`${pkg.name}/easing`]: `${dir}/src/easing/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}/motion`]: `${dir}/src/motion/public.d.ts`,
[`${pkg.name}/server`]: `${dir}/src/server/index.js`, [`${pkg.name}/server`]: `${dir}/src/server/index.js`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`, [`${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>} Props
* @template {Record<string, any> | undefined} Exports * @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Events * @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 {{ * @param {{
* target: Node; * target: Node;
* props?: Props; * props?: Props;
@ -3139,7 +3139,7 @@ export function createRoot(component, options) {
* @template {Record<string, any>} Props * @template {Record<string, any>} Props
* @template {Record<string, any> | undefined} Exports * @template {Record<string, any> | undefined} Exports
* @template {Record<string, any>} Events * @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 {{ * @param {{
* target: Node; * target: Node;
* props?: Props; * props?: Props;

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

@ -6,7 +6,7 @@ import { render } from '../internal/server/index.js';
export { createClassComponent }; 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. * @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>} Events
* @template {Record<string, any>} Slots * @template {Record<string, any>} Slots
* *
* @param {import('../main/public.js').Component<Props, Exports, Events, Slots>} component * @param {import('../main/public.js').SvelteComponent<Props, Events, Slots>} component
* @returns {typeof import('./public.js').SvelteComponent<Props & Exports, Events, Slots>} * @returns {typeof import('../main/public.js').SvelteComponent<Props, Events, Slots> & Exports}
*/ */
export function asClassComponent(component) { export function asClassComponent(component) {
const component_constructor = as_class_component(component); const component_constructor = as_class_component(component);
@ -30,8 +30,9 @@ export function asClassComponent(component) {
html: result.html html: result.html
}; };
}; };
// @ts-expect-error this is present for SSR // this is present for SSR
component_constructor.render = _render; component_constructor.render = _render;
// @ts-ignore
return component_constructor; 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' { 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). // 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, * @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore.
SvelteComponent, * Use `mount` or `createRoot` instead to instantiate components.
SvelteComponentTyped * See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes)
} from '../legacy/public.js'; * for more info.
*/
// For Svelte 6 we can think about only exporting these from svelte/legacy export interface ComponentConstructorOptions<
export { SvelteComponent, SvelteComponentTyped, 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. * Can be used to create strongly typed Svelte components.
* *
* #### Example: * #### Example:
@ -21,8 +27,8 @@ export { SvelteComponent, SvelteComponentTyped, ComponentConstructorOptions };
* you export a component called `MyComponent`. For Svelte+TypeScript users, * you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`: * you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts * ```ts
* import type { Component } from "svelte"; * import { SvelteComponent } from "svelte";
* export type MyComponent = Component<{foo: string}> * export class MyComponent extends SvelteComponent<{foo: string}> {}
* ``` * ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension * 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 * to provide intellisense and to use the component like this in a Svelte file
@ -33,27 +39,87 @@ export { SvelteComponent, SvelteComponentTyped, ComponentConstructorOptions };
* </script> * </script>
* <MyComponent foo={'bar'} /> * <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< export class SvelteComponent<
Props extends Record<string, any> = {}, Props extends Record<string, any> = any,
Exports extends Record<string, any> | undefined = undefined, Events extends Record<string, any> = any,
Events extends Record<string, any> = {}, Slots extends Record<string, any> = any
Slots extends Record<string, any> = {}
> { > {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ [prop: string]: any;
element?: typeof HTMLElement;
/**
* 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;
/**
* @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.
*/
$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;
/** /**
* ## DO NOT USE THIS * @deprecated This method only exists when using one of the legacy compatibility helpers, which
* This only exists for typing purposes and has no runtime value. * is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes
* for more info.
*/ */
z_$$( $set(props: Partial<Props>): void;
props: Props,
events: Events,
slots: Slots
): Exports extends undefined ? Props | undefined : Exports & Partial<Props>;
} }
/**
* @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: * Convenience type to get the events the given component expects. Example:
* ```html * ```html
@ -69,12 +135,12 @@ export interface Component<
* <Component on:close={handleCloseEvent} /> * <Component on:close={handleCloseEvent} />
* ``` * ```
*/ */
export type ComponentEvents<Comp extends Component<any, any, any, any> | SvelteComponent> = export type ComponentEvents<Comp extends SvelteComponent> = Comp extends SvelteComponent<
Comp extends SvelteComponent<any, infer Events> any,
? Events infer Events
: Comp extends Component<any, any, infer Events, any> >
? Events ? Events
: never; : never;
/** /**
* Convenience type to get the props the given component expects. Example: * Convenience type to get the props the given component expects. Example:
@ -87,16 +153,12 @@ export type ComponentEvents<Comp extends Component<any, any, any, any> | SvelteC
* </script> * </script>
* ``` * ```
*/ */
export type ComponentProps<Comp extends Component<any, any, any, any> | SvelteComponent> = export type ComponentProps<Comp extends SvelteComponent> = Comp extends SvelteComponent<infer Props>
Comp extends SvelteComponent<infer Props> ? Props
? Props : never;
: Comp extends Component<infer Props, any, any, any>
? Props
: never;
/** /**
* Convenience type to get the type of a Svelte component. Not necessary when using the `Component` type, * Convenience type to get the type of a Svelte component. Useful for example in combination with
* but useful when using the deprecated `SvelteComponent` type and for example in combination with
* dynamic components using `<svelte:component>`. * dynamic components using `<svelte:component>`.
* *
* Example: * Example:
@ -114,17 +176,14 @@ export type ComponentProps<Comp extends Component<any, any, any, any> | SvelteCo
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" /> * <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ``` * ```
*/ */
export type ComponentType<Comp extends Component<any, any, any, any> | SvelteComponent> = export type ComponentType<Comp extends SvelteComponent> = (new (
Comp extends SvelteComponent options: ComponentConstructorOptions<
? (new ( Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
options: ComponentConstructorOptions< >
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any> ) => Comp) & {
> /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
) => Comp) & { element?: typeof HTMLElement;
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ };
element?: typeof HTMLElement;
}
: Comp;
interface DispatchOptions { interface DispatchOptions {
cancelable?: boolean; cancelable?: boolean;

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

Loading…
Cancel
Save