Skip to content

Commit f92282b

Browse files
committed
feat: wip typed routes
1 parent e8791aa commit f92282b

25 files changed

+458
-87
lines changed

packages/playground/src/main.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ComponentPublicInstance } from 'vue'
44
import { router, routerHistory } from './router'
55
import { globalState } from './store'
66
import App from './App.vue'
7+
import { useRoute, type ParamValue, type RouteRecordInfo } from 'vue-router'
78

89
declare global {
910
interface Window {
@@ -29,3 +30,34 @@ app.provide('state', globalState)
2930
app.use(router)
3031

3132
window.vm = app.mount('#app')
33+
34+
export interface RouteNamedMap {
35+
home: RouteRecordInfo<'home', '/', Record<never, never>, Record<never, never>>
36+
'/[name]': RouteRecordInfo<
37+
'/[name]',
38+
'/:name',
39+
{ name: ParamValue<true> },
40+
{ name: ParamValue<false> }
41+
>
42+
'/[...path]': RouteRecordInfo<
43+
'/[...path]',
44+
'/:path(.*)',
45+
{ path: ParamValue<true> },
46+
{ path: ParamValue<false> }
47+
>
48+
}
49+
50+
declare module 'vue-router' {
51+
interface TypesConfig {
52+
RouteNamedMap: RouteNamedMap
53+
}
54+
}
55+
56+
const r = useRoute()
57+
58+
if (r.name === '/[name]') {
59+
r.params.name.toUpperCase()
60+
// @ts-expect-error: Not existing route
61+
} else if (r.name === 'nope') {
62+
console.log('nope')
63+
}

packages/router/__tests__/RouterLink.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
*/
44
import { RouterLink } from '../src/RouterLink'
55
import {
6-
START_LOCATION_NORMALIZED,
76
RouteQueryAndHash,
87
MatcherLocationRaw,
98
RouteLocationNormalized,
109
} from '../src/types'
10+
import { START_LOCATION_NORMALIZED } from '../src/location'
1111
import { createMemoryHistory, RouterOptions } from '../src'
1212
import { createMockedRoute } from './mount'
1313
import { defineComponent, PropType } from 'vue'

packages/router/__tests__/RouterView.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
*/
44
import { RouterView } from '../src/RouterView'
55
import { components, RouteLocationNormalizedLoose } from './utils'
6-
import {
7-
START_LOCATION_NORMALIZED,
8-
RouteLocationNormalized,
9-
} from '../src/types'
6+
import { RouteLocationNormalized } from '../src/types'
7+
import { START_LOCATION_NORMALIZED } from '../src/location'
108
import { markRaw } from 'vue'
119
import { createMockedRoute } from './mount'
1210
import { mockWarn } from 'jest-mock-warn'

packages/router/__tests__/errors.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import {
88
ErrorTypes,
99
} from '../src/errors'
1010
import { components, tick } from './utils'
11-
import {
12-
RouteRecordRaw,
13-
NavigationGuard,
11+
import { RouteRecordRaw, NavigationGuard } from '../src/types'
12+
import type {
1413
RouteLocationRaw,
15-
START_LOCATION_NORMALIZED,
1614
RouteLocationNormalized,
17-
} from '../src/types'
15+
} from '../src/typed-routes'
1816
import { mockWarn } from 'jest-mock-warn'
17+
import { START_LOCATION_NORMALIZED } from '../src/location'
1918

2019
const routes: Readonly<RouteRecordRaw>[] = [
2120
{ path: '/', component: components.Home },

packages/router/__tests__/guards/extractComponentsGuards.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { extractComponentsGuards } from '../../src/navigationGuards'
2-
import { START_LOCATION_NORMALIZED, RouteRecordRaw } from '../../src/types'
2+
import { RouteRecordRaw } from '../../src/types'
3+
import { START_LOCATION_NORMALIZED } from '../../src/location'
34
import { components } from '../utils'
45
import { normalizeRouteRecord } from '../../src/matcher'
56
import { RouteRecordNormalized } from 'src/matcher/types'

packages/router/__tests__/guards/guardToPromiseFn.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { guardToPromiseFn } from '../../src/navigationGuards'
2-
import { START_LOCATION_NORMALIZED } from '../../src/types'
2+
import { START_LOCATION_NORMALIZED } from '../../src/location'
33
import { ErrorTypes } from '../../src/errors'
44
import { mockWarn } from 'jest-mock-warn'
55

packages/router/__tests__/matcher/resolve.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createRouterMatcher, normalizeRouteRecord } from '../../src/matcher'
22
import {
3-
START_LOCATION_NORMALIZED,
43
RouteComponent,
54
RouteRecordRaw,
65
MatcherLocationRaw,
@@ -9,6 +8,7 @@ import {
98
import { MatcherLocationNormalizedLoose } from '../utils'
109
import { mockWarn } from 'jest-mock-warn'
1110
import { defineComponent } from '@vue/runtime-core'
11+
import { START_LOCATION_NORMALIZED } from '../../src/location'
1212

1313
const component: RouteComponent = defineComponent({})
1414

@@ -75,7 +75,7 @@ describe('RouterMatcher.resolve', () => {
7575
/**
7676
*
7777
* @param record - Record or records we are testing the matcher against
78-
* @param location - location we want to reolve against
78+
* @param location - location we want to resolve against
7979
* @param [start] Optional currentLocation used when resolving
8080
* @returns error
8181
*/

packages/router/__tests__/router.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ import {
77
} from '../src'
88
import { NavigationFailureType } from '../src/errors'
99
import { createDom, components, tick, nextNavigation } from './utils'
10-
import {
11-
RouteRecordRaw,
12-
RouteLocationRaw,
13-
START_LOCATION_NORMALIZED,
14-
} from '../src/types'
10+
import { RouteRecordRaw, RouteLocationRaw } from '../src/types'
1511
import { mockWarn } from 'jest-mock-warn'
12+
import { START_LOCATION_NORMALIZED } from '../src/location'
1613

1714
declare var __DEV__: boolean
1815

packages/router/src/config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
/**
2-
* Allows customizing existing types of the router that are used globally like `$router`, `<RouterLink>`, and `beforeRouteLeave()`. **ONLY FOR INTERNAL USAGE**.
2+
* Allows customizing existing types of the router that are used globally like `$router`, `<RouterLink>`, etc. **ONLY FOR INTERNAL USAGE**.
3+
*
4+
* - `$router` - the router instance
5+
* - `$route` - the current route location
6+
* - `beforeRouteEnter` - Page component option
7+
* - `beforeRouteUpdate` - Page component option
8+
* - `beforeRouteLeave` - Page component option
9+
* - `RouterLink` - RouterLink Component
10+
* - `RouterView` - RouterView Component
311
*
412
* @internal
513
*/

packages/router/src/errors.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import {
2-
MatcherLocationRaw,
3-
MatcherLocation,
4-
RouteLocationRaw,
5-
RouteLocationNormalized,
6-
} from './types'
1+
import type { MatcherLocationRaw, MatcherLocation } from './types'
2+
import type { RouteLocationRaw, RouteLocationNormalized } from './typed-routes'
73
import { assign } from './utils'
84

95
/**

packages/router/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export { createWebHashHistory } from './history/hash'
44
export { createRouterMatcher } from './matcher'
55
export type { RouterMatcher } from './matcher'
66

7+
export type * from './typed-routes'
8+
79
export { parseQuery, stringifyQuery } from './query'
810
export type {
911
LocationQuery,
@@ -29,7 +31,7 @@ export {
2931
viewDepthKey,
3032
} from './injectionSymbols'
3133

32-
export { START_LOCATION_NORMALIZED as START_LOCATION } from './types'
34+
export { START_LOCATION_NORMALIZED as START_LOCATION } from './location'
3335
export type {
3436
// route location
3537
_RouteLocationBase,
@@ -52,7 +54,6 @@ export type {
5254
_RouteRecordBase,
5355
RouteRecordName,
5456
RouteRecordRaw,
55-
RouteRecordRedirectOption,
5657
RouteRecordSingleView,
5758
RouteRecordSingleViewWithChildren,
5859
RouteRecordMultipleViews,

packages/router/src/injectionSymbols.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { InjectionKey, ComputedRef, Ref } from 'vue'
2-
import { RouteLocationNormalizedLoaded } from './types'
2+
import type { RouteLocationNormalizedLoaded } from './typed-routes'
33
import { RouteRecordNormalized } from './matcher/types'
44
import type { Router } from './router'
55

packages/router/src/location.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RouteRecord } from './matcher/types'
88
import { warn } from './warning'
99
import { isArray } from './utils'
1010
import { decode } from './encoding'
11+
import { RouteLocationNormalizedLoaded } from './typed-routes'
1112

1213
/**
1314
* Location object returned by {@link `parseURL`}.
@@ -247,3 +248,32 @@ export function resolveRelativePath(to: string, from: string): string {
247248
toSegments.slice(toPosition).join('/')
248249
)
249250
}
251+
252+
/**
253+
* Initial route location where the router is. Can be used in navigation guards
254+
* to differentiate the initial navigation.
255+
*
256+
* @example
257+
* ```js
258+
* import { START_LOCATION } from 'vue-router'
259+
*
260+
* router.beforeEach((to, from) => {
261+
* if (from === START_LOCATION) {
262+
* // initial navigation
263+
* }
264+
* })
265+
* ```
266+
*/
267+
export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
268+
path: '/',
269+
// @ts-expect-error: internal name for compatibility
270+
name: undefined,
271+
// TODO: could we use a symbol in the future?
272+
params: {},
273+
query: {},
274+
hash: '',
275+
fullPath: '/',
276+
matched: [],
277+
meta: {},
278+
redirectedFrom: undefined,
279+
}

packages/router/src/matcher/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
MatcherLocationRaw,
44
MatcherLocation,
55
isRouteName,
6-
RouteRecordName,
76
_RouteRecordProps,
7+
RouteRecordName,
88
} from '../types'
99
import { createRouterError, ErrorTypes, MatcherError } from '../errors'
1010
import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher'
@@ -28,10 +28,10 @@ import { assign, noop } from '../utils'
2828
*/
2929
export interface RouterMatcher {
3030
addRoute: (record: RouteRecordRaw, parent?: RouteRecordMatcher) => () => void
31-
removeRoute: {
32-
(matcher: RouteRecordMatcher): void
33-
(name: RouteRecordName): void
34-
}
31+
32+
removeRoute(matcher: RouteRecordMatcher): void
33+
removeRoute(name: RouteRecordName): void
34+
3535
getRoutes: () => RouteRecordMatcher[]
3636
getRecordMatcher: (name: RouteRecordName) => RouteRecordMatcher | undefined
3737

packages/router/src/navigationGuards.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import {
22
NavigationGuard,
3-
RouteLocationNormalized,
43
NavigationGuardNext,
5-
RouteLocationRaw,
6-
RouteLocationNormalizedLoaded,
74
NavigationGuardNextCallback,
85
isRouteLocation,
96
Lazy,
107
RouteComponent,
118
RawRouteComponent,
129
} from './types'
10+
import type {
11+
RouteLocationRaw,
12+
RouteLocationNormalized,
13+
RouteLocationNormalizedLoaded,
14+
} from './typed-routes'
1315

1416
import {
1517
createRouterError,

packages/router/src/router.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import {
2-
RouteLocationNormalized,
32
RouteRecordRaw,
4-
RouteLocationRaw,
53
NavigationHookAfter,
6-
START_LOCATION_NORMALIZED,
74
Lazy,
8-
RouteLocationNormalizedLoaded,
9-
RouteLocation,
10-
RouteRecordName,
115
isRouteLocation,
126
isRouteName,
137
NavigationGuardWithThis,
148
RouteLocationOptions,
159
MatcherLocationRaw,
16-
RouteParams,
1710
} from './types'
11+
import type {
12+
RouteLocation,
13+
RouteLocationRaw,
14+
RouteRecordName,
15+
RouteParams,
16+
RouteLocationNormalized,
17+
RouteLocationNormalizedLoaded,
18+
} from './typed-routes'
1819
import { RouterHistory, HistoryState, NavigationType } from './history/common'
1920
import {
2021
ScrollPosition,
@@ -49,6 +50,7 @@ import {
4950
stringifyURL,
5051
isSameRouteLocation,
5152
isSameRouteRecord,
53+
START_LOCATION_NORMALIZED,
5254
} from './location'
5355
import { extractComponentsGuards, guardToPromiseFn } from './navigationGuards'
5456
import { warn } from './warning'
@@ -60,6 +62,7 @@ import {
6062
routerViewLocationKey,
6163
} from './injectionSymbols'
6264
import { addDevtools } from './devtools'
65+
import { _LiteralUnion } from './types/utils'
6366

6467
/**
6568
* Internal type to define an ErrorHandler
@@ -432,7 +435,7 @@ export function createRouter(options: RouterOptions): Router {
432435
}
433436

434437
function resolve(
435-
rawLocation: Readonly<RouteLocationRaw>,
438+
rawLocation: RouteLocationRaw,
436439
currentLocation?: RouteLocationNormalizedLoaded
437440
): RouteLocation & { href: string } {
438441
// const objectLocation = routerLocationAsObject(rawLocation)
@@ -466,15 +469,15 @@ export function createRouter(options: RouterOptions): Router {
466469
hash: decode(locationNormalized.hash),
467470
redirectedFrom: undefined,
468471
href,
469-
})
472+
}) as any // FIXME:
470473
}
471474

472475
if (__DEV__ && !isRouteLocation(rawLocation)) {
473476
warn(
474477
`router.resolve() was passed an invalid location. This will fail in production.\n- Location:`,
475478
rawLocation
476479
)
477-
rawLocation = {}
480+
return resolve({})
478481
}
479482

480483
let matcherLocation: MatcherLocationRaw
@@ -564,7 +567,8 @@ export function createRouter(options: RouterOptions): Router {
564567
? normalizeQuery(rawLocation.query)
565568
: ((rawLocation.query || {}) as LocationQuery),
566569
},
567-
matchedRoute,
570+
// make it typed
571+
matchedRoute as RouteLocation,
568572
{
569573
redirectedFrom: undefined,
570574
href,
@@ -623,7 +627,7 @@ export function createRouter(options: RouterOptions): Router {
623627

624628
if (
625629
__DEV__ &&
626-
newTargetLocation.path == null &&
630+
(!('path' in newTargetLocation) || newTargetLocation.path == null) &&
627631
!('name' in newTargetLocation)
628632
) {
629633
warn(

0 commit comments

Comments
 (0)