Skip to content

Commit db5a85c

Browse files
committed
feat: handle children
1 parent 60564fe commit db5a85c

File tree

7 files changed

+208
-82
lines changed

7 files changed

+208
-82
lines changed

packages/router/__tests__/matcher/pathRanking.spec.ts

-10
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,9 @@ describe('Path ranking', () => {
1313
return comparePathParserScore(
1414
{
1515
score: a,
16-
re: /a/,
17-
// @ts-expect-error
18-
stringify: v => v,
19-
// @ts-expect-error
20-
parse: v => v,
21-
keys: [],
2216
},
2317
{
2418
score: b,
25-
re: /a/,
26-
stringify: v => v,
27-
parse: v => v,
28-
keys: [],
2919
}
3020
)
3121
}

packages/router/src/experimental/router.ts

+25-19
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import {
2323
type RouterHistory,
2424
} from '../history/common'
2525
import type { PathParserOptions } from '../matcher'
26-
import type {
27-
NEW_LocationResolved,
28-
NEW_MatcherRecord,
29-
NEW_MatcherRecordRaw,
30-
NEW_RouterResolver,
26+
import {
27+
type NEW_MatcherRecordBase,
28+
type NEW_LocationResolved,
29+
type NEW_MatcherRecord,
30+
type NEW_MatcherRecordRaw,
31+
type NEW_RouterResolver,
3132
} from '../new-route-resolver/resolver'
3233
import {
3334
parseQuery as originalParseQuery,
@@ -194,7 +195,7 @@ export interface EXPERIMENTAL_RouterOptions<
194195
* Matcher to use to resolve routes.
195196
* @experimental
196197
*/
197-
matcher: NEW_RouterResolver<NEW_MatcherRecordRaw, TMatcherRecord>
198+
resolver: NEW_RouterResolver<NEW_MatcherRecordRaw, TMatcherRecord>
198199
}
199200

200201
/**
@@ -411,14 +412,18 @@ export interface EXPERIMENTAL_RouteRecordRaw extends NEW_MatcherRecordRaw {
411412
component?: unknown
412413

413414
redirect?: unknown
415+
score: Array<number[]>
414416
}
415417

416418
// TODO: is it worth to have 2 types for the undefined values?
417-
export interface EXPERIMENTAL_RouteRecordNormalized extends NEW_MatcherRecord {
419+
export interface EXPERIMENTAL_RouteRecordNormalized
420+
extends NEW_MatcherRecordBase<EXPERIMENTAL_RouteRecordNormalized> {
418421
/**
419422
* Arbitrary data attached to the record.
420423
*/
421424
meta: RouteMeta
425+
group?: boolean
426+
score: Array<number[]>
422427
}
423428

424429
function normalizeRouteRecord(
@@ -429,6 +434,7 @@ function normalizeRouteRecord(
429434
name: __DEV__ ? Symbol('anonymous route record') : Symbol(),
430435
meta: {},
431436
...record,
437+
children: (record.children || []).map(normalizeRouteRecord),
432438
}
433439
}
434440

@@ -439,7 +445,7 @@ export function experimental_createRouter(
439445
EXPERIMENTAL_RouteRecordNormalized
440446
> {
441447
const {
442-
matcher,
448+
resolver,
443449
parseQuery = originalParseQuery,
444450
stringifyQuery = originalStringifyQuery,
445451
history: routerHistory,
@@ -470,11 +476,11 @@ export function experimental_createRouter(
470476
| EXPERIMENTAL_RouteRecordRaw,
471477
route?: EXPERIMENTAL_RouteRecordRaw
472478
) {
473-
let parent: Parameters<(typeof matcher)['addMatcher']>[1] | undefined
479+
let parent: Parameters<(typeof resolver)['addMatcher']>[1] | undefined
474480
let rawRecord: EXPERIMENTAL_RouteRecordRaw
475481

476482
if (isRouteName(parentOrRoute)) {
477-
parent = matcher.getMatcher(parentOrRoute)
483+
parent = resolver.getMatcher(parentOrRoute)
478484
if (__DEV__ && !parent) {
479485
warn(
480486
`Parent route "${String(
@@ -488,31 +494,31 @@ export function experimental_createRouter(
488494
rawRecord = parentOrRoute
489495
}
490496

491-
const addedRecord = matcher.addMatcher(
497+
const addedRecord = resolver.addMatcher(
492498
normalizeRouteRecord(rawRecord),
493499
parent
494500
)
495501

496502
return () => {
497-
matcher.removeMatcher(addedRecord)
503+
resolver.removeMatcher(addedRecord)
498504
}
499505
}
500506

501507
function removeRoute(name: NonNullable<RouteRecordNameGeneric>) {
502-
const recordMatcher = matcher.getMatcher(name)
508+
const recordMatcher = resolver.getMatcher(name)
503509
if (recordMatcher) {
504-
matcher.removeMatcher(recordMatcher)
510+
resolver.removeMatcher(recordMatcher)
505511
} else if (__DEV__) {
506512
warn(`Cannot remove non-existent route "${String(name)}"`)
507513
}
508514
}
509515

510516
function getRoutes() {
511-
return matcher.getMatchers()
517+
return resolver.getMatchers()
512518
}
513519

514520
function hasRoute(name: NonNullable<RouteRecordNameGeneric>): boolean {
515-
return !!matcher.getMatcher(name)
521+
return !!resolver.getMatcher(name)
516522
}
517523

518524
function locationAsObject(
@@ -567,7 +573,7 @@ export function experimental_createRouter(
567573
// rawLocation.params = targetParams
568574
// }
569575

570-
const matchedRoute = matcher.resolve(
576+
const matchedRoute = resolver.resolve(
571577
// incompatible types
572578
rawLocation as any,
573579
// incompatible `matched` requires casting
@@ -1226,7 +1232,7 @@ export function experimental_createRouter(
12261232

12271233
addRoute,
12281234
removeRoute,
1229-
clearRoutes: matcher.clearMatchers,
1235+
clearRoutes: resolver.clearMatchers,
12301236
hasRoute,
12311237
getRoutes,
12321238
resolve,
@@ -1307,7 +1313,7 @@ export function experimental_createRouter(
13071313
// TODO: this probably needs to be updated so it can be used by vue-termui
13081314
if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
13091315
// @ts-expect-error: FIXME: refactor with new types once it's possible
1310-
addDevtools(app, router, matcher)
1316+
addDevtools(app, router, resolver)
13111317
}
13121318
},
13131319
}

packages/router/src/matcher/pathParserRanker.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,10 @@ function compareScoreArray(a: number[], b: number[]): number {
331331
* @param b - second PathParser
332332
* @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
333333
*/
334-
export function comparePathParserScore(a: PathParser, b: PathParser): number {
334+
export function comparePathParserScore(
335+
a: Pick<PathParser, 'score'>,
336+
b: Pick<PathParser, 'score'>
337+
): number {
335338
let i = 0
336339
const aScore = a.score
337340
const bScore = b.score

packages/router/src/new-route-resolver/matcher-resolve.spec.ts

+42-21
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,25 @@ function isMatchable(record: RouteRecordRaw): boolean {
4242
)
4343
}
4444

45+
function joinPaths(a: string | undefined, b: string) {
46+
if (a?.endsWith('/')) {
47+
return a + b
48+
}
49+
return a + '/' + b
50+
}
51+
4552
function compileRouteRecord(
4653
record: RouteRecordRaw,
4754
parentRecord?: RouteRecordRaw
4855
): NEW_MatcherRecordRaw {
4956
// we adapt the path to ensure they are absolute
5057
// TODO: aliases? they could be handled directly in the path matcher
58+
if (!parentRecord && !record.path.startsWith('/')) {
59+
throw new Error(`Record without parent must have an absolute path`)
60+
}
5161
const path = record.path.startsWith('/')
5262
? record.path
53-
: (parentRecord?.path || '') + record.path
63+
: joinPaths(parentRecord?.path, record.path)
5464
record.path = path
5565
const parser = tokensToParser(
5666
tokenizePath(record.path),
@@ -62,10 +72,12 @@ function compileRouteRecord(
6272
return {
6373
group: !isMatchable(record),
6474
name: record.name,
75+
score: parser.score,
6576

6677
path: {
6778
match(value) {
6879
const params = parser.parse(value)
80+
// console.log('🌟', parser.re, value, params)
6981
if (params) {
7082
return params
7183
}
@@ -181,20 +193,21 @@ describe('RouterMatcher.resolve', () => {
181193
: matcher.resolve(
182194
// FIXME: is this a ts bug?
183195
// @ts-expect-error
184-
typeof fromLocation === 'string'
185-
? { path: fromLocation }
186-
: fromLocation
196+
fromLocation
187197
)
188198

199+
// console.log(matcher.getMatchers())
189200
// console.log({ toLocation, resolved, expectedLocation, resolvedFrom })
190201

191202
const result = matcher.resolve(
192203
// FIXME: should work now
193204
// @ts-expect-error
194-
typeof toLocation === 'string' ? { path: toLocation } : toLocation,
205+
toLocation,
195206
resolvedFrom === START_LOCATION ? undefined : resolvedFrom
196207
)
197208

209+
// console.log(result)
210+
198211
if (
199212
expectedLocation.name === undefined ||
200213
expectedLocation.name !== NO_MATCH_LOCATION.name
@@ -479,7 +492,7 @@ describe('RouterMatcher.resolve', () => {
479492
// TODO: not sure where this warning should appear now
480493
it.todo('warns if a path isn not absolute', () => {
481494
const matcher = createCompiledMatcher([
482-
{ path: new MatcherPatternPathStatic('/') },
495+
{ path: new MatcherPatternPathStatic('/'), score: [[80]] },
483496
])
484497
matcher.resolve({ path: 'two' }, matcher.resolve({ path: '/' }))
485498
expect('received "two"').toHaveBeenWarned()
@@ -1169,34 +1182,42 @@ describe('RouterMatcher.resolve', () => {
11691182
})
11701183
})
11711184

1172-
describe.skip('children', () => {
1173-
const ChildA = { path: 'a', name: 'child-a', components }
1174-
const ChildB = { path: 'b', name: 'child-b', components }
1175-
const ChildC = { path: 'c', name: 'child-c', components }
1176-
const ChildD = { path: '/absolute', name: 'absolute', components }
1177-
const ChildWithParam = { path: ':p', name: 'child-params', components }
1178-
const NestedChildWithParam = {
1185+
describe('children', () => {
1186+
const ChildA: RouteRecordRaw = { path: 'a', name: 'child-a', components }
1187+
const ChildB: RouteRecordRaw = { path: 'b', name: 'child-b', components }
1188+
const ChildC: RouteRecordRaw = { path: 'c', name: 'child-c', components }
1189+
const ChildD: RouteRecordRaw = {
1190+
path: '/absolute',
1191+
name: 'absolute',
1192+
components,
1193+
}
1194+
const ChildWithParam: RouteRecordRaw = {
1195+
path: ':p',
1196+
name: 'child-params',
1197+
components,
1198+
}
1199+
const NestedChildWithParam: RouteRecordRaw = {
11791200
...ChildWithParam,
11801201
name: 'nested-child-params',
11811202
}
1182-
const NestedChildA = { ...ChildA, name: 'nested-child-a' }
1183-
const NestedChildB = { ...ChildB, name: 'nested-child-b' }
1184-
const NestedChildC = { ...ChildC, name: 'nested-child-c' }
1185-
const Nested = {
1203+
const NestedChildA: RouteRecordRaw = { ...ChildA, name: 'nested-child-a' }
1204+
const NestedChildB: RouteRecordRaw = { ...ChildB, name: 'nested-child-b' }
1205+
const NestedChildC: RouteRecordRaw = { ...ChildC, name: 'nested-child-c' }
1206+
const Nested: RouteRecordRaw = {
11861207
path: 'nested',
11871208
name: 'nested',
11881209
components,
11891210
children: [NestedChildA, NestedChildB, NestedChildC],
11901211
}
1191-
const NestedWithParam = {
1212+
const NestedWithParam: RouteRecordRaw = {
11921213
path: 'nested/:n',
11931214
name: 'nested',
11941215
components,
11951216
children: [NestedChildWithParam],
11961217
}
11971218

11981219
it('resolves children', () => {
1199-
const Foo = {
1220+
const Foo: RouteRecordRaw = {
12001221
path: '/foo',
12011222
name: 'Foo',
12021223
components,
@@ -1216,8 +1237,8 @@ describe('RouterMatcher.resolve', () => {
12161237
})
12171238

12181239
it('resolves children with empty paths', () => {
1219-
const Nested = { path: '', name: 'nested', components }
1220-
const Foo = {
1240+
const Nested: RouteRecordRaw = { path: '', name: 'nested', components }
1241+
const Foo: RouteRecordRaw = {
12211242
path: '/foo',
12221243
name: 'Foo',
12231244
components,

packages/router/src/new-route-resolver/matchers/test-utils.ts

+14
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,23 @@ export const ANY_HASH_PATTERN_MATCHER: MatcherPatternParams_Base<
6868
export const EMPTY_PATH_ROUTE = {
6969
name: 'no params',
7070
path: EMPTY_PATH_PATTERN_MATCHER,
71+
score: [[80]],
72+
children: [],
73+
parent: undefined,
74+
} satisfies NEW_MatcherRecord
75+
76+
export const ANY_PATH_ROUTE = {
77+
name: 'any path',
78+
path: ANY_PATH_PATTERN_MATCHER,
79+
score: [[-10]],
80+
children: [],
81+
parent: undefined,
7182
} satisfies NEW_MatcherRecord
7283

7384
export const USER_ID_ROUTE = {
7485
name: 'user-id',
7586
path: USER_ID_PATH_PATTERN_MATCHER,
87+
score: [[80], [70]],
88+
children: [],
89+
parent: undefined,
7690
} satisfies NEW_MatcherRecord

0 commit comments

Comments
 (0)