From 4653f498ef07e8f4b96276ca5a293e46e889465e Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 21:09:43 +0200 Subject: [PATCH 1/7] perf: improve type checking performance of `Route.Link` --- packages/react-router/src/link.tsx | 33 +++++++++++++++++------------ packages/react-router/src/route.tsx | 4 ++-- packages/router-core/src/link.ts | 21 +++++++++--------- packages/router-core/src/route.ts | 7 +++--- packages/router-core/src/router.ts | 12 +++++------ packages/router-core/src/utils.ts | 12 +++++------ 6 files changed, 47 insertions(+), 42 deletions(-) diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx index d0bbbdd8199..3023a10288c 100644 --- a/packages/react-router/src/link.tsx +++ b/packages/react-router/src/link.tsx @@ -465,20 +465,25 @@ export type LinkComponent< props: LinkComponentProps, ) => React.ReactElement -export type LinkComponentRoute = < - TRouter extends AnyRouter = RegisteredRouter, - const TTo extends string | undefined = undefined, - const TMaskTo extends string = '', ->( - props: LinkComponentProps< - 'a', - TRouter, - TDefaultFrom, - TTo, - TDefaultFrom, - TMaskTo - >, -) => React.ReactElement +export interface LinkComponentRoute< + in out TDefaultFrom extends string = string, +> { + defaultFrom: TDefaultFrom + < + TRouter extends AnyRouter = RegisteredRouter, + const TTo extends string | undefined = undefined, + const TMaskTo extends string = '', + >( + props: LinkComponentProps< + 'a', + TRouter, + this['defaultFrom'], + TTo, + this['defaultFrom'], + TMaskTo + >, + ): React.ReactElement +} export function createLink( Comp: Constrain ReactNode>, diff --git a/packages/react-router/src/route.tsx b/packages/react-router/src/route.tsx index 928116dba03..8ec238a5b3c 100644 --- a/packages/react-router/src/route.tsx +++ b/packages/react-router/src/route.tsx @@ -53,8 +53,8 @@ declare module '@tanstack/router-core' { } export interface RouteExtensions< - TId extends string, - TFullPath extends string, + in out TId extends string, + in out TFullPath extends string, > { useMatch: UseMatchRoute useRouteContext: UseRouteContextRoute diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index 5fa5a2a5b3d..48a876feb5d 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -21,11 +21,8 @@ import type { ConstrainLiteral, Expand, MakeDifferenceOptional, - NoInfer, NonNullableUpdater, - PickRequired, Updater, - WithoutEmpty, } from './utils' import type { ParsedLocation } from './location' @@ -469,11 +466,11 @@ type MakeRequiredParamsReducer< > = | (string extends TFrom ? never - : ResolveFromParams extends WithoutEmpty< - PickRequired< - ResolveRelativeToParams - > - > + : ResolveFromParams< + TRouter, + TParamVariant, + TFrom + > extends ResolveRelativeToParams ? true : never) | (ParamsReducer & {}) @@ -505,9 +502,11 @@ export type IsRequired< ? never : TPath extends CatchAllPaths ? never - : IsRequiredParams< - ResolveRelativeToParams - > + : TFrom extends TPath + ? never + : IsRequiredParams< + ResolveRelativeToParams + > : never export type SearchParamOptions = diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index 4be19d5c24b..63443577906 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -442,7 +442,10 @@ export type ResolveFullPath< TPrefixed = RoutePrefix, > = TPrefixed extends RootRouteId ? '/' : TPrefixed -export interface RouteExtensions {} +export interface RouteExtensions { + id: TId + fullPath: TFullPath +} export type RouteLazyFn = ( lazyFn: () => Promise, @@ -564,9 +567,7 @@ export interface Route< in out TChildren, in out TFileRouteTypes, > extends RouteExtensions { - fullPath: TFullPath path: TPath - id: TId parentRoute: TParentRoute children?: TChildren types: RouteTypes< diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index f41b47fe2f9..3a510d65068 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -94,15 +94,15 @@ export type ControllablePromise = Promise & { export type InjectedHtmlEntry = Promise -export interface Register { - // router: Router +export interface DefaultRegister { + router: AnyRouter } -export type RegisteredRouter = Register extends { - router: infer TRouter extends AnyRouter +export interface Register extends DefaultRegister { + // router: Router } - ? TRouter - : AnyRouter + +export type RegisteredRouter = Register['router'] export type DefaultRemountDepsFn = ( opts: MakeRemountDepsOptionsUnion, diff --git a/packages/router-core/src/utils.ts b/packages/router-core/src/utils.ts index 1418c2997c2..1df674e8b9c 100644 --- a/packages/router-core/src/utils.ts +++ b/packages/router-core/src/utils.ts @@ -37,12 +37,12 @@ export type DeepPartial = T extends object } : T -export type MakeDifferenceOptional = Omit< - TRight, - keyof TLeft -> & { - [K in keyof TLeft & keyof TRight]?: TRight[K] -} +export type MakeDifferenceOptional = keyof TLeft & + keyof TRight extends never + ? TRight + : Omit & { + [K in keyof TLeft & keyof TRight]?: TRight[K] + } // from https://stackoverflow.com/a/53955431 // eslint-disable-next-line @typescript-eslint/naming-convention From f48a4ab8205246946a54a12503127deeed041417 Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 21:35:31 +0200 Subject: [PATCH 2/7] chore: fix some tests --- packages/react-router/tests/route.test-d.tsx | 4 ++-- packages/router-core/src/link.ts | 1 + packages/router-core/src/redirect.ts | 11 +++++------ packages/solid-router/tests/route.test-d.tsx | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-router/tests/route.test-d.tsx b/packages/react-router/tests/route.test-d.tsx index 2183cf4af71..7fd9a5fde0c 100644 --- a/packages/react-router/tests/route.test-d.tsx +++ b/packages/react-router/tests/route.test-d.tsx @@ -1700,7 +1700,7 @@ test('when creating a child route with no explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ page: number }>() + .toEqualTypeOf<{ page: number } | undefined>() expectTypeOf(navigate) .parameter(0) @@ -1763,7 +1763,7 @@ test('when creating a child route with an explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ input: string }>() + .toEqualTypeOf<{ input: string } | undefined>() expectTypeOf(navigate) .parameter(0) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index 48a876feb5d..b48e93afbc4 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -21,6 +21,7 @@ import type { ConstrainLiteral, Expand, MakeDifferenceOptional, + NoInfer, NonNullableUpdater, Updater, } from './utils' diff --git a/packages/router-core/src/redirect.ts b/packages/router-core/src/redirect.ts index e54e3f054b1..24de9024b2b 100644 --- a/packages/router-core/src/redirect.ts +++ b/packages/router-core/src/redirect.ts @@ -1,5 +1,4 @@ import type { NavigateOptions } from './link' -import type { RoutePaths } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' import type { PickAsRequired } from './utils' @@ -10,9 +9,9 @@ export type AnyRedirect = Redirect */ export type Redirect< TRouter extends AnyRouter = RegisteredRouter, - TFrom extends RoutePaths | string = '/', - TTo extends string | undefined = '.', - TMaskFrom extends RoutePaths | string = TFrom, + TFrom extends string = string, + TTo extends string | undefined = undefined, + TMaskFrom extends string = TFrom, TMaskTo extends string = '.', > = { href?: string @@ -39,9 +38,9 @@ export type Redirect< export type ResolvedRedirect< TRouter extends AnyRouter = RegisteredRouter, - TFrom extends RoutePaths = '/', + TFrom extends string = string, TTo extends string = '', - TMaskFrom extends RoutePaths = TFrom, + TMaskFrom extends string = TFrom, TMaskTo extends string = '', > = PickAsRequired< Redirect, diff --git a/packages/solid-router/tests/route.test-d.tsx b/packages/solid-router/tests/route.test-d.tsx index fc58b779ab6..d70e384af37 100644 --- a/packages/solid-router/tests/route.test-d.tsx +++ b/packages/solid-router/tests/route.test-d.tsx @@ -1683,7 +1683,7 @@ test('when creating a child route with no explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ page: number }>() + .toEqualTypeOf<{ page: number } | undefined>() expectTypeOf(navigate) .parameter(0) @@ -1752,7 +1752,7 @@ test('when creating a child route with an explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ input: string }>() + .toEqualTypeOf<{ input: string } | undefined>() expectTypeOf(navigate) .parameter(0) From 04d9f40d39c3d418d2cb5acb6f4bcbfd545cb3fa Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 21:57:57 +0200 Subject: [PATCH 3/7] chore: fix example --- packages/router-core/src/link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index b48e93afbc4..c690eda4bc1 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -503,7 +503,7 @@ export type IsRequired< ? never : TPath extends CatchAllPaths ? never - : TFrom extends TPath + : ResolveRelativePath extends TFrom ? never : IsRequiredParams< ResolveRelativeToParams From d7d1337aac047918c86ba5409c5ed6236f0fe34c Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 22:21:23 +0200 Subject: [PATCH 4/7] chore: fix example --- packages/router-core/src/link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index c690eda4bc1..d7e573eba2a 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -503,7 +503,7 @@ export type IsRequired< ? never : TPath extends CatchAllPaths ? never - : ResolveRelativePath extends TFrom + : [TFrom] extends [TPath] ? never : IsRequiredParams< ResolveRelativeToParams From dbdd4822d62cc33a6d1b80eb94c9f32b9ae05cdc Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 22:35:05 +0200 Subject: [PATCH 5/7] chore: change solid --- packages/solid-router/src/link.tsx | 33 +++++++++++++++++------------ packages/solid-router/src/route.tsx | 4 ++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/solid-router/src/link.tsx b/packages/solid-router/src/link.tsx index c4f406d915a..ca43fd2fa35 100644 --- a/packages/solid-router/src/link.tsx +++ b/packages/solid-router/src/link.tsx @@ -522,20 +522,25 @@ export type LinkComponent< props: LinkComponentProps, ) => Solid.JSX.Element -export type LinkComponentRoute = < - TRouter extends AnyRouter = RegisteredRouter, - const TTo extends string | undefined = undefined, - const TMaskTo extends string = '', ->( - props: LinkComponentProps< - 'a', - TRouter, - TDefaultFrom, - TTo, - TDefaultFrom, - TMaskTo - >, -) => Solid.JSX.Element +export interface LinkComponentRoute< + in out TDefaultFrom extends string = string, +> { + defaultFrom: TDefaultFrom + < + TRouter extends AnyRouter = RegisteredRouter, + const TTo extends string | undefined = undefined, + const TMaskTo extends string = '', + >( + props: LinkComponentProps< + 'a', + TRouter, + this['defaultFrom'], + TTo, + this['defaultFrom'], + TMaskTo + >, + ): Solid.JSX.Element +} export function createLink( Comp: Constrain Solid.JSX.Element>, diff --git a/packages/solid-router/src/route.tsx b/packages/solid-router/src/route.tsx index c14f0615872..afc96ab5fda 100644 --- a/packages/solid-router/src/route.tsx +++ b/packages/solid-router/src/route.tsx @@ -53,8 +53,8 @@ declare module '@tanstack/router-core' { } export interface RouteExtensions< - TId extends string, - TFullPath extends string, + in out TId extends string, + in out TFullPath extends string, > { useMatch: UseMatchRoute useRouteContext: UseRouteContextRoute From 419ce14d98554f495b1548db2f95a7fce7e86336 Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 22:41:03 +0200 Subject: [PATCH 6/7] chore: fix build --- packages/solid-router/src/route.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/solid-router/src/route.tsx b/packages/solid-router/src/route.tsx index afc96ab5fda..a1c2762f690 100644 --- a/packages/solid-router/src/route.tsx +++ b/packages/solid-router/src/route.tsx @@ -132,13 +132,13 @@ export class RouteApi< return notFound({ routeId: this.id as string, ...opts }) } - Link: LinkComponentRoute['fullPath']> = ( + Link: LinkComponentRoute['fullPath']> = (( props, ) => { const router = useRouter() const fullPath = router.routesById[this.id as string].fullPath return - } + }) as LinkComponentRoute['fullPath']> } export class Route< @@ -242,9 +242,9 @@ export class Route< return useNavigate({ from: this.fullPath }) } - Link: LinkComponentRoute = (props) => { + Link: LinkComponentRoute = ((props) => { return - } + }) as LinkComponentRoute } export function createRoute< @@ -427,9 +427,9 @@ export class RootRoute< return useNavigate({ from: this.fullPath }) } - Link: LinkComponentRoute<'/'> = (props) => { + Link: LinkComponentRoute<'/'> = ((props) => { return - } + }) as LinkComponentRoute<'/'> } export function createRouteMask< From 168d088818a4add39c1fba74b7ebee6506b405b0 Mon Sep 17 00:00:00 2001 From: chorobin Date: Thu, 8 May 2025 22:53:49 +0200 Subject: [PATCH 7/7] chore: revert some parts --- packages/react-router/tests/route.test-d.tsx | 4 ++-- packages/router-core/src/link.ts | 8 +++----- packages/solid-router/tests/route.test-d.tsx | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/react-router/tests/route.test-d.tsx b/packages/react-router/tests/route.test-d.tsx index 7fd9a5fde0c..2183cf4af71 100644 --- a/packages/react-router/tests/route.test-d.tsx +++ b/packages/react-router/tests/route.test-d.tsx @@ -1700,7 +1700,7 @@ test('when creating a child route with no explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ page: number } | undefined>() + .toEqualTypeOf<{ page: number }>() expectTypeOf(navigate) .parameter(0) @@ -1763,7 +1763,7 @@ test('when creating a child route with an explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ input: string } | undefined>() + .toEqualTypeOf<{ input: string }>() expectTypeOf(navigate) .parameter(0) diff --git a/packages/router-core/src/link.ts b/packages/router-core/src/link.ts index d7e573eba2a..26d038e769f 100644 --- a/packages/router-core/src/link.ts +++ b/packages/router-core/src/link.ts @@ -503,11 +503,9 @@ export type IsRequired< ? never : TPath extends CatchAllPaths ? never - : [TFrom] extends [TPath] - ? never - : IsRequiredParams< - ResolveRelativeToParams - > + : IsRequiredParams< + ResolveRelativeToParams + > : never export type SearchParamOptions = diff --git a/packages/solid-router/tests/route.test-d.tsx b/packages/solid-router/tests/route.test-d.tsx index d70e384af37..fc58b779ab6 100644 --- a/packages/solid-router/tests/route.test-d.tsx +++ b/packages/solid-router/tests/route.test-d.tsx @@ -1683,7 +1683,7 @@ test('when creating a child route with no explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ page: number } | undefined>() + .toEqualTypeOf<{ page: number }>() expectTypeOf(navigate) .parameter(0) @@ -1752,7 +1752,7 @@ test('when creating a child route with an explicit search input', () => { .parameter(0) .toHaveProperty('search') .exclude() - .toEqualTypeOf<{ input: string } | undefined>() + .toEqualTypeOf<{ input: string }>() expectTypeOf(navigate) .parameter(0)