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

[feat] add convenience types ComponentType and ComponentProps #6770

Merged
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
Expand Up @@ -5,6 +5,7 @@
* Faster SSR ([#5701](https://github.com/sveltejs/svelte/pull/5701))
* Fix `class:` directive updates with `<svelte:element>` ([#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

Expand Down
13 changes: 13 additions & 0 deletions 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'));
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/index.ts
Expand Up @@ -13,4 +13,5 @@ export {
createEventDispatcher,
SvelteComponentDev as SvelteComponent,
SvelteComponentTyped
// additional exports added through post-typegen.js
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
} from 'svelte/internal';
46 changes: 43 additions & 3 deletions src/runtime/internal/dev.ts
Expand Up @@ -128,7 +128,7 @@ export interface SvelteComponentDev {
$destroy(): void;
[accessor: string]: any;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This file exports an interface SvelteComponentDev and a class with the same name. The types of the interface and the class don't match. It was not added in this PR but I find it confusing.

Copy link
Contributor

Choose a reason for hiding this comment

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

also not part of this PR but wouldn't it be better to combine SvelteComponentDev and SvelteComponentTyped. Currently SvelteComponentTyped is just SvelteComponentDev with some generics that all contain default parameters. When not adding any generics this should result into the same type as SvelteComponentDev

interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> {
export interface ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>> {
target: Element | ShadowRoot;
anchor?: Element;
props?: Props;
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -256,11 +256,51 @@ export class SvelteComponentTyped<
*/
$$slot_def: Slots;

constructor(options: IComponentOptions<Props>) {
constructor(options: ComponentConstructorOptions<Props>) {
super(options);
}
}

/**
* Convenience type to get the type of a Svelte component. Useful for example in combination with
* dynamic components using `<svelte:component>`.
*
* Example:
* ```html
* <script lang="ts">
* import type { ComponentType, SvelteComponentTyped } 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;
* </script>
*
* <svelte:component this={component} />
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
export type ComponentType<Component extends SvelteComponentTyped = SvelteComponentTyped> = new (
options: ComponentConstructorOptions<
Component extends SvelteComponentTyped<infer Props> ? Props : Record<string, any>
>
) => Component;

/**
* Convenience type to get the props the given component expects. Example:
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
* ```html
* <script lang="ts">
* import type { ComponentProps } from 'svelte';
* import Component from './Component.svelte';
*
* const props: ComponentProps<Component> = { foo: 'bar' }; // Errors if these aren't the correct props
* </script>
* ```
*/
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentTyped<infer Props>
? Props
: never;

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