sveltejs / svelte

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

enhancement(custom element): svelte 5 should automatically set converters based on types #12114

Open Theo-Steiner opened 4 months ago

Theo-Steiner commented 4 months ago

Describe the problem

Motivation

We maintain custom element libraries at work, some using vue.js, some using svelte.js. To be honest, this is not really a pleasant experience in either of framework. While vue.js has tremendous issues with correctly outputting css for nested components, svelte technically works as expected, but the process to get there is very clunky and un-svelte-like.

Clunkiness in Svelte

To me, the biggest issue with building custom elements in svelte 4 is the <svelte:options/> api. Since customElement must be a statically analyzable object literal, it cannot be dynamically generated using javascript. This means that if you have to set a very large amount of properties, the config alone takes away a lot of valuable space.

<svelte:options customElement={{
  tag: "my-foo",
  props: {
    // if not manually converted the attribute is: `mymultiwordprop`, which is bad dx at best and a trap at worst
    myMultiwordProp: {
      attribute: "my-multiword-prop"
    },
    obj: {
      type: "Object"
    },
   num: {
     type: "Number"
   }
  }
}} />

<script lang="ts">
  export let myMultiwordProp: string;
  export let obj: {a: string};
  export let num: number;
</script>

for decent sized components, this can become so bad, that I ended up writing a vite plugin that injects the <svelte:options customElement/> config, just so that the amount of code inside the SFC stayed manageable.

Custom Elements in Svelte 5

svelte 5 already relieves part of this clunkiness, since props can now be declared as "attribute" kebab-case from the start:

<script lang="ts">
let {'my-multiword-prop': myMultiwordProp} = $props<{
  ['my-multiword-prop']: string;
}>();
</script>
<p>{myMultiwordProp}</p>

However, most web components take props other than just strings, which in the case of a complex component still requires a lot of clunky configuration.

Describe the proposed solution

The Vue Approach

vue 3 leverages the information it gets from its defineProps() macro to automatically convert attributes from strings into the correct type. (vue does this only for basic types: String, Number, Boolean)

I've created two stackblitz reproductions, so you can compare what vue does differently here than svelte:

Proposal for Svelte 5: Implicit Conversion Leveraging Types

While svelte does not collect "type" information for non-typescript props like vue does, it should still be possible to make the process easier for typescript users (and with more and more users choosing typescript, that would be most users).

I know next to nothing about compilers, but I thought since svelte 5 now uses acorn-typescript for parsing the necessary type information might already be there, ready to be statically analized (assuming the script content is parsed)

While implicit type conversion would be a great first step, this idea could even be expanded to

  1. use the $bindable() rune to show that an attribute should be reflected back to the host element
  2. use an attribute on the style tag to signify that styles should be encapsuled into the shadow dom

I don't know if implementing this proposal is feasible, but it would remove a lot of the "boilerplate" associated with the <svelte:options customElement={{...}}/> clutter configuration of today.

Importance

would make my life easier

Theo-Steiner commented 2 months ago

started to look a bit into how vue is doing this... and as expected this is quite the beast: https://github.com/vuejs/core/blob/main/packages/compiler-sfc/src/script/resolveType.ts