Skip to content

Commit

Permalink
types: expose Param helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jul 4, 2022
1 parent 4a4435e commit 3791545
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 89 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,21 +262,21 @@ Extending types with dynamically added routes:
declare module '@vue-router/routes' {
import type {
RouteRecordInfo,
_ParamValue,
ParamValue,
// these are other param helper types
_ParamValueOneOrMore,
_ParamValueZeroOrMore,
_ParamValueZeroOrOne,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'unplugin-vue-router'
export interface RouteNamedMap {
// the key is the name and should match the first generic of RouteRecordInfo
'custom-dynamic-name': RouteRecordInfo<
'custom-dynamic-name',
'/added-during-runtime/[...path]',
// these are the raw param types (accept numbers, strings, booleans, etc)
{ path: _ParamValue<true> },
{ path: ParamValue<true> },
// these are the normalized params as found in useRoute().params
{ path: _ParamValue<false> }
{ path: ParamValue<false> }
>
}
}
Expand Down
12 changes: 6 additions & 6 deletions playground/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
declare module '@vue-router/routes' {
import type {
RouteRecordInfo,
_ParamValue,
_ParamValueOneOrMore,
_ParamValueZeroOrMore,
_ParamValueZeroOrOne,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'unplugin-vue-router'
export interface RouteNamedMap {
'custom-dynamic-name': RouteRecordInfo<
'custom-dynamic-name',
'/added-during-runtime/[...path]',
{ path: _ParamValue<true> },
{ path: _ParamValue<false> }
{ path: ParamValue<true> },
{ path: ParamValue<false> }
>
}
}
12 changes: 7 additions & 5 deletions playground/src/routes/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ if (routeLocation.name === '/[name]') {
routeLocation.params.id
}
const route = useRoute()
if (route.name == '/articles/[id]') {
console.log('route.params', route.params.id)
const route = useRoute('/[name]')
const anyRoute = useRoute()
if (anyRoute.name == '/articles/[id]') {
console.log('anyRoute.params', anyRoute.params.id)
}
useRoute('/multiple-[a]-[b]-params').params
useRoute<'/[name]'>().params.name
Expand All @@ -66,14 +67,15 @@ defineRoute({
defineRoute((route) => ({
...route,
children: [
...(route.children || []),
...('children' in route ? route.children : []),
{ path: '/cosa', name: 'cosa', component: {} },
],
}))
</script>

<template>
<main>
<h1>Param: {{ $route.params.name }}</h1>
<h1>Param: {{ $route.name === '/[name]' && $route.params.name }}</h1>
<h2>Param: {{ route.params.name }}</h2>
</main>
</template>
31 changes: 15 additions & 16 deletions playground/typed-router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,37 @@ import type {
RouteRecordInfo,
RouterLinkTyped,
RouteLocationNormalizedLoadedTypedList,
RouteLocationAsString,
NavigationGuard,
_ParamValue,
_ParamValueOneOrMore,
_ParamValueZeroOrMore,
_ParamValueZeroOrOne,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'unplugin-vue-router'

declare module '@vue-router/routes' {
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', 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> }>,
'/[name]': RouteRecordInfo<'/[name]', '/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/[...path]': RouteRecordInfo<'/[...path]', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
'/about': RouteRecordInfo<'/about', '/about', Record<never, never>, Record<never, never>>,
'/articles': RouteRecordInfo<'/articles', '/articles', Record<never, never>, Record<never, never>>,
'/articles/': RouteRecordInfo<'/articles/', '/articles/', Record<never, never>, Record<never, never>>,
'/articles/[id]': RouteRecordInfo<'/articles/[id]', '/articles/:id', { id: _ParamValue<true> }, { id: _ParamValue<false> }>,
'/articles/[id]+': RouteRecordInfo<'/articles/[id]+', '/articles/:id+', { id: _ParamValueOneOrMore<true> }, { id: _ParamValueOneOrMore<false> }>,
'/articles/[id]': RouteRecordInfo<'/articles/[id]', '/articles/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/articles/[id]+': RouteRecordInfo<'/articles/[id]+', '/articles/:id+', { id: ParamValueOneOrMore<true> }, { id: ParamValueOneOrMore<false> }>,
'a rebel': RouteRecordInfo<'a rebel', '/custom-name', Record<never, never>, Record<never, never>>,
'/deep/nesting/works/[[files]]+': RouteRecordInfo<'/deep/nesting/works/[[files]]+', '/deep/nesting/works/:files*', { files?: _ParamValueZeroOrMore<true> }, { files?: _ParamValueZeroOrMore<false> }>,
'/deep/nesting/works/[[files]]+': RouteRecordInfo<'/deep/nesting/works/[[files]]+', '/deep/nesting/works/:files*', { files?: ParamValueZeroOrMore<true> }, { files?: ParamValueZeroOrMore<false> }>,
'/deep/nesting/works/too': RouteRecordInfo<'/deep/nesting/works/too', '/deep/nesting/works/too', Record<never, never>, Record<never, never>>,
'the most rebel': RouteRecordInfo<'the most rebel', '/most-rebel', Record<never, never>, Record<never, never>>,
'/multiple-[a]-[b]-params': RouteRecordInfo<'/multiple-[a]-[b]-params', '/multiple-:a-:b-params', { a: _ParamValue<true>, b: _ParamValue<true> }, { a: _ParamValue<false>, b: _ParamValue<false> }>,
'/my-optional-[[slug]]': RouteRecordInfo<'/my-optional-[[slug]]', '/my-optional-:slug?', { slug?: _ParamValueZeroOrOne<true> }, { slug?: _ParamValueZeroOrOne<false> }>,
'/multiple-[a]-[b]-params': RouteRecordInfo<'/multiple-[a]-[b]-params', '/multiple-:a-:b-params', { a: ParamValue<true>, b: ParamValue<true> }, { a: ParamValue<false>, b: ParamValue<false> }>,
'/my-optional-[[slug]]': RouteRecordInfo<'/my-optional-[[slug]]', '/my-optional-:slug?', { slug?: ParamValueZeroOrOne<true> }, { slug?: ParamValueZeroOrOne<false> }>,
'/n-[[n]]/': RouteRecordInfo<'/n-[[n]]/', '/n-:n?/', Record<never, never>, Record<never, never>>,
'/n-[[n]]/[[more]]+/': RouteRecordInfo<'/n-[[n]]/[[more]]+/', '/n-:n?/:more*/', Record<never, never>, Record<never, never>>,
'/n-[[n]]/[[more]]+/[final]': RouteRecordInfo<'/n-[[n]]/[[more]]+/[final]', '/n-:n?/:more*/:final', { final: _ParamValue<true> }, { final: _ParamValue<false> }>,
'/partial-[name]': RouteRecordInfo<'/partial-[name]', '/partial-:name', { name: _ParamValue<true> }, { name: _ParamValue<false> }>,
'/n-[[n]]/[[more]]+/[final]': RouteRecordInfo<'/n-[[n]]/[[more]]+/[final]', '/n-:n?/:more*/:final', { final: ParamValue<true> }, { final: ParamValue<false> }>,
'/partial-[name]': RouteRecordInfo<'/partial-[name]', '/partial-:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/custom-path': RouteRecordInfo<'/custom-path', '/surprise-:id(\d+)', Record<never, never>, Record<never, never>>,
'/users/': RouteRecordInfo<'/users/', '/users/', Record<never, never>, Record<never, never>>,
'/users/[id]': RouteRecordInfo<'/users/[id]', '/users/:id', { id: _ParamValue<true> }, { id: _ParamValue<false> }>,
'/users/[id].edit': RouteRecordInfo<'/users/[id].edit', '/users/:id/edit', { id: _ParamValue<true> }, { id: _ParamValue<false> }>,
'/users/[id]': RouteRecordInfo<'/users/[id]', '/users/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/[id].edit': RouteRecordInfo<'/users/[id].edit', '/users/:id/edit', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/users/nested.route.deep': RouteRecordInfo<'/users/nested.route.deep', '/users/nested/route/deep', Record<never, never>, Record<never, never>>,
}
}
Expand Down
24 changes: 12 additions & 12 deletions src/codegen/generateRouteMap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ describe('generateRouteNamedMap', () => {
tree.insert('[...a].vue') // splat
expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/[a]': RouteRecordInfo<'/[a]', '/:a', { a: _ParamValue<true> }, { a: _ParamValue<false> }>,
'/[[a]]': RouteRecordInfo<'/[[a]]', '/:a?', { a?: _ParamValueZeroOrOne<true> }, { a?: _ParamValueZeroOrOne<false> }>,
'/[...a]': RouteRecordInfo<'/[...a]', '/:a(.*)', { a: _ParamValue<true> }, { a: _ParamValue<false> }>,
'/[[a]]+': RouteRecordInfo<'/[[a]]+', '/:a*', { a?: _ParamValueZeroOrMore<true> }, { a?: _ParamValueZeroOrMore<false> }>,
'/[a]+': RouteRecordInfo<'/[a]+', '/:a+', { a: _ParamValueOneOrMore<true> }, { a: _ParamValueOneOrMore<false> }>,
'/partial-[a]': RouteRecordInfo<'/partial-[a]', '/partial-:a', { a: _ParamValue<true> }, { a: _ParamValue<false> }>,
'/partial-[[a]]': RouteRecordInfo<'/partial-[[a]]', '/partial-:a?', { a?: _ParamValueZeroOrOne<true> }, { a?: _ParamValueZeroOrOne<false> }>,
'/[a]': RouteRecordInfo<'/[a]', '/:a', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/[[a]]': RouteRecordInfo<'/[[a]]', '/:a?', { a?: ParamValueZeroOrOne<true> }, { a?: ParamValueZeroOrOne<false> }>,
'/[...a]': RouteRecordInfo<'/[...a]', '/:a(.*)', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/[[a]]+': RouteRecordInfo<'/[[a]]+', '/:a*', { a?: ParamValueZeroOrMore<true> }, { a?: ParamValueZeroOrMore<false> }>,
'/[a]+': RouteRecordInfo<'/[a]+', '/:a+', { a: ParamValueOneOrMore<true> }, { a: ParamValueOneOrMore<false> }>,
'/partial-[a]': RouteRecordInfo<'/partial-[a]', '/partial-:a', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/partial-[[a]]': RouteRecordInfo<'/partial-[[a]]', '/partial-:a?', { a?: ParamValueZeroOrOne<true> }, { a?: ParamValueZeroOrOne<false> }>,
}"
`)
})
Expand All @@ -57,11 +57,11 @@ describe('generateRouteNamedMap', () => {
tree.insert('n/[...a].vue') // splat
expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/n/[a]': RouteRecordInfo<'/n/[a]', '/n/:a', { a: _ParamValue<true> }, { a: _ParamValue<false> }>,
'/n/[[a]]': RouteRecordInfo<'/n/[[a]]', '/n/:a?', { a?: _ParamValueZeroOrOne<true> }, { a?: _ParamValueZeroOrOne<false> }>,
'/n/[...a]': RouteRecordInfo<'/n/[...a]', '/n/:a(.*)', { a: _ParamValue<true> }, { a: _ParamValue<false> }>,
'/n/[[a]]+': RouteRecordInfo<'/n/[[a]]+', '/n/:a*', { a?: _ParamValueZeroOrMore<true> }, { a?: _ParamValueZeroOrMore<false> }>,
'/n/[a]+': RouteRecordInfo<'/n/[a]+', '/n/:a+', { a: _ParamValueOneOrMore<true> }, { a: _ParamValueOneOrMore<false> }>,
'/n/[a]': RouteRecordInfo<'/n/[a]', '/n/:a', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/n/[[a]]': RouteRecordInfo<'/n/[[a]]', '/n/:a?', { a?: ParamValueZeroOrOne<true> }, { a?: ParamValueZeroOrOne<false> }>,
'/n/[...a]': RouteRecordInfo<'/n/[...a]', '/n/:a(.*)', { a: ParamValue<true> }, { a: ParamValue<false> }>,
'/n/[[a]]+': RouteRecordInfo<'/n/[[a]]+', '/n/:a*', { a?: ParamValueZeroOrMore<true> }, { a?: ParamValueZeroOrMore<false> }>,
'/n/[a]+': RouteRecordInfo<'/n/[a]+', '/n/:a+', { a: ParamValueOneOrMore<true> }, { a: ParamValueOneOrMore<false> }>,
}"
`)
})
Expand Down
30 changes: 14 additions & 16 deletions src/codegen/generateRouteParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export function generateRouteParams(node: TreeLeaf, isRaw: boolean): string {
(param) =>
`${param.paramName}${param.optional ? '?' : ''}: ` +
(param.modifier === '+'
? `_ParamValueOneOrMore<${isRaw}>`
? `ParamValueOneOrMore<${isRaw}>`
: param.modifier === '*'
? `_ParamValueZeroOrMore<${isRaw}>`
? `ParamValueZeroOrMore<${isRaw}>`
: param.modifier === '?'
? `_ParamValueZeroOrOne<${isRaw}>`
: `_ParamValue<${isRaw}>`)
? `ParamValueZeroOrOne<${isRaw}>`
: `ParamValue<${isRaw}>`)
)
.join(', ')} }`
: // no params allowed
Expand All @@ -22,35 +22,33 @@ export function generateRouteParams(node: TreeLeaf, isRaw: boolean): string {
/**
* Utility type for raw and non raw params like :id+
*
* @internal
*/
export type _ParamValueOneOrMore<isRaw extends boolean> = true extends isRaw
? [string | number, ...(string | number)[]]
: [string, ...string[]]
export type ParamValueOneOrMore<isRaw extends boolean> = [
ParamValue<isRaw>,
...ParamValue<isRaw>[]
]

/**
* Utility type for raw and non raw params like :id*
*
* @internal
*/
export type _ParamValueZeroOrMore<isRaw extends boolean> = true extends isRaw
? (string | number)[] | undefined | null
: string[] | undefined | null
export type ParamValueZeroOrMore<isRaw extends boolean> =
| ParamValue<isRaw>[]
| undefined
| null

/**
* Utility type for raw and non raw params like :id?
*
* @internal
*/
export type _ParamValueZeroOrOne<isRaw extends boolean> = true extends isRaw
export type ParamValueZeroOrOne<isRaw extends boolean> = true extends isRaw
? string | number | null | undefined
: string

/**
* Utility type for raw and non raw params like :id
*
* @internal
*/
export type _ParamValue<isRaw extends boolean> = true extends isRaw
export type ParamValue<isRaw extends boolean> = true extends isRaw
? string | number
: string
9 changes: 4 additions & 5 deletions src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,11 @@ import type {
RouteRecordInfo,
RouterLinkTyped,
RouteLocationNormalizedLoadedTypedList,
RouteLocationAsString,
NavigationGuard,
_ParamValue,
_ParamValueOneOrMore,
_ParamValueZeroOrMore,
_ParamValueZeroOrOne,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'unplugin-vue-router'
declare module '${MODULE_ROUTES_PATH}' {
Expand Down
10 changes: 4 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { createRoutesContext } from './core/context'
import {
MODULE_ROUTES_PATH,
MODULE_VUE_ROUTER,
VIRTUAL_PREFIX,
getVirtualId as _getVirtualId,
asVirtualId as _asVirtualId,
} from './core/moduleConstants'
Expand Down Expand Up @@ -86,7 +85,6 @@ export type {
} from './codegen/generateRouteMap'
export type {
RouteLocationNormalizedTyped,
// RouteLocationNormalizedTypedList,
RouteLocationNormalizedLoadedTyped,
RouteLocationNormalizedLoadedTypedList,
RouteLocationAsRelativeTyped,
Expand All @@ -99,8 +97,8 @@ export type { NavigationGuard } from './typeExtensions/navigationGuards'
export type { _RouterTyped } from './typeExtensions/router'
export type { RouterLinkTyped } from './typeExtensions/RouterLink'
export type {
_ParamValue,
_ParamValueOneOrMore,
_ParamValueZeroOrMore,
_ParamValueZeroOrOne,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from './codegen/generateRouteParams'
32 changes: 15 additions & 17 deletions src/typeExtensions/RouterTyped.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import type {
_RouteMapGeneric,
} from '../codegen/generateRouteMap'
import type {
_ParamValue,
_ParamValueOneOrMore,
_ParamValueZeroOrOne,
_ParamValueZeroOrMore,
ParamValue,
ParamValueOneOrMore,
} from '../codegen/generateRouteParams'
import type { _RouterTyped as RouterTyped } from './router'
import { RouteLocationTyped } from './routeLocation'
Expand All @@ -28,14 +26,14 @@ describe('RouterTyped', () => {
'/[...path]': RouteRecordInfo<
'/[...path]',
'/:path(.*)',
{ path: _ParamValue<true> },
{ path: _ParamValue<false> }
{ path: ParamValue<true> },
{ path: ParamValue<false> }
>
'/[a]': RouteRecordInfo<
'/[a]',
'/:a',
{ a: _ParamValue<true> },
{ a: _ParamValue<false> }
{ a: ParamValue<true> },
{ a: ParamValue<false> }
>
'/a': RouteRecordInfo<
'/a',
Expand All @@ -46,16 +44,16 @@ describe('RouterTyped', () => {
'/[id]+': RouteRecordInfo<
'/[id]+',
'/:id+',
{ id: _ParamValueOneOrMore<true> },
{ id: _ParamValueOneOrMore<false> }
{ id: ParamValueOneOrMore<true> },
{ id: ParamValueOneOrMore<false> }
>
}
const router = defineRouter<RouteMap>()

it('resolve', () => {
typeTest(() => {
expectType<Record<never, never>>(router.resolve({ name: '/a' }).params)
expectType<{ a: _ParamValue<true> }>(
expectType<{ a: ParamValue<true> }>(
router.resolve({ name: '/[a]' }).params
)

Expand Down Expand Up @@ -92,12 +90,12 @@ describe('RouterTyped', () => {
// @ts-expect-error: no route named this way
if (to.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
// @ts-expect-error: no route named this way
if (from.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
if (Math.random()) {
return { name: '/[a]', params: { a: 2 } }
Expand All @@ -115,12 +113,12 @@ describe('RouterTyped', () => {
// @ts-expect-error: no route named this way
if (to.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
// @ts-expect-error: no route named this way
if (from.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
if (Math.random()) {
return { name: '/[a]', params: { a: 2 } }
Expand All @@ -138,12 +136,12 @@ describe('RouterTyped', () => {
// @ts-expect-error: no route named this way
if (to.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
// @ts-expect-error: no route named this way
if (from.name === '/[id]') {
} else if (to.name === '/[a]') {
expectType<{ a: _ParamValue<true> }>(to.params)
expectType<{ a: ParamValue<true> }>(to.params)
}
if (Math.random()) {
return { name: '/[a]', params: { a: 2 } }
Expand Down

0 comments on commit 3791545

Please sign in to comment.