Skip to content

Commit 6f57571

Browse files
dummdidummSimon Holthausenivanhoferignatiusmb
authored
[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 <simon.holthausen@accso.de> Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> Co-authored-by: Ignatius Bagus <ignatius.mbs@gmail.com>
1 parent 2f562d9 commit 6f57571

File tree

6 files changed

+65
-10
lines changed

6 files changed

+65
-10
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Faster SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701))
66
* Fix `class:` directive updates with `<svelte:element>` ([#7521](https://github.com/sveltejs/svelte/issues/7521), [#7571](https://github.com/sveltejs/svelte/issues/7571))
77
* Harden attribute escaping during ssr ([#7530](https://github.com/sveltejs/svelte/pull/7530))
8+
* Add `ComponentType` and `ComponentProps` convenience types ([#6770](https://github.com/sveltejs/svelte/pull/6770))
89

910
## 3.48.0
1011

generate-type-definitions.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This script generates the TypeScript definitions
2+
3+
const { execSync } = require('child_process');
4+
const { readFileSync, writeFileSync } = require('fs');
5+
6+
execSync('tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly');
7+
8+
// We need to add these types to the index.d.ts here because if we add them before building, the build will fail,
9+
// because the TS->JS transformation doesn't know these exports are types and produces code that fails at runtime.
10+
// We can't use `export type` syntax either because the TS version we're on doesn't have this feature yet.
11+
const path = 'types/runtime/index.d.ts';
12+
const content = readFileSync(path, 'utf8');
13+
writeFileSync(path, content.replace('SvelteComponentTyped', 'SvelteComponentTyped, ComponentType, ComponentConstructorOptions, ComponentProps'));

package-lock.json

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"pretest": "npm run build",
9696
"posttest": "agadoo internal/index.mjs",
9797
"prepublishOnly": "node check_publish_env.js && npm run lint && npm test",
98-
"tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
98+
"tsd": "node ./generate-type-definitions.js",
9999
"lint": "eslint \"{src,test}/**/*.{ts,js}\""
100100
},
101101
"repository": {

src/runtime/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ export {
1313
createEventDispatcher,
1414
SvelteComponentDev as SvelteComponent,
1515
SvelteComponentTyped
16+
// additional exports added through post-typegen.js
1617
} from 'svelte/internal';

src/runtime/internal/dev.ts

+43-3
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export interface SvelteComponentDev {
128128
$destroy(): void;
129129
[accessor: string]: any;
130130
}
131-
interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> {
131+
export interface ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>> {
132132
target: Element | ShadowRoot;
133133
anchor?: Element;
134134
props?: Props;
@@ -164,7 +164,7 @@ export class SvelteComponentDev extends SvelteComponent {
164164
*/
165165
$$slot_def: any;
166166

167-
constructor(options: IComponentOptions) {
167+
constructor(options: ComponentConstructorOptions) {
168168
if (!options || (!options.target && !options.$$inline)) {
169169
throw new Error("'target' is a required option");
170170
}
@@ -256,11 +256,51 @@ export class SvelteComponentTyped<
256256
*/
257257
$$slot_def: Slots;
258258

259-
constructor(options: IComponentOptions<Props>) {
259+
constructor(options: ComponentConstructorOptions<Props>) {
260260
super(options);
261261
}
262262
}
263263

264+
/**
265+
* Convenience type to get the type of a Svelte component. Useful for example in combination with
266+
* dynamic components using `<svelte:component>`.
267+
*
268+
* Example:
269+
* ```html
270+
* <script lang="ts">
271+
* import type { ComponentType, SvelteComponentTyped } from 'svelte';
272+
* import Component1 from './Component1.svelte';
273+
* import Component2 from './Component2.svelte';
274+
*
275+
* const component: ComponentType = someLogic() ? Component1 : Component2;
276+
* const componentOfCertainSubType: ComponentType<SvelteComponentTyped<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
277+
* </script>
278+
*
279+
* <svelte:component this={component} />
280+
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
281+
* ```
282+
*/
283+
export type ComponentType<Component extends SvelteComponentTyped = SvelteComponentTyped> = new (
284+
options: ComponentConstructorOptions<
285+
Component extends SvelteComponentTyped<infer Props> ? Props : Record<string, any>
286+
>
287+
) => Component;
288+
289+
/**
290+
* Convenience type to get the props the given component expects. Example:
291+
* ```html
292+
* <script lang="ts">
293+
* import type { ComponentProps } from 'svelte';
294+
* import Component from './Component.svelte';
295+
*
296+
* const props: ComponentProps<Component> = { foo: 'bar' }; // Errors if these aren't the correct props
297+
* </script>
298+
* ```
299+
*/
300+
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentTyped<infer Props>
301+
? Props
302+
: never;
303+
264304
export function loop_guard(timeout) {
265305
const start = Date.now();
266306
return () => {

0 commit comments

Comments
 (0)