sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
80k stars 4.25k forks source link

spreading with `this` attribute: `<svelte:element>` must have a 'this' attribute with a value #12662

Open kutoman opened 3 months ago

kutoman commented 3 months ago

Describe the bug

When we set the this attribute of svelte:element explicitly via an object containing the this attribute, everything is as expected. But when we spread that object in the svelte:element component the compiler does not consider that this attribute.

Reproduction

https://stackblitz.com/edit/vitejs-vite-w8nmej?file=src%2FApp.svelte

Logs

No response

System Info

skipping this as it is happening everywhere, like in stackblitz

Severity

annoyance

dummdidumm commented 3 months ago

This works as expected - the this attribute needs to be set statically (the compiler error is correct).

Question is whether or not it is possible to relax this constraint. Why is this important to you? I imagine this being rather easy to work around.

kutoman commented 3 months ago

Why is this important to you?

I'm working on a components library where I noticed a specific pattern regarding the case when I have to use svelte:element. So I decided to share the logic between those components where I use svelte:element with a custom rune.

/**
 * Takes care of setting up svelte:element based on props for [CanBeLabel] properly
 * @param props 
 * @param defaultTag tag to be used in default cases
 * @returns props to be spread on svelte:element
 */
export function createCanBeLabel<T extends string>(labelId: string|undefined, defaultTag: T): { ['this']: T|'label', for: U<string> } {
    const tag = $derived(labelId? 'label' : defaultTag);
    return { 
        get ['this']() { return tag }, 
        get for() { return labelId }
    };
}

The rune decides when to use a label tag and sets the necssary additional attributes (e.g. for) accordingly. Since I am used to use the spread operator in Svelte components, which is really great, I just thought it makes sense to use it in this case also. Therefore I intuitively tried:

<script lang="ts">
...
</script>
<svelte:element {...createCanBeLabel(labelId, 'div')}>
    {@render props.children()}
</svelte:element>

but I have to do instead:

<script lang="ts">
...
    const _ = createCanBeLabel(labelId, 'div');

</script>
<svelte:element this={_.this} for={_.for}>
    {@render props.children()}
</svelte:element>

Yes this seems like a straightforward workaround but it is a bit more error prone since the usage of the shared behavior (rune) only makes sense when all attributes are passed to svelte:element without missing any. Please note: it's not just for a single component.