Skip to content

Commit

Permalink
feat: wip typed routes
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jun 10, 2024
1 parent e8791aa commit f92282b
Show file tree
Hide file tree
Showing 25 changed files with 458 additions and 87 deletions.
32 changes: 32 additions & 0 deletions packages/playground/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ComponentPublicInstance } from 'vue'
import { router, routerHistory } from './router'
import { globalState } from './store'
import App from './App.vue'
import { useRoute, type ParamValue, type RouteRecordInfo } from 'vue-router'

declare global {
interface Window {
Expand All @@ -29,3 +30,34 @@ app.provide('state', globalState)
app.use(router)

window.vm = app.mount('#app')

export interface RouteNamedMap {
home: RouteRecordInfo<'home', '/', Record<never, never>, Record<never, never>>
'/[name]': RouteRecordInfo<
'/[name]',
'/:name',
{ name: ParamValue<true> },
{ name: ParamValue<false> }
>
'/[...path]': RouteRecordInfo<
'/[...path]',
'/:path(.*)',
{ path: ParamValue<true> },
{ path: ParamValue<false> }
>
}

declare module 'vue-router' {
interface TypesConfig {
RouteNamedMap: RouteNamedMap
}
}

const r = useRoute()

if (r.name === '/[name]') {
r.params.name.toUpperCase()
// @ts-expect-error: Not existing route
} else if (r.name === 'nope') {
console.log('nope')
}
2 changes: 1 addition & 1 deletion packages/router/__tests__/RouterLink.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*/
import { RouterLink } from '../src/RouterLink'
import {
START_LOCATION_NORMALIZED,
RouteQueryAndHash,
MatcherLocationRaw,
RouteLocationNormalized,
} from '../src/types'
import { START_LOCATION_NORMALIZED } from '../src/location'
import { createMemoryHistory, RouterOptions } from '../src'
import { createMockedRoute } from './mount'
import { defineComponent, PropType } from 'vue'
Expand Down
6 changes: 2 additions & 4 deletions packages/router/__tests__/RouterView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
*/
import { RouterView } from '../src/RouterView'
import { components, RouteLocationNormalizedLoose } from './utils'
import {
START_LOCATION_NORMALIZED,
RouteLocationNormalized,
} from '../src/types'
import { RouteLocationNormalized } from '../src/types'
import { START_LOCATION_NORMALIZED } from '../src/location'
import { markRaw } from 'vue'
import { createMockedRoute } from './mount'
import { mockWarn } from 'jest-mock-warn'
Expand Down
9 changes: 4 additions & 5 deletions packages/router/__tests__/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import {
ErrorTypes,
} from '../src/errors'
import { components, tick } from './utils'
import {
RouteRecordRaw,
NavigationGuard,
import { RouteRecordRaw, NavigationGuard } from '../src/types'
import type {
RouteLocationRaw,
START_LOCATION_NORMALIZED,
RouteLocationNormalized,
} from '../src/types'
} from '../src/typed-routes'
import { mockWarn } from 'jest-mock-warn'
import { START_LOCATION_NORMALIZED } from '../src/location'

const routes: Readonly<RouteRecordRaw>[] = [
{ path: '/', component: components.Home },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { extractComponentsGuards } from '../../src/navigationGuards'
import { START_LOCATION_NORMALIZED, RouteRecordRaw } from '../../src/types'
import { RouteRecordRaw } from '../../src/types'
import { START_LOCATION_NORMALIZED } from '../../src/location'
import { components } from '../utils'
import { normalizeRouteRecord } from '../../src/matcher'
import { RouteRecordNormalized } from 'src/matcher/types'
Expand Down
2 changes: 1 addition & 1 deletion packages/router/__tests__/guards/guardToPromiseFn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { guardToPromiseFn } from '../../src/navigationGuards'
import { START_LOCATION_NORMALIZED } from '../../src/types'
import { START_LOCATION_NORMALIZED } from '../../src/location'
import { ErrorTypes } from '../../src/errors'
import { mockWarn } from 'jest-mock-warn'

Expand Down
4 changes: 2 additions & 2 deletions packages/router/__tests__/matcher/resolve.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createRouterMatcher, normalizeRouteRecord } from '../../src/matcher'
import {
START_LOCATION_NORMALIZED,
RouteComponent,
RouteRecordRaw,
MatcherLocationRaw,
Expand All @@ -9,6 +8,7 @@ import {
import { MatcherLocationNormalizedLoose } from '../utils'
import { mockWarn } from 'jest-mock-warn'
import { defineComponent } from '@vue/runtime-core'
import { START_LOCATION_NORMALIZED } from '../../src/location'

const component: RouteComponent = defineComponent({})

Expand Down Expand Up @@ -75,7 +75,7 @@ describe('RouterMatcher.resolve', () => {
/**
*
* @param record - Record or records we are testing the matcher against
* @param location - location we want to reolve against
* @param location - location we want to resolve against
* @param [start] Optional currentLocation used when resolving
* @returns error
*/
Expand Down
7 changes: 2 additions & 5 deletions packages/router/__tests__/router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ import {
} from '../src'
import { NavigationFailureType } from '../src/errors'
import { createDom, components, tick, nextNavigation } from './utils'
import {
RouteRecordRaw,
RouteLocationRaw,
START_LOCATION_NORMALIZED,
} from '../src/types'
import { RouteRecordRaw, RouteLocationRaw } from '../src/types'
import { mockWarn } from 'jest-mock-warn'
import { START_LOCATION_NORMALIZED } from '../src/location'

declare var __DEV__: boolean

Expand Down
10 changes: 9 additions & 1 deletion packages/router/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
/**
* Allows customizing existing types of the router that are used globally like `$router`, `<RouterLink>`, and `beforeRouteLeave()`. **ONLY FOR INTERNAL USAGE**.
* Allows customizing existing types of the router that are used globally like `$router`, `<RouterLink>`, etc. **ONLY FOR INTERNAL USAGE**.
*
* - `$router` - the router instance
* - `$route` - the current route location
* - `beforeRouteEnter` - Page component option
* - `beforeRouteUpdate` - Page component option
* - `beforeRouteLeave` - Page component option
* - `RouterLink` - RouterLink Component
* - `RouterView` - RouterView Component
*
* @internal
*/
Expand Down
8 changes: 2 additions & 6 deletions packages/router/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
MatcherLocationRaw,
MatcherLocation,
RouteLocationRaw,
RouteLocationNormalized,
} from './types'
import type { MatcherLocationRaw, MatcherLocation } from './types'
import type { RouteLocationRaw, RouteLocationNormalized } from './typed-routes'
import { assign } from './utils'

/**
Expand Down
5 changes: 3 additions & 2 deletions packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { createWebHashHistory } from './history/hash'
export { createRouterMatcher } from './matcher'
export type { RouterMatcher } from './matcher'

export type * from './typed-routes'

export { parseQuery, stringifyQuery } from './query'
export type {
LocationQuery,
Expand All @@ -29,7 +31,7 @@ export {
viewDepthKey,
} from './injectionSymbols'

export { START_LOCATION_NORMALIZED as START_LOCATION } from './types'
export { START_LOCATION_NORMALIZED as START_LOCATION } from './location'
export type {
// route location
_RouteLocationBase,
Expand All @@ -52,7 +54,6 @@ export type {
_RouteRecordBase,
RouteRecordName,
RouteRecordRaw,
RouteRecordRedirectOption,
RouteRecordSingleView,
RouteRecordSingleViewWithChildren,
RouteRecordMultipleViews,
Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/injectionSymbols.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InjectionKey, ComputedRef, Ref } from 'vue'
import { RouteLocationNormalizedLoaded } from './types'
import type { RouteLocationNormalizedLoaded } from './typed-routes'
import { RouteRecordNormalized } from './matcher/types'
import type { Router } from './router'

Expand Down
30 changes: 30 additions & 0 deletions packages/router/src/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RouteRecord } from './matcher/types'
import { warn } from './warning'
import { isArray } from './utils'
import { decode } from './encoding'
import { RouteLocationNormalizedLoaded } from './typed-routes'

/**
* Location object returned by {@link `parseURL`}.
Expand Down Expand Up @@ -247,3 +248,32 @@ export function resolveRelativePath(to: string, from: string): string {
toSegments.slice(toPosition).join('/')
)
}

/**
* Initial route location where the router is. Can be used in navigation guards
* to differentiate the initial navigation.
*
* @example
* ```js
* import { START_LOCATION } from 'vue-router'
*
* router.beforeEach((to, from) => {
* if (from === START_LOCATION) {
* // initial navigation
* }
* })
* ```
*/
export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
path: '/',
// @ts-expect-error: internal name for compatibility
name: undefined,
// TODO: could we use a symbol in the future?
params: {},
query: {},
hash: '',
fullPath: '/',
matched: [],
meta: {},
redirectedFrom: undefined,
}
10 changes: 5 additions & 5 deletions packages/router/src/matcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
MatcherLocationRaw,
MatcherLocation,
isRouteName,
RouteRecordName,
_RouteRecordProps,
RouteRecordName,
} from '../types'
import { createRouterError, ErrorTypes, MatcherError } from '../errors'
import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher'
Expand All @@ -28,10 +28,10 @@ import { assign, noop } from '../utils'
*/
export interface RouterMatcher {
addRoute: (record: RouteRecordRaw, parent?: RouteRecordMatcher) => () => void
removeRoute: {
(matcher: RouteRecordMatcher): void
(name: RouteRecordName): void
}

removeRoute(matcher: RouteRecordMatcher): void
removeRoute(name: RouteRecordName): void

getRoutes: () => RouteRecordMatcher[]
getRecordMatcher: (name: RouteRecordName) => RouteRecordMatcher | undefined

Expand Down
8 changes: 5 additions & 3 deletions packages/router/src/navigationGuards.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import {
NavigationGuard,
RouteLocationNormalized,
NavigationGuardNext,
RouteLocationRaw,
RouteLocationNormalizedLoaded,
NavigationGuardNextCallback,
isRouteLocation,
Lazy,
RouteComponent,
RawRouteComponent,
} from './types'
import type {
RouteLocationRaw,
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
} from './typed-routes'

import {
createRouterError,
Expand Down
28 changes: 16 additions & 12 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import {
RouteLocationNormalized,
RouteRecordRaw,
RouteLocationRaw,
NavigationHookAfter,
START_LOCATION_NORMALIZED,
Lazy,
RouteLocationNormalizedLoaded,
RouteLocation,
RouteRecordName,
isRouteLocation,
isRouteName,
NavigationGuardWithThis,
RouteLocationOptions,
MatcherLocationRaw,
RouteParams,
} from './types'
import type {
RouteLocation,
RouteLocationRaw,
RouteRecordName,
RouteParams,
RouteLocationNormalized,
RouteLocationNormalizedLoaded,
} from './typed-routes'
import { RouterHistory, HistoryState, NavigationType } from './history/common'
import {
ScrollPosition,
Expand Down Expand Up @@ -49,6 +50,7 @@ import {
stringifyURL,
isSameRouteLocation,
isSameRouteRecord,
START_LOCATION_NORMALIZED,
} from './location'
import { extractComponentsGuards, guardToPromiseFn } from './navigationGuards'
import { warn } from './warning'
Expand All @@ -60,6 +62,7 @@ import {
routerViewLocationKey,
} from './injectionSymbols'
import { addDevtools } from './devtools'
import { _LiteralUnion } from './types/utils'

/**
* Internal type to define an ErrorHandler
Expand Down Expand Up @@ -432,7 +435,7 @@ export function createRouter(options: RouterOptions): Router {
}

function resolve(
rawLocation: Readonly<RouteLocationRaw>,
rawLocation: RouteLocationRaw,
currentLocation?: RouteLocationNormalizedLoaded
): RouteLocation & { href: string } {
// const objectLocation = routerLocationAsObject(rawLocation)
Expand Down Expand Up @@ -466,15 +469,15 @@ export function createRouter(options: RouterOptions): Router {
hash: decode(locationNormalized.hash),
redirectedFrom: undefined,
href,
})
}) as any // FIXME:
}

if (__DEV__ && !isRouteLocation(rawLocation)) {
warn(
`router.resolve() was passed an invalid location. This will fail in production.\n- Location:`,
rawLocation
)
rawLocation = {}
return resolve({})
}

let matcherLocation: MatcherLocationRaw
Expand Down Expand Up @@ -564,7 +567,8 @@ export function createRouter(options: RouterOptions): Router {
? normalizeQuery(rawLocation.query)
: ((rawLocation.query || {}) as LocationQuery),
},
matchedRoute,
// make it typed
matchedRoute as RouteLocation,
{
redirectedFrom: undefined,
href,
Expand Down Expand Up @@ -623,7 +627,7 @@ export function createRouter(options: RouterOptions): Router {

if (
__DEV__ &&
newTargetLocation.path == null &&
(!('path' in newTargetLocation) || newTargetLocation.path == null) &&
!('name' in newTargetLocation)
) {
warn(
Expand Down
Loading

0 comments on commit f92282b

Please sign in to comment.