Skip to content

Commit da102e5

Browse files
perf(react-router): improve location building performance (#2516)
1 parent 63562e8 commit da102e5

1 file changed

Lines changed: 86 additions & 70 deletions

File tree

packages/react-router/src/router.ts

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,11 @@ export interface BuildNextOptions {
495495
_fromLocation?: ParsedLocation
496496
}
497497

498+
export interface MatchedRoutesResult {
499+
matchedRoutes: Array<AnyRoute>
500+
routeParams: Record<string, string>
501+
}
502+
498503
export interface DehydratedRouterState {
499504
dehydratedMatches: Array<DehydratedRouteMatch>
500505
}
@@ -602,6 +607,7 @@ type MatchRoutesOpts = {
602607
preload?: boolean
603608
throwOnError?: boolean
604609
_buildLocation?: boolean
610+
dest?: BuildNextOptions
605611
}
606612

607613
export class Router<
@@ -1013,33 +1019,10 @@ export class Router<
10131019
next: ParsedLocation,
10141020
opts?: MatchRoutesOpts,
10151021
): Array<AnyRouteMatch> {
1016-
let routeParams: Record<string, string> = {}
1017-
1018-
const foundRoute = this.flatRoutes.find((route) => {
1019-
const matchedParams = matchPathname(
1020-
this.basepath,
1021-
trimPathRight(next.pathname),
1022-
{
1023-
to: route.fullPath,
1024-
caseSensitive:
1025-
route.options.caseSensitive ?? this.options.caseSensitive,
1026-
fuzzy: true,
1027-
},
1028-
)
1029-
1030-
if (matchedParams) {
1031-
routeParams = matchedParams
1032-
return true
1033-
}
1034-
1035-
return false
1036-
})
1037-
1038-
let routeCursor: AnyRoute =
1039-
foundRoute || (this.routesById as any)[rootRouteId]
1040-
1041-
const matchedRoutes: Array<AnyRoute> = [routeCursor]
1042-
1022+
const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
1023+
next,
1024+
opts?.dest,
1025+
)
10431026
let isGlobalNotFound = false
10441027

10451028
// Check to see if the route needs a 404 entry
@@ -1059,11 +1042,6 @@ export class Router<
10591042
}
10601043
}
10611044

