) {
record = Array.isArray(record) ? record : [record]
const matcher = new RouterMatcher(record)
- const targetLocation = {}
if (!('meta' in resolved)) {
resolved.meta = record[0].meta || {}
}
// add location if provided as it should be the same value
- if ('path' in location) {
+ if ('path' in location && !('path' in resolved)) {
resolved.path = location.path
}
// make matched non enumerable
Object.defineProperty(startCopy, 'matched', { enumerable: false })
- const result = matcher.resolve(
- {
- ...targetLocation,
- // override anything provided in location
- ...location,
- },
- startCopy
- )
+ const result = matcher.resolve(location, startCopy)
expect(result).toEqual(resolved)
}
}
}
+ describe('alias', () => {
+ it('resolves an alias', () => {
+ assertRecordMatch(
+ {
+ path: '/',
+ alias: '/home',
+ name: 'Home',
+ components,
+ meta: { foo: true },
+ },
+ { path: '/home' },
+ {
+ name: 'Home',
+ path: '/home',
+ params: {},
+ meta: { foo: true },
+ matched: [
+ {
+ path: '/home',
+ name: 'Home',
+ components,
+ meta: { foo: true },
+ },
+ ],
+ }
+ )
+ })
+ })
+
describe('LocationAsPath', () => {
it('resolves a normal path', () => {
assertRecordMatch(
MatcherLocation,
MatcherLocationNormalized,
MatcherLocationRedirect,
+ RouteRecordRedirect,
+ RouteRecordMultipleViews,
+ RouteRecordSingleView,
+ Mutable,
// TODO: add it to matched
// MatchedRouteRecord,
} from './types/index'
import { NoRouteMatchError, InvalidRouteMatch } from './errors'
-type NormalizedRouteRecord = Exclude<RouteRecord, { component: any }> // normalize component/components into components
+// normalize component/components into components
+type NormalizedRouteRecord =
+ | Omit<RouteRecordRedirect, 'alias'>
+ | Omit<RouteRecordMultipleViews, 'alias'>
export interface RouteMatcher {
re: RegExp
parent: RouteMatcher | void
// TODO: children so they can be removed
// children: RouteMatcher[]
+ // TODO: needs information like optional, repeatable
keys: string[]
score: number
}
+function copyObject<T extends Object, K extends keyof T>(
+ a: T,
+ keys: K[]
+): Mutable<Pick<T, K>> {
+ const copy: Pick<T, K> = {} as Pick<T, K>
+
+ for (const key of keys) {
+ if (!a.hasOwnProperty(key)) continue
+ if (key in a) copy[key] = a[key]
+ }
+
+ return copy
+}
+
+const ROUTE_RECORD_REDIRECT_KEYS: (keyof RouteRecordRedirect)[] = [
+ 'path',
+ 'name',
+ 'beforeEnter',
+ 'redirect',
+ 'meta',
+]
+const ROUTE_RECORD_MULTIPLE_VIEWS_KEYS: (keyof (
+ | RouteRecordMultipleViews
+ | RouteRecordSingleView))[] = [
+ 'path',
+ 'name',
+ 'beforeEnter',
+ 'children',
+ 'meta',
+]
/**
* Normalizes a RouteRecord into a MatchedRouteRecord. Creates a copy
* @param record
export function normalizeRecord(
record: Readonly<RouteRecord>
): NormalizedRouteRecord {
- if ('component' in record) {
- const { component, ...rest } = record
- // @ts-ignore I could do it type safe by copying again rest:
- // return {
- // ...rest,
- // components: { default: component }
- // }
- // but it's slower
- rest.components = { default: component }
- return rest as NormalizedRouteRecord
+ // TODO: could be refactored to improve typings
+ if ('redirect' in record) {
+ return copyObject(record, ROUTE_RECORD_REDIRECT_KEYS)
+ } else {
+ const copy: RouteRecordMultipleViews = copyObject(
+ record,
+ ROUTE_RECORD_MULTIPLE_VIEWS_KEYS
+ ) as RouteRecordMultipleViews
+ copy.components =
+ 'components' in record ? record.components : { default: record.component }
+ return copy
}
-
- // otherwise just create a copy
- return { ...record }
}
const enum PathScore {
strict: false,
}
- const recordCopy = normalizeRecord(record)
+ // generate an array of records to correctly handle aliases
+ const normalizedRecords = [normalizeRecord(record)]
+ if ('alias' in record && record.alias) {
+ const aliases =
+ typeof record.alias === 'string' ? [record.alias] : record.alias
+ for (const alias of aliases) {
+ const copyForAlias = normalizeRecord(record)
+ copyForAlias.path = alias
+ normalizedRecords.push(copyForAlias)
+ }
+ }
if (parent) {
// if the child isn't an absolute route
if (record.path[0] !== '/') {
let path = parent.record.path
// only add the / delimiter if the child path isn't empty
- if (recordCopy.path) path += '/'
- path += record.path
- recordCopy.path = path
+ for (const normalizedRecord of normalizedRecords) {
+ if (normalizedRecord.path) path += '/'
+ path += record.path
+ normalizedRecord.path = path
+ }
}
}
- // create the object before hand so it can be passed to children
- const matcher = createRouteMatcher(recordCopy, parent, options)
+ for (const normalizedRecord of normalizedRecords) {
+ // create the object before hand so it can be passed to children
+ const matcher = createRouteMatcher(normalizedRecord, parent, options)
- if ('children' in record && record.children) {
- for (const childRecord of record.children) {
- this.addRouteRecord(childRecord, matcher)
+ if ('children' in record && record.children) {
+ for (const childRecord of record.children) {
+ this.addRouteRecord(childRecord, matcher)
+ }
+ // TODO: the parent is special, we should match their children. They
+ // reference to the parent so we can render the parent
+ //
+ // matcher.score = -10
}
- // TODO: the parent is special, we should match their children. They
- // reference to the parent so we can render the parent
- //
- // matcher.score = -10
- }
- this.insertMatcher(matcher)
+ this.insertMatcher(matcher)
+ }
}
private insertMatcher(matcher: RouteMatcher) {
if (!matcher) throw new NoRouteMatchError(currentLocation, location)
// no need to resolve the path with the matcher as it was provided
+ // this also allows the user to control the encoding
path = location.path
name = matcher.record.name
// and I don't thin it's possible to filter out the route
// by any means
-interface RouteRecordCommon {
- path: string // | RegExp
+export interface RouteRecordCommon {
+ path: string
+ alias?: string | string[]
name?: string
beforeEnter?: NavigationGuard | NavigationGuard[]
meta?: Record<string | number | symbol, any>
redirect: RouteRecordRedirectOption
}
-interface RouteRecordSingleView extends RouteRecordCommon {
+export interface RouteRecordSingleView extends RouteRecordCommon {
component: RouteComponent
children?: RouteRecord[]
}
-interface RouteRecordMultipleViews extends RouteRecordCommon {
+export interface RouteRecordMultipleViews extends RouteRecordCommon {
components: Record<string, RouteComponent>
// TODO: add tests
children?: RouteRecord[]
}
export * from './type-guards'
+
+export type Mutable<T> = {
+ -readonly [P in keyof T]: T[P]
+}