ItalyPaleAle / svelte-spa-router

Router for SPAs using Svelte 3
MIT License
1.53k stars 105 forks source link

Typescript problematic types after "wrapping" the route #314

Closed lukyncze closed 8 months ago

lukyncze commented 8 months ago

VSCode is complaining when I try to use a wrap function for a route and then specify component/asyncComponent inside wrap - it does not complain when I use it basically like this:

/* correct imports... */

const routes = {
  '/': Home,
  '/counter': Counter
};

1] scenario:

import {wrap} from 'svelte-spa-router/wrap';
import Home from './lib/pages/Home.svelte';
import Counter from './lib/pages/Counter.svelte';

const routes = {
  '/': Home,
  '/counter': wrap({
    component: Counter,
  }),
};
Type 'typeof Counter__SvelteComponent_' is not assignable to type 'typeof SvelteComponent'.
  Types of construct signatures are incompatible.
    Type 'new (options: ComponentConstructorOptions<Record<string, never>>) => Counter__SvelteComponent_' is not assignable to type 'new <Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any>(options: ComponentConstructorOptions<Props>) => SvelteComponent<...>'.
      Types of parameters 'options' and 'options' are incompatible.
        Type 'ComponentConstructorOptions<Props>' is not assignable to type 'ComponentConstructorOptions<Record<string, never>>'.
          Type 'Props' is not assignable to type 'Record<string, never>'.
            Type 'Record<string, any>' is not assignable to type 'Record<string, never>'.
              'string' index signatures are incompatible.
                Type 'any' is not assignable to type 'never'.ts(2322)

2] scenario:

import {wrap} from 'svelte-spa-router/wrap';
import Home from './lib/pages/Home.svelte';

const routes = {
  '/': Landing,
  '/counter': wrap({
    asyncComponent: import('./lib/pages/Counter.svelte'),
  }),
};
Type 'Promise<typeof import("/svelte/src/lib/pages/Counter.svelte")>' is not assignable to type 'AsyncSvelteComponent'.
  Type 'Promise<typeof import("/svelte/src/lib/pages/Counter.svelte")>' provides no match for the signature '(): Promise<{ default: typeof SvelteComponent; }>'.ts(2322)

(I edited paths for 2nd scenario, so normally it would start with /home/something/...)

My dev dependencies:

"devDependencies": {
  "@sveltejs/vite-plugin-svelte": "^2.4.2",
  "@tsconfig/svelte": "^5.0.2",
  "autoprefixer": "^10.4.14",
  "postcss": "^8.4.24",
  "postcss-load-config": "^4.0.1",
  "svelte": "^4.2.3",
  "svelte-check": "^3.6.0",
  "svelte-spa-router": "^4.0.0",
  "tailwindcss": "^3.3.2",
  "tslib": "^2.6.2",
  "typescript": "^5.2.2",
  "vite": "^5.0.0"
}

I tried searching for this issue but I haven't find any. Also I tried some hacky solutions via typescript "as" keyword, but with no success.

laat commented 8 months ago

Not sure if this is correct, but I've had success with this patch on this package.

diff --git a/Router.d.ts b/Router.d.ts
index e64617ba7997d48654331f3f44791e1d8230e8d0..773c82317d253c103896b83c06446c9c855fc5ce 100644
--- a/Router.d.ts
+++ b/Router.d.ts
@@ -1,6 +1,6 @@
 ///<reference types="svelte" />

-import {SvelteComponent} from 'svelte'
+import {SvelteComponent, ComponentType} from 'svelte'
 import {Readable} from 'svelte/store'

 /** Dictionary with route details passed to the pre-conditions functions, as well as the `routeLoading` and `conditionsFailed` events */
@@ -24,7 +24,7 @@ export interface RouteDetail {
 /** Detail object for the `routeLoaded` event */
 export interface RouteDetailLoaded extends RouteDetail {
      /** Svelte component */
-     component: typeof SvelteComponent
+     component: ComponentType

      /** Name of the Svelte component that was loaded (note: might be minified in production) */
      name: string
@@ -34,7 +34,7 @@ export interface RouteDetailLoaded extends RouteDetail {
  * This is a Svelte component loaded asynchronously.
  * It's meant to be used with the `import()` function, such as `() => import('Foo.svelte')}`
  */
-export type AsyncSvelteComponent = () => Promise<{default: typeof SvelteComponent}>
+export type AsyncSvelteComponent = () => Promise<{default: ComponentType}>

 /**
  * Route pre-condition function. This is a callback that receives a RouteDetail object as argument containing information on the route that we're trying to load.
@@ -50,7 +50,7 @@ export type RoutePrecondition = (detail: RouteDetail) => (boolean | Promise<bool
 /** Object returned by the `wrap` method */
 export interface WrappedComponent {
     /** Component to load (this is always asynchronous) */
-    component: typeof SvelteComponent
+    component: ComponentType

     /** Route pre-conditions to validate */
     conditions?: RoutePrecondition[]
@@ -152,8 +152,8 @@ export const params: Readable<Record<string, string> | undefined>
 // Note: the above is implemented as writable but exported as readable because consumers should not modify the value

 /** List of routes */
-export type RouteDefinition = Record<string, typeof SvelteComponent | WrappedComponent> |
-    Map<string | RegExp, typeof SvelteComponent | WrappedComponent>
+export type RouteDefinition = Record<string, ComponentType | WrappedComponent> |
+    Map<string | RegExp, ComponentType | WrappedComponent>

 /** Generic interface for events from the router */
 interface RouterEvent<T> {
lukyncze commented 8 months ago

@laat Nice, I haven't tried it yet, but I think it looks great! Can you maybe create a PR with these changes please? Thx :)