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

Discussion: How can we type the events supported by a component #424

Open
AlexGalays opened this issue Aug 4, 2020 · 6 comments
Open

Discussion: How can we type the events supported by a component #424

AlexGalays opened this issue Aug 4, 2020 · 6 comments
Labels
feature request New feature or request

Comments

@AlexGalays
Copy link

AlexGalays commented Aug 4, 2020

The goal is to easily catch typos and help the discovery of all possible events dispatched by a component. After all, there is no reason why the inputs (props) can have a strong contract but the outputs (events) don't. In fact, it's even more important than for props, because the dispatching can occur about anywhere in the file, whereas props are usually neatly bunched together are the top.

Right now I can type on:qoidfoqidjoiqsjd just fine.

Flex, which also had its compiler used custom annotations for this: https://github.com/apache/flex-sdk/blob/master/frameworks/projects/mx/src/mx/core/Container.as#L97

Perhaps we could have a special, reserved convention like export type Events = {name: 'eventA', data: number} | ...
Having the TSDoc carry over on the consumer side tooltip would be the icing on the cake.

@dummdidumm dummdidumm added the feature request New feature or request label Aug 5, 2020
@dummdidumm
Copy link
Member

dummdidumm commented Aug 5, 2020

The reason why this cannot be implemented easily is that it's a lot harder to collect all possible events than to collect the props. That's also the reason why autocompletion does not work because these are for props.

Looking under the hood it's that props are implemented through jsx props. Events are not because of the limitations of collecting them. If the user would explicitly type them this could be changed. We cannot mark the dispatched events as "this does not conform to the definition" though because of the challenges mentioned.

Having a reserved interface is likely the way forward. We have thought about this before in the context of generic props. Reserved interface names could be ComponentEvents, ComponentProps, ComponentSlots and ComponentDef for typing all three.

Related #304

@dummdidumm
Copy link
Member

Getting autocompletion with on:<event> has to be done differently because under the hood we use JSX to get these autocompletions. To get autocompletion "out of the box", we would need to have on:<event> as part of the props. But this is not possible because JSX does not allow characters like : in props.
Another problem is that we somehow would need to convert the events type definition to prepend the on:, which cannot be done with TypeScript at the moment (related TS issue).

Two solutions arise from these constraints:

  • Tell devs to type their events like __on__<eventName>: .. and we would in the language server then replace __on__ with on: during autocomplete. This feels suboptimal and brittle.
  • Implement our "own" autocompletion for this. This is possibly a hard task, but would lead to a clean solution.

Getting "on:XXX does not exist" errors is easily possible after #386 is merged through a variation of the $on method definition not falling back to CustomEvent<any> if users type their events explicitely.

@dummdidumm
Copy link
Member

Once sveltejs/svelte#5260 is released we can try to update svelte2tsx so that it searches for createEventDispatcher, looks if it has a type annotation, and if yes, uses that to get proper types.

dummdidumm pushed a commit to dummdidumm/language-tools that referenced this issue Aug 17, 2020
This adds the possibility to use a reserved interface name `ComponentEvents` and define all possible events within it.
Also adds autocompletion for these events.
Also disables autocompletions from HTMLPlugin on component tags.

sveltejs#424 sveltejs#304
dummdidumm added a commit that referenced this issue Aug 20, 2020
This adds the possibility to use a reserved interface name `ComponentEvents` and define all possible events within it.
Also adds autocompletion for these events.
Also disables autocompletions from HTMLPlugin on component tags.
Also fixes an import type bug.

#424 #304
dummdidumm added a commit that referenced this issue Sep 17, 2020
#424

- now tries to detect event names from script/template if no typings given
- supports typed createEventDispatcher
@dummdidumm
Copy link
Member

You can now take advantage of the createEventDispatcher typing introduced in Svelte 3.25.0. If you do

const dispatch = createEventDispatcher<{foo: string; bar: boolean}>();

You get strong typing for dispatch within the component, and if you listen to the foo/bar events, you'll get strong typings:

on:foo={e => ... // <- e is of type CustomEvent<string>

You'll also get much better autocompletion for events now. Note however that you are still allowed to listen to other events, so type safety in the sense of "only listen to events defined through createEventDispatcher" is still only possible through the ComponentEvents interface. But we plan on providing something (maybe a script tag attribute) which would make the events strict.

Related docs: https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md#typing-component-events

@linuxuser586
Copy link

@dummdidumm

Any ideas on why some listening events for createEventDispatcher don't receive the type?

Example:

// Component Foo
const dispatchFoo = createEventDispatcher<{foo: string; bar: boolean}>();
const dispatchBar = createEventDispatcher<{bar: string; baz: string}>();

// Component Bar
// e is CustomEvent<any>
on:foo="{(e) => handleFoo(e.detail.bar)}"
// e is CustomEvent<{baz: string}>
on:bar="{(e) => handleBar(e.detail.bar)}"

The typescript compiler catches this as expected.

Changing the order produces different results.

if the createEventDispatcher order changes, then the typescript compiler does not catch the error.
It appears that only the last createEventDispatcher produces the type for the listener.

// Component Foo
const dispatchBar = createEventDispatcher<{bar: string; baz: string}>();
const dispatchFoo = createEventDispatcher<{foo: string; bar: boolean}>();

// Component Bar
// e is CustomEvent<{bar: boolean}>
on:foo="{(e) => handleFoo(e.detail.bar)}"
// e is CustomEvent<any>
on:bar="{(e) => handleBar(e.detail.bar)}"

The typescript compiler does not catch the error since the type is now CustomEvent<any>.

dummdidumm pushed a commit to dummdidumm/language-tools that referenced this issue Jun 14, 2021
$$Events lets the user define all events that a component can dispatch. If createEventDispatcher is not typed, the $$Events definition is added to it under the hood for type checking.
strictEvents can be used when the user is sure that there are no other events besides the one from createEventDispatcher and forwarded events - this removes the custom event fallback typing.

sveltejs#442
sveltejs#424
sveltejs/rfcs#38
dummdidumm added a commit that referenced this issue Jun 15, 2021
$$Events lets the user define all events that a component can dispatch. If createEventDispatcher is not typed, the $$Events definition is added to it under the hood for type checking.
strictEvents can be used when the user is sure that there are no other events besides the one from createEventDispatcher and forwarded events - this removes the custom event fallback typing.

#442
#424
sveltejs/rfcs#38
@dummdidumm
Copy link
Member

Experimental support for typing events is now available. See the RFC on how to use it. Please provide feedback in #442

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants