Skip to content

Commit 76c40ef

Browse files
authored
(feat) event typings (#535)
#424 - now tries to detect event names from script/template if no typings given - supports typed createEventDispatcher
1 parent a872bc6 commit 76c40ef

File tree

17 files changed

+525
-122
lines changed

17 files changed

+525
-122
lines changed

docs/preprocessors/typescript.md

+32-10
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,31 @@ Hit `ctrl-shift-p` or `cmd-shift-p` on mac, type `svelte restart`, and select `S
4444

4545
## Typing component events
4646

47-
When you are using TypeScript, you can type which events your component has by defining a reserved `interface` (_NOT_ `type`) called `ComponentEvents`:
47+
When you are using TypeScript, you can type which events your component has in two ways:
48+
49+
The first and possibly most often used way is to type the `createEventDispatcher` invocation like this:
50+
51+
```html
52+
<script lang="ts">
53+
import { createEventDispatcher } from 'svelte';
54+
55+
const dispatch = createEventDispatcher<{
56+
/**
57+
* you can also add docs
58+
*/
59+
checked: boolean; // Will translate to `CustomEvent<boolean>`
60+
hello: string;
61+
}>();
62+
63+
// ...
64+
</script>
65+
```
66+
67+
This will make sure that if you use `dispatch` that you can only invoke it with the specified names and its types.
68+
69+
Note though that this will _NOT_ make the events strict so that you get type errors when trying to listen to other events when using the component. Due to Svelte's dynamic events creation, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of another import. This is almost impossible to infer.
70+
71+
If you want strict events, you can do so by defining a reserved `interface` (_NOT_ `type`) called `ComponentEvents`:
4872

4973
```html
5074
<script lang="ts">
@@ -72,8 +96,6 @@ If you want to be sure that the interface definition names correspond to your di
7296
</script>
7397
```
7498

75-
> In case you ask why the events cannot be infered: Due to Svelte's dynamic nature, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of a mixin. This is almost impossible to infer, so we need you to tell us which events are possible.
76-
7799
## Troubleshooting / FAQ
78100

79101
### I cannot use TS inside my script even when `lang="ts"` is present
@@ -130,13 +152,13 @@ Create a `additional-svelte-jsx.d.ts` file:
130152

131153
```ts
132154
declare namespace svelte.JSX {
133-
interface HTMLAttributes<T> {
134-
// If you want to use on:beforeinstallprompt
135-
onbeforeinstallprompt?: (event: any) => any;
136-
// If you want to use myCustomAttribute={..} (note: all lowercase)
137-
mycustomattribute?: any;
138-
// You can replace any with something more specific if you like
139-
}
155+
interface HTMLAttributes<T> {
156+
// If you want to use on:beforeinstallprompt
157+
onbeforeinstallprompt?: (event: any) => any;
158+
// If you want to use myCustomAttribute={..} (note: all lowercase)
159+
mycustomattribute?: any;
160+
// You can replace any with something more specific if you like
161+
}
140162
}
141163
```
142164

packages/svelte2tsx/src/svelte2tsx/index.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import path from 'path';
55
import { convertHtmlxToJsx } from '../htmlxtojsx';
66
import { parseHtmlx } from '../utils/htmlxparser';
77
import { ComponentDocumentation } from './nodes/ComponentDocumentation';
8-
import {
9-
ComponentEvents,
10-
ComponentEventsFromEventsMap,
11-
ComponentEventsFromInterface,
12-
} from './nodes/ComponentEvents';
8+
import { ComponentEvents } from './nodes/ComponentEvents';
139
import { EventHandler } from './nodes/event-handler';
1410
import { ExportedNames } from './nodes/ExportedNames';
1511
import { createClassGetters, createRenderFunctionGetterStr } from './nodes/exportgetters';
@@ -179,6 +175,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
179175
case 'Identifier':
180176
handleIdentifier(node);
181177
stores.handleIdentifier(node, parent, prop);
178+
eventHandler.handleIdentifier(node, parent, prop);
182179
break;
183180
case 'Slot':
184181
slotHandler.handleSlot(node, templateScope);
@@ -266,7 +263,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
266263
moduleScriptTag,
267264
scriptTag,
268265
slots: slotHandler.getSlotDef(),
269-
events: new ComponentEventsFromEventsMap(eventHandler),
266+
events: new ComponentEvents(eventHandler),
270267
uses$$props,
271268
uses$$restProps,
272269
uses$$slots,
@@ -472,7 +469,7 @@ export function svelte2tsx(
472469
str,
473470
uses$$propsOr$$restProps: uses$$props || uses$$restProps,
474471
strictMode: !!options?.strictMode,
475-
strictEvents: events instanceof ComponentEventsFromInterface,
472+
strictEvents: events.hasInterface(),
476473
isTsFile: options?.isTsFile,
477474
getters,
478475
fileName: options?.filename,
@@ -485,6 +482,6 @@ export function svelte2tsx(
485482
code: str.toString(),
486483
map: str.generateMap({ hires: true, source: options?.filename }),
487484
exportedNames,
488-
events,
485+
events: events.createAPI(),
489486
};
490487
}

0 commit comments

Comments
 (0)