withastro/language-tools

๐Ÿ› BUG: Props are not inferred with a Svelte generic component

Opened this issue ยท 4 comments

Describe the Bug

Before all, once again, I'm note sure if it is a compiler or a language-tools issue. I guess it is not the compiler since the app can be build without errors. Sorry if I picked up the wrong repository.

Context

Svelte supports the creation of generic components using Typescript. The syntax that was retained looks like:

<script lang="ts" generics="T extends boolean, X">
    import {createEventDispatcher} from "svelte";

    export let array1: T[];
    export let item1: T;
    export let array2: X[];

    const dispatch = createEventDispatcher<{arrayItemClick: X}>();
</script>

However, it seems this API is still experimental and not yet documented on the Svelte website.

The issue

When importing a generic component in a Svelte file, everything works as expected: the props are correctly inferred and we can have auto-completion in VS Code.

When importing the same generic component in an Astro file, the app can be build (no errors, and the component is visible in the browser). However, the Props are not inferred and Typescript complains.

Example

With this component:

<script lang="ts" generics="T extends boolean = false">
  import type { SvelteHTMLElements } from "svelte/elements";

  type BaseProps = T extends true
    ? SvelteHTMLElements["ol"]
    : SvelteHTMLElements["ul"];

  type $$Props = BaseProps & {
    isOrdered?: T;
  };

  export let isOrdered = false;

  let tag: Extract<keyof HTMLElementTagNameMap, "ol" | "ul">;
  $: tag = isOrdered ? "ol" : "ul";
</script>

<svelte:element this={tag} {...$$restProps}>
  <slot />
</svelte:element>

If I try to import it in a file with the .astro extension, I can't benefit from the intellisense.

import of a Svelte generic component in Astro

And Typescript gives me the following error:

Type '{}' is not assignable to type 'IntrinsicAttributes & ComponentConstructorOptions<$$Props>'.
  Property 'target' is missing in type '{}' but required in type 'ComponentConstructorOptions<$$Props>'.ts(2322)

My expectations

No errors from Typescript and the ability to use the intellisense to fill the props, like any component.

System info

> astro info

Astro                    v4.3.2
Node                     v18.17.1
System                   Linux (x64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             @astrojs/svelte
which: no xclip in .......

Steps to Reproduce

I created a Stackblitz that can be downloaded and opened in VS Code (since the parser won't work otherwise). It contains three files in src/components:

  • a generic Svelte component (List.svelte)
  • a Svelte file to test the import (SvelteTest.svelte)
  • an Astro file to test the import (AstroTest.svelte)

Here the steps to manually reproduce the error:

  1. In VS Code, create a new Astro project with npm create astro@latest -- --template framework-svelte (and install the Astro and Svelte extensions if needed)
  2. Answer Yes for everything (and keep Strict for Typescript)
  3. Create a file src/components/List.svelte that contains our generic component:
<script lang="ts" generics="T extends boolean = false">
  import type { SvelteHTMLElements } from "svelte/elements";

  type BaseProps = T extends true
    ? SvelteHTMLElements["ol"]
    : SvelteHTMLElements["ul"];

  type $$Props = BaseProps & {
    isOrdered?: T;
  };

  export let isOrdered = false;

  let tag: Extract<keyof HTMLElementTagNameMap, "ol" | "ul">;
  $: tag = isOrdered ? "ol" : "ul";
</script>

<svelte:element this={tag} {...$$restProps}>
  <slot />
</svelte:element>
  1. Import this component in another Svelte file src/components/SvelteTest.svelte:
<script>
  import List from "./List.svelte";
</script>

<List />
  1. Import the component in an Astro file src/components/AstroTest.astro:
---
import ListSvelte from "./List.svelte";
---

<ListSvelte />
  1. Now if we look our two tests files:
  • the Svelte one correctly infer the Props: class List<T extends boolean = false>
  • the Astro one does not infer the Props and Typescript is complaining with:
Type '{}' is not assignable to type 'IntrinsicAttributes & ComponentConstructorOptions<$$Props>'.
  Property 'target' is missing in type '{}' but required in type 'ComponentConstructorOptions<$$Props>'.ts(2322)

Might just be a question of updating the svelte2tsx dependency of @astrojs/svelte, if this is a new feature.

Thank you for submitting an issue!

I just looked in the svelte2tsx releases. It seems it was added in svelte2tsx-0.6.15 (which was released in May 26, 2023). The @astrojs/svelte package already uses "svelte2tsx": "^0.6.25". I checked the example I set up yesterday and the installed version is svelte2tsx@0.6.27. So the problem must lie elsewhere...

I dig up a little to understand how the integration is done. I have not tested but I think the issue comes from these lines in @astrojs/svelte.
When a component does not use the generics attribute, it seems the output matches:

export default class extends __sveltets_2_createSvelte2TsxComponent(

We can see that this is the case in the following Svelte tests: component-default-slot and component-with-documentation.

However, when the generics attribute is used, the output seems different according to these other tests generic-attribute-const-modifier and ts-script-tag-generics (or ts-$$generics for the discarded syntax):

export default class Input__SvelteComponent_<const T extends readonly string[]> extends __SvelteComponentTyped__<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> {

So I guess we are loosing the correct types because of the tsx.replace which is no longer accurate.

If I'm correct it seems I pick up the wrong repository again. The issue belongs to the astro repository.

Thank you for investigating! That replace is definitely brittle, I kinda wish we could remove it. Maybe in the future.

Despite this issue being in code that's in the main repo, we typically keep issues about this here, so no worries!