From 9a99554379aa3f7bb66fd06e1f564b9800aa6247 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:23:05 +0100 Subject: [PATCH] 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> - If you're using generics, you can do export class MyComponent 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 --- .changeset/small-papayas-laugh.md | 5 + packages/svelte/scripts/build.js | 2 +- packages/svelte/src/internal/client/render.js | 4 +- packages/svelte/src/legacy/legacy-client.js | 12 +- packages/svelte/src/legacy/legacy-server.js | 9 +- packages/svelte/src/legacy/public.d.ts | 93 ---------- packages/svelte/src/main/ambient.d.ts | 2 +- packages/svelte/src/main/public.d.ts | 163 ++++++++++++------ packages/svelte/tests/types/component.ts | 63 ++++--- 9 files changed, 166 insertions(+), 187 deletions(-) create mode 100644 .changeset/small-papayas-laugh.md delete mode 100644 packages/svelte/src/legacy/public.d.ts diff --git a/.changeset/small-papayas-laugh.md b/.changeset/small-papayas-laugh.md new file mode 100644 index 0000000000..43475946cb --- /dev/null +++ b/.changeset/small-papayas-laugh.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +breaking: remove Component type, keep using SvelteComponent instead diff --git a/packages/svelte/scripts/build.js b/packages/svelte/scripts/build.js index 4b49fa76e9..00e0920a3a 100644 --- a/packages/svelte/scripts/build.js +++ b/packages/svelte/scripts/build.js @@ -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`, diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 2ef1033638..045d3b5bf8 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -3020,7 +3020,7 @@ export function unwrap(value) { * @template {Record} Props * @template {Record | undefined} Exports * @template {Record} Events - * @param {import('../../main/public.js').Component} component + * @param {import('../../main/public.js').SvelteComponent} component * @param {{ * target: Node; * props?: Props; @@ -3139,7 +3139,7 @@ export function createRoot(component, options) { * @template {Record} Props * @template {Record | undefined} Exports * @template {Record} Events - * @param {import('../../main/public.js').Component} component + * @param {import('../../main/public.js').SvelteComponent} component * @param {{ * target: Node; * props?: Props; diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index ec6560cba1..8c63213208 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -11,12 +11,12 @@ import * as $ from '../internal/index.js'; * @template {Record} Events * @template {Record} Slots * - * @param {import('./public.js').ComponentConstructorOptions & { - * component: import('../main/public.js').Component; + * @param {import('../main/public.js').ComponentConstructorOptions & { + * component: import('../main/public.js').SvelteComponent; * immutable?: boolean; * recover?: false; * }} options - * @returns {import('./public.js').SvelteComponent} + * @returns {import('../main/public.js').SvelteComponent & 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} Events * @template {Record} Slots * - * @param {import('../main/public.js').Component} component - * @returns {typeof import('./public.js').SvelteComponent} + * @param {import('../main/public.js').SvelteComponent} component + * @returns {typeof import('../main/public.js').SvelteComponent & 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; diff --git a/packages/svelte/src/legacy/legacy-server.js b/packages/svelte/src/legacy/legacy-server.js index d11bbd5873..aeba694ea6 100644 --- a/packages/svelte/src/legacy/legacy-server.js +++ b/packages/svelte/src/legacy/legacy-server.js @@ -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} Events * @template {Record} Slots * - * @param {import('../main/public.js').Component} component - * @returns {typeof import('./public.js').SvelteComponent} + * @param {import('../main/public.js').SvelteComponent} component + * @returns {typeof import('../main/public.js').SvelteComponent & 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; } diff --git a/packages/svelte/src/legacy/public.d.ts b/packages/svelte/src/legacy/public.d.ts deleted file mode 100644 index dd6eb9725e..0000000000 --- a/packages/svelte/src/legacy/public.d.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * @deprecated Use `Component` instead. See TODO for more information. - */ -export interface ComponentConstructorOptions< - Props extends Record = Record -> { - target: Element | Document | ShadowRoot; - anchor?: Element; - props?: Props; - context?: Map; - 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 - * - * - * ``` - */ -export class SvelteComponent< - Props extends Record = any, - Events extends Record = any, - Slots extends Record = any -> { - [prop: string]: any; - - constructor(options: ComponentConstructorOptions); - /** - * 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>( - type: K, - callback: (e: Events[K]) => void - ): () => void; - - $set(props: Partial): void; -} - -/** - * @deprecated Use `Component` instead. See TODO for more information. - */ -export class SvelteComponentTyped< - Props extends Record = any, - Events extends Record = any, - Slots extends Record = any -> extends SvelteComponent {} - -export * from './legacy-client.js'; diff --git a/packages/svelte/src/main/ambient.d.ts b/packages/svelte/src/main/ambient.d.ts index a48fc73e58..d80e8e4323 100644 --- a/packages/svelte/src/main/ambient.d.ts +++ b/packages/svelte/src/main/ambient.d.ts @@ -1,5 +1,5 @@ declare module '*.svelte' { - export { Component as default } from 'svelte'; + export { SvelteComponent as default } from 'svelte'; } /** diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index b01e3bad11..f6d1ab7571 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -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 = Record +> { + target: Element | Document | ShadowRoot; + anchor?: Element; + props?: Props; + context?: Map; + 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 }; * * * ``` + * + * 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 = {}, - Exports extends Record | undefined = undefined, - Events extends Record = {}, - Slots extends Record = {} +export class SvelteComponent< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = 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); + /** + * 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>( + type: K, + callback: (e: Events[K]) => void + ): () => void; /** - * ## 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; + $set(props: Partial): void; } +/** + * @deprecated Use `SvelteComponent` instead. See TODO for more information. + */ +export class SvelteComponentTyped< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any +> extends SvelteComponent {} + /** * Convenience type to get the events the given component expects. Example: * ```html @@ -69,12 +135,12 @@ export interface Component< * * ``` */ -export type ComponentEvents | SvelteComponent> = - Comp extends SvelteComponent - ? Events - : Comp extends Component - ? Events - : never; +export type ComponentEvents = Comp extends SvelteComponent< + any, + infer Events +> + ? Events + : never; /** * Convenience type to get the props the given component expects. Example: @@ -87,16 +153,12 @@ export type ComponentEvents | SvelteC * * ``` */ -export type ComponentProps | SvelteComponent> = - Comp extends SvelteComponent - ? Props - : Comp extends Component - ? Props - : never; +export type ComponentProps = Comp extends SvelteComponent + ? 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 ``. * * Example: @@ -114,17 +176,14 @@ export type ComponentProps | SvelteCo * * ``` */ -export type ComponentType | SvelteComponent> = - Comp extends SvelteComponent - ? (new ( - options: ComponentConstructorOptions< - Comp extends SvelteComponent ? Props : Record - > - ) => Comp) & { - /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ - element?: typeof HTMLElement; - } - : Comp; +export type ComponentType = (new ( + options: ComponentConstructorOptions< + Comp extends SvelteComponent ? Props : Record + > +) => Comp) & { + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + element?: typeof HTMLElement; +}; interface DispatchOptions { cancelable?: boolean; diff --git a/packages/svelte/tests/types/component.ts b/packages/svelte/tests/types/component.ts index e75d433992..749c648e2f 100644 --- a/packages/svelte/tests/types/component.ts +++ b/packages/svelte/tests/types/component.ts @@ -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 = { // --------------------------------------------------------------------------- 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; +const newComponentType: ComponentType = NewComponent; const newComponentProps1: ComponentProps = { 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;