Skip to content

Commit

Permalink
[feat] add convenience types ComponentType and ComponentProps (#6770)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
4 people authored Jul 1, 2022
1 parent 2f562d9 commit 6f57571
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export {
createEventDispatcher,
SvelteComponentDev as SvelteComponent,
SvelteComponentTyped
// additional exports added through post-typegen.js
} from 'svelte/internal';
46 changes: 43 additions & 3 deletions src/runtime/internal/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export interface SvelteComponentDev {
$destroy(): void;
[accessor: string]: any;
}
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:
* ```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

0 comments on commit 6f57571

Please sign in to comment.