Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking: deprecate SvelteComponentTyped, add generics to SvelteComponent #8512

Merged
merged 3 commits into from
Apr 24, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136))
* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512))
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))
Expand Down
13 changes: 8 additions & 5 deletions elements/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ export interface DOMAttributes<T extends EventTarget> {
'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null;
'on:input'?: FormEventHandler<T> | undefined | null;
'on:reset'?: FormEventHandler<T> | undefined | null;
'on:submit'?: EventHandler<Event & { readonly submitter: HTMLElement | null; }, T> | undefined | null; // TODO make this SubmitEvent once we require TS>=4.4
'on:submit'?: EventHandler<SubmitEvent, T> | undefined | null;
'on:invalid'?: EventHandler<Event, T> | undefined | null;
'on:formdata'?: EventHandler<Event & { readonly formData: FormData; }, T> | undefined | null; // TODO make this FormDataEvent once we require TS>=4.4
'on:formdata'?: EventHandler<FormDataEvent, T> | undefined | null;

// Image Events
'on:load'?: EventHandler | undefined | null;
Expand Down Expand Up @@ -547,9 +547,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'bind:innerText'?: string | undefined | null;

readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;

// SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
Expand All @@ -558,6 +558,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;

// allow any data- attribute
[key: `data-${string}`]: any;
}

export type HTMLAttributeAnchorTarget =
Expand Down
129 changes: 48 additions & 81 deletions src/runtime/internal/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,13 @@ export function construct_svelte_component_dev(component, props) {
}
}

type Props = Record<string, any>;
export interface SvelteComponentDev {
$set(props?: Props): void;
$on(event: string, callback: ((event: any) => void) | null | undefined): () => void;
export interface SvelteComponentDev<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars
> {
$set(props?: Partial<Props>): void;
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
$destroy(): void;
[accessor: string]: any;
}
Expand All @@ -177,8 +180,33 @@ export interface ComponentConstructorOptions<Props extends Record<string, any> =

/**
* Base class for Svelte components with some minor dev-enhancements. Used when dev=true.
*
* 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 SvelteComponentDev extends SvelteComponent {
export class SvelteComponentDev<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponent {
/**
* @private
* For type checking capabilities only.
Expand All @@ -192,16 +220,16 @@ export class SvelteComponentDev extends SvelteComponent {
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$events_def: any;
$$events_def: Events;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$slot_def: any;
$$slot_def: Slots;

constructor(options: ComponentConstructorOptions) {
constructor(options: ComponentConstructorOptions<Props>) {
if (!options || (!options.target && !options.$$inline)) {
throw new Error("'target' is a required option");
}
Expand All @@ -221,82 +249,21 @@ export class SvelteComponentDev extends SvelteComponent {
$inject_state() {}
}

// TODO https://github.com/microsoft/TypeScript/issues/41770 is the reason
// why we have to split out SvelteComponentTyped to not break existing usage of SvelteComponent.
// Try to find a better way for Svelte 4.0.

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SvelteComponentTyped<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars
> {
$set(props?: Partial<Props>): void;
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
$destroy(): void;
[accessor: string]: any;
}
Slots extends Record<string, any> = any
> extends SvelteComponentDev<Props, Events, Slots> {}

/**
* Base class to create strongly typed Svelte components.
* This only exists for typing purposes and should be used in `.d.ts` files.
*
* ### 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 { SvelteComponentTyped } from "svelte";
* export class MyComponent extends SvelteComponentTyped<{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'} />
* ```
*
* #### Why not make this part of `SvelteComponent(Dev)`?
* Because
* ```ts
* class ASubclassOfSvelteComponent extends SvelteComponent<{foo: string}> {}
* const component: typeof SvelteComponent = ASubclassOfSvelteComponent;
* ```
* will throw a type error, so we need to separate the more strictly typed class.
* @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove SvelteComponentTyped completely if we will release this PR in Svelte4 because this is a major version up?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but I think it's better to do that in Svelte 5 to give people time to gracefully update their code. Also if some use components built for Svelte 3 which you can otherwise use in Svelte 4 we shouldn't block that just because the class was removed. Right now svelte package uses SvelteCompentTyped in the generated d.ts files.

*/
export class SvelteComponentTyped<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponentDev {
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$prop_def: Props;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$events_def: Events;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$slot_def: Slots;

constructor(options: ComponentConstructorOptions<Props>) {
super(options);
}
}
> extends SvelteComponentDev<Props, Events, Slots> {}

/**
* Convenience type to get the type of a Svelte component. Useful for example in combination with
Expand All @@ -305,21 +272,21 @@ export class SvelteComponentTyped<
* Example:
* ```html
* <script lang="ts">
* import type { ComponentType, SvelteComponentTyped } from 'svelte';
* import type { ComponentType, SvelteComponent } from 'svelte';
* import Component1 from './Component1.svelte';
* import Component2 from './Component2.svelte';
*
* const component: ComponentType = someLogic() ? Component1 : Component2;
* const componentOfCertainSubType: ComponentType<SvelteComponentTyped<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
* const componentOfCertainSubType: ComponentType<SvelteComponent<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
* </script>
*
* <svelte:component this={component} />
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Component extends SvelteComponentTyped = SvelteComponentTyped> = new (
export type ComponentType<Component extends SvelteComponentDev = SvelteComponentDev> = new (
options: ComponentConstructorOptions<
Component extends SvelteComponentTyped<infer Props> ? Props : Record<string, any>
Component extends SvelteComponentDev<infer Props> ? Props : Record<string, any>
>
) => Component;

Expand All @@ -334,7 +301,7 @@ export type ComponentType<Component extends SvelteComponentTyped = SvelteCompone
* </script>
* ```
*/
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentTyped<infer Props>
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentDev<infer Props>
? Props
: never;

Expand All @@ -354,7 +321,7 @@ export type ComponentProps<Component extends SvelteComponent> = Component extend
* ```
*/
export type ComponentEvents<Component extends SvelteComponent> =
Component extends SvelteComponentTyped<any, infer Events> ? Events : never;
Component extends SvelteComponentDev<any, infer Events> ? Events : never;

export function loop_guard(timeout) {
const start = Date.now();
Expand Down