Svelte: Reduce boilerplate for firing events

Created on 31 Aug 2019  路  13Comments  路  Source: sveltejs/svelte

Instead of requiring this boilerplate:

import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();

with the warning that the call to createEventDispatcher _must_ happen at the top level, just provide a global variable dispatch (maybe there can be a project-level option for the name), and if you detect that it is used, emit the boilerplate during compilation, otherwise omit it.

proposal

Most helpful comment

I'm not sold on this being a great idea, but technically I don't think this would be much of a hassle.

There's precedent for special $$-prefix variables (although currently it's a precedent of one, $$props). It'd be pretty easy to inject the appropriate import and call createEventDispatcher() and assign it to $$dispatch if that variable were used.

I can't speak to other tooling, but there's a mechanism already in place that the compiler is using to tell eslint-plugin-svelte3 about variables that it's injecting (used for $$props as well as the $-prefixed store autosubscriptions), and this could be used to tell the plugin about $$dispatch without any more work needed in the plugin.

But, again, none of this is necessarily an endorsement of this being a good idea. Just that it's reasonable from a technical standpoint.

All 13 comments

I feel like having a magical global variable would reduce the ability to reason about svelte code.

It's also important for code authors to understand that dispatch fires only from the components it is initialised in, and cannot be passed around.

This would be very hard to express with a global variable.

We have Number, Object, Array, etc. in the global context. I think most people deal just fine with it.

My biggest point here is that Svelte is not quite realizing the full potential of a compiler. Svelte can diagnose passing dispatch to a nested element as an error.

Number, Array and Object are builtins/language native, not globals.

The point of Svelte is to create a language to describe reactive user interfaces, not to eradicate all boilerplate.

A goal of svelte code is to maintain the path of least surprise, make code easy to reason about, live within the bounds of the javascript language, and not require custom tooling to write, parse, or understand.

A global dispatch variable would cause problems for most existing tooling without specific configuration / whitelisting.

I'm not sold on this being a great idea, but technically I don't think this would be much of a hassle.

There's precedent for special $$-prefix variables (although currently it's a precedent of one, $$props). It'd be pretty easy to inject the appropriate import and call createEventDispatcher() and assign it to $$dispatch if that variable were used.

I can't speak to other tooling, but there's a mechanism already in place that the compiler is using to tell eslint-plugin-svelte3 about variables that it's injecting (used for $$props as well as the $-prefixed store autosubscriptions), and this could be used to tell the plugin about $$dispatch without any more work needed in the plugin.

But, again, none of this is necessarily an endorsement of this being a good idea. Just that it's reasonable from a technical standpoint.

Just to add a voice to this proposal, and +1, Svelte's power is in it's lack of boilerplate. I can create a component without any imports and just start writing code.

The create event dispatcher call feels completely out of place in within the context of the rest of svelte, it also uses oddly object oriented language when the rest of svelte is quite functional and data oriented.

I shouldn't need to bind a snippet in my editor just to fire an event, a basic task that 50%+ components need to do. It's a small thing, but most devs I've talked to about this also feel it's pretty out of place within the rest of Svelte.

Just reading through the couple of threads on this issue, I'm not really able to agree with the arguments presented against this thus far.

I don't really buy the magical variable being counter to Svelte's approach, as mentioned above svelte already has a global variable namespace with both $$props and $$restProps. Obviously tools would require updating, but svelte already has quite good centralized linting and language tools that could be updated quickly. For the rest, linting isn't really a great argument against making a non-breaking change.

Furthermore, in terms of passing around, if I'm not mistaken you can pass around a svelte event dispatcher, not that I'd recommend it, but technically if you wanted a child component to dispatch events from a parent it would work.

Source:

export function createEventDispatcher() {
    const component = get_current_component();

    return (type: string, detail?: any) => {
        const callbacks = component.$$.callbacks[type];

        if (callbacks) {
            // TODO are there situations where events could be dispatched
            // in a server (non-DOM) environment?
            const event = custom_event(type, detail);
            callbacks.slice().forEach(fn => {
                fn.call(component, event);
            });
        }
    };
}

