sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.54k stars 4.12k forks source link

Backward compatibility, even after migration #13434

Open adiguba opened 2 hours ago

adiguba commented 2 hours ago

Describe the problem

Svelte 5 remains compatible with Svelte 4 components by allowing the use of old syntaxes (slots, on:event directive). That's fine because it's will not break existing code, and this does not force a full migration which could be complex on large projects.

But there a catch : as soon as a Svelte 4 component is migrated to a Svelte 5 syntax, all files that use it must also be migrated to the new syntax :

This is a breaking change that will force users of the component to migrate their own code, and which could push developers not to migrate their libraries...

I think it should be possible to migrate a Svelte 4 component to Svelte 5 syntax, while maintaining backward compatibility with existing code.

Slots

The default slot is managed properly, but they are some use-cases that can be problematic and break compatibility :

Events

For events, I see 2 use-cases that might cause problems :

Describe the proposed solution

It would be great to be able to write these components with Svelte 5 syntax (snippets and onevent handlers), while maintaining compatibility with existing code that might use the new syntax.

I think the solution could be through metadata, which would indicate how to make the transition between the old syntax and the new one. Maybe by using a $legacy runes?

This rune will contains two information for each slot

Exemple :

$legacy({
    slots: {
        // Support for `<slot name="header" />`
        // Replaced by `{@render header?.()}`
        header: true,

        // Support for `<slot prop1={value1} prop2={value2}/>`
        // Replaced by `{@render children?.(value1, value2)}`
        // The name of the properties passed to the slot
        default: ['prop1', 'prop2'],

        // Support for `<slot name="info"/>` with a new name
        // Replaced by `{@render infoTemplate?.()}`
        // The slot "info" will be mapped into the new prop snippet "infoTemplate"
        info: "infoTemplate",

        // Support for `<slot name="footer" {copyright} {year}/>` with a new name
        // Replaced by `{@render footerTemplate?.(copyright, year)}`
        // The slot "footer" will be mapped into the prop snippet "footerTemplate"
        footer: {
            // Name of the snippet :
            prop: "footerTemplate",
            The name of the properties passed to the slot
            args: ['copyright', 'year']
        }
    },

    events: {
        // on:click while be mapped into the `onclick` prop
        'click': true,
        // on:customevent will be mapped into the `onfire` prop
        'customevent': 'onfire'
    }
});

How would this work?

When the component is initialized, the props will be filled with the data from $$props and $$events according to the rules defined in these metadata. So the component could use Svelte 5 syntax internally, however it is used.

In the same way, createEventDispatcher() should use this metadata in order to use props instead of $$events. So something like this :

    const dispatch = createEventDispatcher();

    dispatch('notify', 'detail value');

should be equivalent to :

    let { onnotify } = $props();

    onnotify?.('detail value');

Importance

nice to have

adiguba commented 2 hours ago

I make a small and quick PoC here for slots : https://adiguba-svelte-5-test-git-legacy-runes-adigubas-projects.vercel.app/ Source here : https://github.com/adiguba/svelte/tree/legacy-runes

There are some works to do (more checks, adding some warnings/errors, events, SRR...), but it works for slots on client side.

Example : REPL with 3 components :

Note that there is no specific API in Svelte5Legacy. It's just the same Svelte5Component with a $legacy() rune that describe the slots he accept.

The day slots are no longer supported, the $legacy rune could be deprecated and ignored...