From 7bb239348ed61a0810369633409c8e344f9fbebd Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 1 Jul 2022 14:48:10 +0200 Subject: [PATCH] [feat] add convenience types ComponentType and ComponentProps (#6770) ComponentType eases typing "this is a variable that expects a Svelte component constructor (of a certain shape)". Removes the need for SvelteComponentTyped to be an extra type so it can be deprecated in v4 and removed in v5, and SvelteComponent(Dev) can receive the same generic typings as SvelteComponetTyped in v4. ComponentProps eases typing "give me the props this component expects". Closes #7584 Co-authored-by: Simon Holthausen Co-authored-by: Hofer Ivan Co-authored-by: Ignatius Bagus --- CHANGELOG.md | 1 + generate-type-definitions.js | 13 ++++++++++ package-lock.json | 12 +++++----- package.json | 2 +- src/runtime/index.ts | 1 + src/runtime/internal/dev.ts | 46 +++++++++++++++++++++++++++++++++--- 6 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 generate-type-definitions.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca35627cf..b5bc4858d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Faster SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701)) * Fix `class:` directive updates with `` ([#7521](https://github.com/sveltejs/svelte/issues/7521), [#7571](https://github.com/sveltejs/svelte/issues/7571)) * Harden attribute escaping during ssr ([#7530](https://github.com/sveltejs/svelte/pull/7530)) +* Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770)) ## 3.48.0 diff --git a/generate-type-definitions.js b/generate-type-definitions.js new file mode 100644 index 0000000000..6cad31807f --- /dev/null +++ b/generate-type-definitions.js @@ -0,0 +1,13 @@ +// This script generates the TypeScript definitions + +const { execSync } = require('child_process'); +const { readFileSync, writeFileSync } = require('fs'); + +execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly'); + +// We need to add these types to the index.d.ts here because if we add them before building, the build will fail, +// because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime. +// We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet. +const path = 'types/runtime/index.d.ts'; +const content = readFileSync(path, 'utf8'); +writeFileSync(path, content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps')); diff --git a/package-lock.json b/package-lock.json index 3e84c9b8c6..83d963b827 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4402,9 +4402,9 @@ "dev": true }, "node_modules/typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8096,9 +8096,9 @@ "dev": true }, "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, "unbox-primitive": { diff --git a/package.json b/package.json index b3c9c65f98..a1c9ae9a5d 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "pretest": "npm run build", "posttest": "agadoo internal/index.mjs", "prepublishOnly": "node check_publish_env.js && npm run lint && npm test", - "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly", + "tsd": "node ./generate-type-definitions.js", "lint": "eslint \"{src,test}/**/*.{ts,js}\"" }, "repository": { diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 8e12f9f0ee..08b25ba2ca 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -13,4 +13,5 @@ export { createEventDispatcher, SvelteComponentDev as SvelteComponent, SvelteComponentTyped + // additional exports added through post-typegen.js } from 'svelte/internal'; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 3144911672..5b00e7dc5b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -128,7 +128,7 @@ export interface SvelteComponentDev { $destroy(): void; [accessor: string]: any; } -interface IComponentOptions = Record> { +export interface ComponentConstructorOptions = Record> { target: Element | ShadowRoot; anchor?: Element; props?: Props; @@ -164,7 +164,7 @@ export class SvelteComponentDev extends SvelteComponent { */ $$slot_def: any; - constructor(options: IComponentOptions) { + constructor(options: ComponentConstructorOptions) { if (!options || (!options.target && !options.$$inline)) { throw new Error("'target' is a required option"); } @@ -256,11 +256,51 @@ export class SvelteComponentTyped< */ $$slot_def: Slots; - constructor(options: IComponentOptions) { + constructor(options: ComponentConstructorOptions) { super(options); } } +/** + * Convenience type to get the type of a Svelte component. Useful for example in combination with + * dynamic components using ``. + * + * Example: + * ```html + * + * + * + * + * ``` + */ +export type ComponentType = new ( + options: ComponentConstructorOptions< + Component extends SvelteComponentTyped ? Props : Record + > +) => Component; + +/** + * Convenience type to get the props the given component expects. Example: + * ```html + * + * ``` + */ +export type ComponentProps = Component extends SvelteComponentTyped + ? Props + : never; + export function loop_guard(timeout) { const start = Date.now(); return () => {