1062-
while (routeCursor.parentRoute) {
1063-
routeCursor = routeCursor.parentRoute
1064-
matchedRoutes.unshift(routeCursor)
1065-
}
1066-
10671045
const globalNotFoundRouteId = (() => {
10681046
if (!isGlobalNotFound) {
10691047
return undefined
@@ -1307,6 +1285,49 @@ export class Router<
13071285
return matches as any
13081286
}
13091287

1288+
getMatchedRoutes = (next: ParsedLocation, dest?: BuildNextOptions) => {
1289+
let routeParams: Record<string, string> = {}
1290+
const trimmedPath = trimPathRight(next.pathname)
1291+
const getMatchedParams = (route: AnyRoute) => {
1292+
const result = matchPathname(this.basepath, trimmedPath, {
1293+
to: route.fullPath,
1294+
caseSensitive:
1295+
route.options.caseSensitive ?? this.options.caseSensitive,
1296+
fuzzy: true,
1297+
})
1298+
return result
1299+
}
1300+
1301+
let foundRoute: AnyRoute | undefined =
1302+
dest?.to !== undefined ? this.routesByPath[dest.to!] : undefined
1303+
if (foundRoute) {
1304+
routeParams = getMatchedParams(foundRoute)!
1305+
} else {
1306+
foundRoute = this.flatRoutes.find((route) => {
1307+
const matchedParams = getMatchedParams(route)
1308+
1309+
if (matchedParams) {
1310+
routeParams = matchedParams
1311+
return true
1312+
}
1313+
1314+
return false
1315+
})
1316+
}
1317+
1318+
let routeCursor: AnyRoute =
1319+
foundRoute || (this.routesById as any)[rootRouteId]
1320+
1321+
const matchedRoutes: Array<AnyRoute> = [routeCursor]
1322+
1323+
while (routeCursor.parentRoute) {
1324+
routeCursor = routeCursor.parentRoute
1325+
matchedRoutes.unshift(routeCursor)
1326+
}
1327+
1328+
return { matchedRoutes, routeParams, foundRoute }
1329+
}
1330+
13101331
cancelMatch = (id: string) => {
13111332
const match = this.getMatch(id)
13121333

@@ -1327,7 +1348,7 @@ export class Router<
13271348
dest: BuildNextOptions & {
13281349
unmaskOnReload?: boolean
13291350
} = {},
1330-
matches?: Array<MakeRouteMatch<TRouteTree>>,
1351+
matchedRoutesResult?: MatchedRoutesResult,
13311352
): ParsedLocation => {
13321353
const fromMatches = dest._fromLocation
13331354
? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
@@ -1355,22 +1376,30 @@ export class Router<
13551376
? last(this.state.pendingMatches)?.search
13561377
: last(fromMatches)?.search || this.latestLocation.search
13571378

1358-
const stayingMatches = matches?.filter((d) =>
1359-
fromMatches.find((e) => e.routeId === d.routeId),
1379+
const stayingMatches = matchedRoutesResult?.matchedRoutes.filter((d) =>
1380+
fromMatches.find((e) => e.routeId === d.id),
13601381
)
13611382

1362-
const fromRouteByFromPathRouteId =
1363-
this.routesById[
1364-
stayingMatches?.find((d) => d.pathname === fromPath)
1365-
?.routeId as keyof this['routesById']
1366-
]
1367-
1368-
let pathname = dest.to
1369-
? this.resolvePathWithBase(fromPath, `${dest.to}`)
1370-
: this.resolvePathWithBase(
1371-
fromPath,
1372-
fromRouteByFromPathRouteId?.to ?? fromPath,
1373-
)
1383+
let pathname: string
1384+
if (dest.to) {
1385+
pathname = this.resolvePathWithBase(fromPath, `${dest.to}`)
1386+
} else {
1387+
const fromRouteByFromPathRouteId =
1388+
this.routesById[
1389+
stayingMatches?.find((route) => {
1390+
const interpolatedPath = interpolatePath({
1391+
path: route.fullPath,
1392+
params: matchedRoutesResult?.routeParams ?? {},
1393+
})
1394+
const pathname = joinPaths([this.basepath, interpolatedPath])
1395+
return pathname === fromPath
1396+
})?.id as keyof this['routesById']
1397+
]
1398+
pathname = this.resolvePathWithBase(
1399+
fromPath,
1400+
fromRouteByFromPathRouteId?.to ?? fromPath,
1401+
)
1402+
}
13741403

13751404
const prevParams = { ...last(fromMatches)?.params }
13761405

@@ -1380,11 +1409,10 @@ export class Router<
13801409
: { ...prevParams, ...functionalUpdate(dest.params, prevParams) }
13811410

13821411
if (Object.keys(nextParams).length > 0) {
1383-
matches
1384-
?.map((d) => {
1385-
const route = this.looseRoutesById[d.routeId]
1412+
matchedRoutesResult?.matchedRoutes
1413+
.map((route) => {
13861414
return (
1387-
route?.options.params?.stringify ?? route!.options.stringifyParams
1415+
route.options.params?.stringify ?? route.options.stringifyParams
13881416
)
13891417
})
13901418
.filter(Boolean)
@@ -1402,21 +1430,13 @@ export class Router<
14021430

14031431
const preSearchFilters =
14041432
stayingMatches
1405-
?.map(
1406-
(match) =>
1407-
this.looseRoutesById[match.routeId]!.options.preSearchFilters ??
1408-
[],
1409-
)
1433+
?.map((route) => route.options.preSearchFilters ?? [])
14101434
.flat()
14111435
.filter(Boolean) ?? []
14121436

14131437
const postSearchFilters =
14141438
stayingMatches
1415-
?.map(
1416-
(match) =>
1417-
this.looseRoutesById[match.routeId]!.options.postSearchFilters ??
1418-
[],
1419-
)
1439+
?.map((route) => route.options.postSearchFilters ?? [])
14201440
.flat()
14211441
.filter(Boolean) ?? []
14221442

@@ -1509,17 +1529,12 @@ export class Router<
15091529
}
15101530
}
15111531

1512-
const nextMatches = this.matchRoutes(next, { _buildLocation: true })
1513-
const maskedMatches = maskedNext
1514-
? this.matchRoutes(maskedNext, { _buildLocation: true })
1515-
: undefined
1516-
const maskedFinal = maskedNext
1517-
? build(maskedDest, maskedMatches)
1518-
: undefined
1519-
1532+
const nextMatches = this.getMatchedRoutes(next, dest)
15201533
const final = build(dest, nextMatches)
15211534

1522-
if (maskedFinal) {
1535+
if (maskedNext) {
1536+
const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest)
1537+
const maskedFinal = build(maskedDest, maskedMatches)
15231538
final.maskedLocation = maskedFinal
15241539
}
15251540

@@ -2497,6 +2512,7 @@ export class Router<
24972512
let matches = this.matchRoutes(next, {
24982513
throwOnError: true,
24992514
preload: true,
2515+
dest: opts,
25002516
})
25012517

25022518
const loadedMatchIds = Object.fromEntries(

0 commit comments

Comments
 (0)