From: Eduardo San Martin Morote Date: Thu, 17 Oct 2019 14:23:55 +0000 (+0200) Subject: feat(matcher): allow alias X-Git-Tag: v4.0.0-alpha.0~184 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dffb72b405a3fcf30432f611bf4224495d67210c;p=thirdparty%2Fvuejs%2Frouter.git feat(matcher): allow alias --- diff --git a/__tests__/matcher.spec.ts b/__tests__/matcher.spec.ts index 734a08cb..0c45d367 100644 --- a/__tests__/matcher.spec.ts +++ b/__tests__/matcher.spec.ts @@ -25,14 +25,13 @@ describe('Router Matcher', () => { ) { 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 } @@ -61,14 +60,7 @@ describe('Router Matcher', () => { // 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) } @@ -91,6 +83,35 @@ describe('Router Matcher', () => { } } + 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( diff --git a/explorations/html5.ts b/explorations/html5.ts index 58e70a7f..086ec8aa 100644 --- a/explorations/html5.ts +++ b/explorations/html5.ts @@ -113,7 +113,7 @@ const scrollWaiter = new ScrollQueue() const router = new Router({ history: routerHistory, routes: [ - { path: '/', component: Home, name: 'home' }, + { path: '/', component: Home, name: 'home', alias: '/home' }, { path: '/users/:id', name: 'user', component: User }, { path: '/documents/:id', name: 'docs', component: User }, { path: encodeURI('/n/€'), name: 'euro', component }, diff --git a/src/matcher.ts b/src/matcher.ts index 387bcd41..1ed75330 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -5,12 +5,19 @@ import { MatcherLocation, MatcherLocationNormalized, MatcherLocationRedirect, + RouteRecordRedirect, + RouteRecordMultipleViews, + RouteRecordSingleView, + Mutable, // TODO: add it to matched // MatchedRouteRecord, } from './types/index' import { NoRouteMatchError, InvalidRouteMatch } from './errors' -type NormalizedRouteRecord = Exclude // normalize component/components into components +// normalize component/components into components +type NormalizedRouteRecord = + | Omit + | Omit export interface RouteMatcher { re: RegExp @@ -19,10 +26,41 @@ export interface RouteMatcher { parent: RouteMatcher | void // TODO: children so they can be removed // children: RouteMatcher[] + // TODO: needs information like optional, repeatable keys: string[] score: number } +function copyObject( + a: T, + keys: K[] +): Mutable> { + const copy: Pick = {} as Pick + + 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 @@ -31,20 +69,18 @@ export interface RouteMatcher { export function normalizeRecord( record: Readonly ): 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 { @@ -286,33 +322,47 @@ export class RouterMatcher { 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) { @@ -376,6 +426,7 @@ export class RouterMatcher { 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 diff --git a/src/types/index.ts b/src/types/index.ts index e291717e..e2294e0b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -105,8 +105,9 @@ export type RouteComponent = Component | Lazy // 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 @@ -119,12 +120,12 @@ export interface RouteRecordRedirect extends RouteRecordCommon { 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 // TODO: add tests children?: RouteRecord[] @@ -201,3 +202,7 @@ export interface PostNavigationGuard { } export * from './type-guards' + +export type Mutable = { + -readonly [P in keyof T]: T[P] +}