From: Eduardo San Martin Morote Date: Mon, 6 May 2019 13:42:48 +0000 (+0200) Subject: feat: add children suppor to matcher X-Git-Tag: v4.0.0-alpha.0~392 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=251062d84bd25c0a352107b4c9346e693ed51f8f;p=thirdparty%2Fvuejs%2Frouter.git feat: add children suppor to matcher --- diff --git a/__tests__/matcher.spec.js b/__tests__/matcher.spec.js index 42a0b9a4..b714866d 100644 --- a/__tests__/matcher.spec.js +++ b/__tests__/matcher.spec.js @@ -445,5 +445,65 @@ describe('Router Matcher', () => { ) }) }) + + describe('children', () => { + const ChildA = { path: 'a', name: 'child-a', component } + const ChildB = { path: 'b', name: 'child-b', component } + const ChildC = { path: 'c', name: 'child-c', component } + const NestedChildA = { ...ChildA, name: 'nested-child-a' } + const NestedChildB = { ...ChildB, name: 'nested-child-b' } + const NestedChildC = { ...ChildC, name: 'nested-child-c' } + const Nested = { + path: 'nested', + name: 'nested', + component, + children: [NestedChildA, NestedChildB, NestedChildC], + } + + it('resolves children', () => { + const Foo = { + path: '/foo', + name: 'Foo', + component, + children: [ChildA, ChildB, ChildC], + } + assertRecordMatch( + Foo, + { path: '/foo/b' }, + { + name: 'child-b', + path: '/foo/b', + params: {}, + matched: [Foo, { ...ChildB, path: `${Foo.path}/${ChildB.path}` }], + } + ) + }) + + it('resolves nested children', () => { + const Foo = { + path: '/foo', + name: 'Foo', + component, + children: [Nested], + } + assertRecordMatch( + Foo, + { path: '/foo/nested/a' }, + { + name: 'nested-child-a', + path: '/foo/nested/a', + params: {}, + matched: [ + Foo, + { ...Nested, path: `${Foo.path}/${Nested.path}` }, + { + ...NestedChildA, + path: `${Foo.path}/${Nested.path}/${NestedChildA.path}`, + }, + ], + } + ) + }) + }) }) }) diff --git a/src/matcher.ts b/src/matcher.ts index 46baa6a3..14845626 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -11,27 +11,77 @@ import { NoRouteMatchError, InvalidRouteMatch } from './errors' interface RouteMatcher { re: RegExp resolve: (params?: RouteParams) => string - record: RouteRecord + record: RouteRecord // TODO: NormalizedRouteRecord? + parent: RouteMatcher | void keys: string[] } -function generateMatcher(record: RouteRecord): RouteMatcher { - const keys: pathToRegexp.Key[] = [] - // TODO: if children use option end: false ? - const re = pathToRegexp(record.path, keys) - return { - re, - resolve: pathToRegexp.compile(record.path), - keys: keys.map(k => '' + k.name), - record, - } -} +// function generateMatcher(record: RouteRecord): RouteMatcher { +// const keys: pathToRegexp.Key[] = [] + +// const options: pathToRegexp.RegExpOptions = {} +// let children: RouteMatcher[] = [] +// // TODO: if children use option end: false ? +// // TODO: why is the isArray check necessary for ts? +// if ('children' in record && Array.isArray(record.children)) { +// children = record.children.map(generateMatcher) +// options.end = false // match for partial url +// } + +// const re = pathToRegexp(record.path, keys, options) + +// return { +// re, +// resolve: pathToRegexp.compile(record.path), +// keys: keys.map(k => '' + k.name), +// record, +// } +// } export class RouterMatcher { private matchers: RouteMatcher[] = [] constructor(routes: RouteRecord[]) { - this.matchers = routes.map(generateMatcher) + for (const route of routes) { + this.addRouteRecord(route) + } + } + + private addRouteRecord( + record: Readonly, + parent?: RouteMatcher + ): void { + const keys: pathToRegexp.Key[] = [] + const options: pathToRegexp.RegExpOptions = {} + + const recordCopy = { ...record } + if (parent) { + // if the child isn't an absolute route + if (record.path[0] !== '/') { + recordCopy.path = parent.record.path + '/' + record.path // TODO: check for trailing slash? + } + } + + const re = pathToRegexp(recordCopy.path, keys, options) + + // create the object before hand so it can be passed to children + const matcher: RouteMatcher = { + parent, + record: recordCopy, + re, + keys: keys.map(k => '' + k.name), + resolve: pathToRegexp.compile(record.path), + } + + // TODO: if children use option end: false ? + // TODO: why is the isArray check necessary for ts? + if ('children' in record && Array.isArray(record.children)) { + for (const childRecord of record.children) { + this.addRouteRecord(childRecord, matcher) + } + } + + this.matchers.push(matcher) } /** @@ -88,8 +138,16 @@ export class RouterMatcher { } } - // TODO: build up the array with children based on current location - const matched = [matcher.record] + // put the parents of the matched record first + const matched: MatcherLocationNormalized['matched'] = [matcher.record] + let parentMatcher: RouteMatcher | void = matcher.parent + while (parentMatcher) { + // reversed order so parents are at the beginning + // TODO: should be doable by typing RouteMatcher in a different way + if ('redirect' in parentMatcher.record) throw new Error('TODO') + matched.unshift(parentMatcher.record) + parentMatcher = parentMatcher.parent + } return { name: matcher.record.name, diff --git a/src/types/index.ts b/src/types/index.ts index 0aa8e254..cbee6c39 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -109,6 +109,7 @@ export interface RouteRecordRedirect extends RouteRecordCommon { interface RouteRecordSingleView extends RouteRecordCommon { component: RouteComponent | Lazy + children?: RouteRecord[] } interface RouteRecordMultipleViews extends RouteRecordCommon {