diff --git a/site/content/blog/2020-12-01-whats-new-in-svelte-december-2020.md b/site/content/blog/2020-12-01-whats-new-in-svelte-december-2020.md index 5edaf31311..2bcb9f105c 100644 --- a/site/content/blog/2020-12-01-whats-new-in-svelte-december-2020.md +++ b/site/content/blog/2020-12-01-whats-new-in-svelte-december-2020.md @@ -11,7 +11,7 @@ It's the last "What's new in Svelte" of the year and there's lots to celebrate! 1. `$$props`, `$$restProps`, and `$$slots` are all now supported in custom web components (**3.29.5**, [Example](https://svelte.dev/repl/ad8e6f39cd20403dacd1be84d71e498d?version=3.29.5)) and `slot` components now support spread props: `` (**3.30.0**) 2. A new `hasContext` lifecycle function makes it easy to check whether a `key` has been set in the context of a parent component (**3.30.0** & **3.30.1**, [Docs](https://svelte.dev/docs#hasContext)) -3. `SvelteComponent` is now typed which makes it easier to add typed classes that extend base Svelte Components. Component library and framework authors rejoice! An example: `export class YourComponent extends SvelteComponent<{aProp: boolean}, {click: MouseEvent}, {default: {aSlot: string}}> {}` (**3.30.0**, [RFC](https://github.com/sveltejs/rfcs/pull/37)) +3. There is now a new `SvelteComponentTyped` class which makes it easier to add strongly typed components that extend base Svelte components. Component library and framework authors rejoice! An example: `export class YourComponent extends SvelteComponentTyped<{aProp: boolean}, {click: MouseEvent}, {default: {aSlot: string}}> {}` (**3.30.0**, [RFC](https://github.com/sveltejs/rfcs/pull/37)) 4. Transitions within `{:else}` blocks should now complete successfully (**3.29.5**, [Example](https://svelte.dev/repl/49cef205e5da459594ef2eafcbd41593?version=3.29.5)) 5. Svelte now includes an export map, which explicitly states which files can be imported from its npm package (**3.29.5** with some fixes in **3.29.6**, **3.29.7** and **3.30.0**) 6. `rollup-plugin-svelte` had a new [7.0.0 release](https://github.com/sveltejs/rollup-plugin-svelte/blob/master/CHANGELOG.md). The biggest change is that the `css` option was removed. Users who were using that option should add another plugin like `rollup-plugin-css-only` as demonstrated [in the template](https://github.com/sveltejs/template/blob/5b1135c286f7a649daa99825a077586655051649/rollup.config.js#L48) diff --git a/src/runtime/index.ts b/src/runtime/index.ts index f1154b092f..b3451ed5cb 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -10,5 +10,6 @@ export { hasContext, tick, createEventDispatcher, - SvelteComponentDev as SvelteComponent + SvelteComponentDev as SvelteComponent, + SvelteComponentTyped } from 'svelte/internal'; diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index d107dd3997..c503a507ff 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -212,19 +212,19 @@ if (typeof HTMLElement === 'function') { }; } -export class SvelteComponent< - Props extends Record = any, - Events extends Record = any -> { +/** + * Base class for Svelte components. Used when dev=false. + */ +export class SvelteComponent { $$: T$$; - $$set?: ($$props: Partial) => void; + $$set?: ($$props: any) => void; $destroy() { destroy_component(this, 1); this.$destroy = noop; } - $on>(type: K, callback: (e: Events[K]) => void) { + $on(type, callback) { const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); callbacks.push(callback); @@ -234,7 +234,7 @@ export class SvelteComponent< }; } - $set($$props: Partial) { + $set($$props) { if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true; this.$$set($$props); diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index e93523572f..74bf581bab 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -97,7 +97,57 @@ export function validate_slots(name, slot, keys) { } } -export interface SvelteComponentDev< +type Props = Record; +export interface SvelteComponentDev { + $set(props?: Props): void; + $on(event: string, callback: (event: any) => void): () => void; + $destroy(): void; + [accessor: string]: any; +} +/** + * Base class for Svelte components with some minor dev-enhancements. Used when dev=true. + */ +export class SvelteComponentDev extends SvelteComponent { + /** + * @private + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$prop_def: Props; + + constructor(options: { + target: Element; + anchor?: Element; + props?: Props; + hydrate?: boolean; + intro?: boolean; + $$inline?: boolean; + }) { + if (!options || (!options.target && !options.$$inline)) { + throw new Error("'target' is a required option"); + } + + super(); + } + + $destroy() { + super.$destroy(); + this.$destroy = () => { + console.warn('Component was already destroyed'); // eslint-disable-line no-console + }; + } + + $capture_state() {} + + $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. + +export interface SvelteComponentTyped< Props extends Record = any, Events extends Record = any, Slots extends Record = any @@ -107,12 +157,42 @@ export interface SvelteComponentDev< $destroy(): void; [accessor: string]: any; } - -export class SvelteComponentDev< +/** + * 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 + * + * + * ``` + * + * #### 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 seperate the more strictly typed class. + */ +export class SvelteComponentTyped< Props extends Record = any, Events extends Record = any, Slots extends Record = any -> extends SvelteComponent { +> extends SvelteComponentDev { /** * @private * For type checking capabilities only. @@ -142,24 +222,9 @@ export class SvelteComponentDev< hydrate?: boolean; intro?: boolean; $$inline?: boolean; - }) { - if (!options || (!options.target && !options.$$inline)) { - throw new Error("'target' is a required option"); - } - - super(); + }) { + super(options); } - - $destroy() { - super.$destroy(); - this.$destroy = () => { - console.warn('Component was already destroyed'); // eslint-disable-line no-console - }; - } - - $capture_state() {} - - $inject_state() {} } export function loop_guard(timeout) {