sveltesvelte-5

Svelte 5 dynamic component loading: TypeScript errors


In my Svelte 4 app I dynamically load components, all of which have different props. Very simple reproduction (also available on the Svelte Playground):

// App.svelte
<script lang="ts">
  import One from "./One.svelte";
  import Two from "./Two.svelte";

  import type { ComponentType } from "svelte";

  type Item = {
    component: ComponentType;
  };

  const items: Item[] = [
    { component: One, one: "World" },
    { component: Two, two: "Svelte" },
  ];
</script>

<div class="container py-20 text-white">
  {#each items as item}
    <div>
      <svelte:component this={item.component} {...item} />
    </div>
  {/each}
</div>
// One.svelte
<script lang="ts">
    export let one;
</script>

Hello {one}
// Two.svelte
<script lang="ts">
    export let two;
</script>

Goodbye {two}

This works fine without any warnings or errors, no complaints so far. However I want to migrate to Svelte 5 and now I am getting a bunch of TypeScript errors. For example ComponentType is deprecated, and things like this:

Type '__sveltets_2_IsomorphicComponent<{ one: any; }, { [evt: string]: CustomEvent<any>; }, {}, {}, string>' is not assignable to type 'ComponentType'.
  Type '__sveltets_2_IsomorphicComponent<{ one: any; }, { [evt: string]: CustomEvent<any>; }, {}, {}, string>' is not assignable to type 'new (options: ComponentConstructorOptions<Record<string, any>>) => SvelteComponent<Record<string, any>, any, any>'.
    Types of parameters 'options' and 'options' are incompatible.
      Type 'ComponentConstructorOptions<Record<string, any>>' is not assignable to type 'ComponentConstructorOptions<{ one: any; }>'.
        Property 'one' is missing in type 'Record<string, any>' but required in type '{ one: any; }'.

How can I refactor my code so that it keeps on working, without TypeScript errors, and without having to strongly type each component? In my real-world app there are many components involved, each with a whole bunch of different props. I don't want to have to create type aliases for each of them.

Simply changing ComponentType to Component does get rid of the deprecation warning, but the other warning is still basically the same:

Type '__sveltets_2_IsomorphicComponent<{ one: any; }, { [evt: string]: CustomEvent<any>; }, {}, {}, string>' is not assignable to type 'Component<{}, {}, string>'.
  Types of parameters 'props' and 'props' are incompatible.
    Type '{}' is not assignable to type '{ one: any; } & { $$events?: { [evt: string]: CustomEvent<any>; } | undefined; $$slots?: {} | undefined; }'.
      Property 'one' is missing in type '{}' but required in type '{ one: any; }'.ts(2322)

Changing ComponentType to SvelteComponent doesn't make things better either. When I use the unknown type then I get this warning:

Argument of type 'unknown' is not assignable to parameter of type 'ConstructorOfATypedSvelteComponent | Component<any, any, any> | null | undefined'.

At least that's only one warning instead of a whole bunch in the array so it's better, but can this really not be solved in a better way (without typing every component)?


Solution

  • unknown means you have to assert a type first to use it in a meaningful way, if you want to be less strict, you would want any instead:

    type Item = {
      component: Component<any>;
      [key: string]: any; // for props
    };
    
    const items: Item[] = [
      { component: One, one: "World" },
      { component: Two, two: "Svelte" },
    ];
    

    (There should be ways to also align the props with the given component type using generics, but that is a bit more complicated.)

    In Svelte 5 you also should not use <svelte:component>, you can use the component directly:

    <item.component {...item} />
    

    (Lowercase name is only allowed via the . notation.)