Because the current component is called outside the closure, fire is always bound to the component that created the event dispatcher. The same could apply for a global $$dispatch.

In terms of performance (the cost of creating an event dispatcher is obviously near zero from the source above, but perhaps it could add up), another point is that dispatch could default to a single global stub instead of dispatch logic if no event callbacks are passed from the calling component. That way, the variable would always be created, but for majority of components that don't dispatch events there would be no memory impact at all.

just a quick one here:

  1. A goal of Svelte is to require zero tooling, this means zero magic variables. This doesn't preclude being able to simply import { dispatch } from 'svelte'.
  2. If somebody wants to open a PR for this I will happily test and review, because thanks to all the feedback on this issue, I am also changing my opinion on whether we need createEventDispatcher. I don't speak for everyone, but I think it's worth coding up and seeing the difference.

Magic globals like $$dispatch would only be able to be used inside component definitions. So, as with letting onMount/etc stuff being wrapped up into a single function that can be used in multiple components, I don't think we'd be removing or deprecating createEventDispatcher anyway.

import { dispatch } from 'svelte' is not an option, because that involves a magic _import_, which is way more magical than a magic global. We need something like createEventDispatcher (or an implicit call to it from a magic global) so that we can capture the value of the internal current_component variable during component initialization.

Interesting. I'm curious to learn more about the Zero Tooling goal of svelte. Is the idea that svelte should evolve towards a simple js library?

It seems like svelte needs some way of talking about the current component or "module" that doesn't rely on magic vars and sync current component logic.

The "right" way of doing that seems almost like import.meta? import.meta.fire(event, detail) isn't terribly pretty, but it's a bit nicer:

<script>
const { fire } = import.meta
</script>

<div on:click={() => fire('event', 'test')}>
</div>

Given the import.meta.document proposal (https://github.com/w3c/webcomponents/blob/gh-pages/proposals/html-modules-explainer.md), this seems somewhat reasonable to me. However, I don't know how realistic it is to modify import.meta in the current state of the world or if this is an abuse of import.meta.

It seems it would spite @antony, so I turned this feature into a preprocessor: https://github.com/rixo/svelte-preprocess-autoimport.

This way, everyone can try it and choose for themselves.

I too have always been bothered by the boilerplaty syntax of createEventDispatcher and, increasingly, also by the micro management of lifecycle functions imports like onMount and onDestroy, that often come and go as a component evolves...

On the other hand, I've lived through the "everything global" and "libraries attach to default prototypes" era of JS, and so I'm pretty wary of magic imports and magic in general. And I guess that the lack of imports in the source code will surely trip up tooling like Typescript.

Yet, as long as it is limited to the framework's own helpers, I think the added magic vs reduced boilerplate & micro management trade off might be worth it. I'm personally going to try this in my code, and see if I encounter any major setbacks that would make me reverse course.

Event forwarding is enhanced by an equivalent shorthand such as on:click. Event dispatching would really benefit from that approach as well. It appears to me that Svelte is trying to get rid of boilerplate wherever possible (shorthand attributes etc), but somehow dispatching events is left outside of that pattern. Vue.js for example follows this simple approach of having events dispatched from tags in <template> or from <script> if more logic is needed. But no other boilerplate is necessary to initiate the dispatcher.

I don't buy "no magic" as a reason because Svelte already uses magic where it's useful.

In Svelte magic generally starts with a "$" so using $dispatch or $$dispatch or $$onXXX isn't more magically than for example the store-access shortcut via $.

Of course it's important to prevent proliferation of "to much magic", but I think event-dispatch is to common to leave it as boilerplaty as it's now.

the whole point of a compiler is to make magic to write something understandable instead of assembly

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bestguy picture bestguy  路  3Comments

thoughtspile picture thoughtspile  路  3Comments

juniorsd picture juniorsd  路  3Comments

plumpNation picture plumpNation  路  3Comments

sskyy picture sskyy  路  3Comments