@tanstack/react-router 0.0.1-beta.273 → 0.0.1-beta.275
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/RouterProvider.js +9 -6
- package/build/cjs/RouterProvider.js.map +1 -1
- package/build/cjs/fileRoute.js.map +1 -1
- package/build/cjs/index.js +4 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/redirects.js.map +1 -1
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/router.js +163 -105
- package/build/cjs/router.js.map +1 -1
- package/build/esm/index.js +170 -108
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +353 -353
- package/build/types/Matches.d.ts +4 -3
- package/build/types/RouterProvider.d.ts +1 -1
- package/build/types/fileRoute.d.ts +2 -2
- package/build/types/redirects.d.ts +1 -1
- package/build/types/route.d.ts +24 -24
- package/build/types/routeInfo.d.ts +1 -1
- package/build/types/router.d.ts +3 -1
- package/build/umd/index.development.js +172 -111
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/Matches.tsx +4 -3
- package/src/RouterProvider.tsx +11 -5
- package/src/fileRoute.ts +3 -0
- package/src/redirects.ts +1 -1
- package/src/route.ts +31 -20
- package/src/routeInfo.ts +2 -0
- package/src/router.ts +211 -133
package/src/router.ts
CHANGED
|
@@ -110,6 +110,7 @@ export interface RouterOptions<
|
|
|
110
110
|
defaultPendingComponent?: RouteComponent
|
|
111
111
|
defaultPendingMs?: number
|
|
112
112
|
defaultPendingMinMs?: number
|
|
113
|
+
defaultPreloadMaxAge?: number
|
|
113
114
|
caseSensitive?: boolean
|
|
114
115
|
routeTree?: TRouteTree
|
|
115
116
|
basepath?: string
|
|
@@ -129,6 +130,7 @@ export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
|
129
130
|
isTransitioning: boolean
|
|
130
131
|
matches: RouteMatch<TRouteTree>[]
|
|
131
132
|
pendingMatches?: RouteMatch<TRouteTree>[]
|
|
133
|
+
preloadMatches: RouteMatch<TRouteTree>[]
|
|
132
134
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
133
135
|
resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
134
136
|
lastUpdated: number
|
|
@@ -159,7 +161,7 @@ export interface DehydratedRouterState {
|
|
|
159
161
|
|
|
160
162
|
export type DehydratedRouteMatch = Pick<
|
|
161
163
|
RouteMatch,
|
|
162
|
-
'
|
|
164
|
+
'id' | 'status' | 'updatedAt'
|
|
163
165
|
>
|
|
164
166
|
|
|
165
167
|
export interface DehydratedRouter {
|
|
@@ -615,13 +617,21 @@ export class Router<
|
|
|
615
617
|
}
|
|
616
618
|
})()
|
|
617
619
|
|
|
620
|
+
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
621
|
+
// deps that the route's loader function might need to run. We need to do this
|
|
622
|
+
// before we create the match so that we can pass the deps to the route's
|
|
623
|
+
// potential key function which is used to uniquely identify the route match in state
|
|
624
|
+
|
|
625
|
+
const loaderDeps =
|
|
626
|
+
route.options.loaderDeps?.({
|
|
627
|
+
search: preMatchSearch,
|
|
628
|
+
}) ?? ''
|
|
629
|
+
|
|
630
|
+
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
|
|
631
|
+
|
|
618
632
|
const interpolatedPath = interpolatePath(route.fullPath, routeParams)
|
|
619
633
|
const matchId =
|
|
620
|
-
interpolatePath(route.id, routeParams, true) +
|
|
621
|
-
(route.options.key?.({
|
|
622
|
-
search: preMatchSearch,
|
|
623
|
-
location: this.state.location,
|
|
624
|
-
}) ?? '')
|
|
634
|
+
interpolatePath(route.id, routeParams, true) + loaderDepsHash
|
|
625
635
|
|
|
626
636
|
// Waste not, want not. If we already have a match for this route,
|
|
627
637
|
// reuse it. This is important for layout routes, which might stick
|
|
@@ -658,8 +668,9 @@ export class Router<
|
|
|
658
668
|
context: undefined!,
|
|
659
669
|
abortController: new AbortController(),
|
|
660
670
|
shouldReloadDeps: undefined,
|
|
661
|
-
|
|
671
|
+
fetchCount: 0,
|
|
662
672
|
cause,
|
|
673
|
+
loaderDeps,
|
|
663
674
|
}
|
|
664
675
|
|
|
665
676
|
// Regardless of whether we're reusing an existing match or creating
|
|
@@ -976,10 +987,20 @@ export class Router<
|
|
|
976
987
|
let latestPromise
|
|
977
988
|
let firstBadMatchIndex: number | undefined
|
|
978
989
|
|
|
979
|
-
const
|
|
990
|
+
const updateMatch = (match: AnyRouteMatch) => {
|
|
991
|
+
const isPreload = this.state.preloadMatches.find((d) => d.id === match.id)
|
|
992
|
+
const isPending = this.state.pendingMatches?.find(
|
|
993
|
+
(d) => d.id === match.id,
|
|
994
|
+
)
|
|
995
|
+
const matchesKey = isPreload
|
|
996
|
+
? 'preloadMatches'
|
|
997
|
+
: isPending
|
|
998
|
+
? 'pendingMatches'
|
|
999
|
+
: 'matches'
|
|
1000
|
+
|
|
980
1001
|
this.__store.setState((s) => ({
|
|
981
1002
|
...s,
|
|
982
|
-
|
|
1003
|
+
[matchesKey]: s[matchesKey]?.map((d) =>
|
|
983
1004
|
d.id === match.id ? match : d,
|
|
984
1005
|
),
|
|
985
1006
|
}))
|
|
@@ -1043,7 +1064,7 @@ export class Router<
|
|
|
1043
1064
|
navigate: (opts) =>
|
|
1044
1065
|
this.navigate({ ...opts, from: match.pathname } as any),
|
|
1045
1066
|
buildLocation: this.buildLocation,
|
|
1046
|
-
cause: match.cause,
|
|
1067
|
+
cause: preload ? 'preload' : match.cause,
|
|
1047
1068
|
})) ?? ({} as any)
|
|
1048
1069
|
|
|
1049
1070
|
if (isRedirect(beforeLoadContext)) {
|
|
@@ -1083,7 +1104,7 @@ export class Router<
|
|
|
1083
1104
|
|
|
1084
1105
|
validResolvedMatches.forEach((match, index) => {
|
|
1085
1106
|
matchPromises.push(
|
|
1086
|
-
(async () => {
|
|
1107
|
+
new Promise<void>(async (resolve) => {
|
|
1087
1108
|
const parentMatchPromise = matchPromises[index - 1]
|
|
1088
1109
|
const route = this.looseRoutesById[match.routeId]!
|
|
1089
1110
|
|
|
@@ -1101,126 +1122,129 @@ export class Router<
|
|
|
1101
1122
|
|
|
1102
1123
|
matches[index] = match = {
|
|
1103
1124
|
...match,
|
|
1104
|
-
fetchedAt: Date.now(),
|
|
1105
1125
|
showPending: false,
|
|
1106
1126
|
}
|
|
1107
1127
|
|
|
1128
|
+
let didShowPending = false
|
|
1108
1129
|
const pendingMs =
|
|
1109
1130
|
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
1131
|
+
const pendingMinMs =
|
|
1132
|
+
route.options.pendingMinMs ?? this.options.defaultPendingMinMs
|
|
1133
|
+
const shouldPending =
|
|
1114
1134
|
!preload &&
|
|
1115
1135
|
pendingMs &&
|
|
1116
1136
|
(route.options.pendingComponent ??
|
|
1117
1137
|
this.options.defaultPendingComponent)
|
|
1118
|
-
) {
|
|
1119
|
-
pendingPromise = new Promise((r) => setTimeout(r, pendingMs))
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
if (match.isFetching) {
|
|
1123
|
-
loadPromise = getRouteMatch(this.state, match.id)?.loadPromise
|
|
1124
|
-
} else {
|
|
1125
|
-
const loaderContext: LoaderFnContext = {
|
|
1126
|
-
params: match.params,
|
|
1127
|
-
search: match.search,
|
|
1128
|
-
preload: !!preload,
|
|
1129
|
-
parentMatchPromise,
|
|
1130
|
-
abortController: match.abortController,
|
|
1131
|
-
context: match.context,
|
|
1132
|
-
location: this.state.location,
|
|
1133
|
-
navigate: (opts) =>
|
|
1134
|
-
this.navigate({ ...opts, from: match.pathname } as any),
|
|
1135
|
-
cause: match.cause,
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
// Default to reloading the route all the time
|
|
1139
|
-
let shouldReload = true
|
|
1140
1138
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1139
|
+
const fetch = async () => {
|
|
1140
|
+
if (match.isFetching) {
|
|
1141
|
+
loadPromise = getRouteMatch(this.state, match.id)?.loadPromise
|
|
1142
|
+
} else {
|
|
1143
|
+
const loaderContext: LoaderFnContext = {
|
|
1144
|
+
params: match.params,
|
|
1145
|
+
deps: match.loaderDeps,
|
|
1146
|
+
preload: !!preload,
|
|
1147
|
+
parentMatchPromise,
|
|
1148
|
+
abortController: match.abortController,
|
|
1149
|
+
context: match.context,
|
|
1150
|
+
location: this.state.location,
|
|
1151
|
+
navigate: (opts) =>
|
|
1152
|
+
this.navigate({ ...opts, from: match.pathname } as any),
|
|
1153
|
+
cause: preload ? 'preload' : match.cause,
|
|
1154
|
+
}
|
|
1145
1155
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1156
|
+
// Default to reloading the route all the time
|
|
1157
|
+
let shouldLoad = true
|
|
1158
|
+
|
|
1159
|
+
const shouldReloadFn = route.options.shouldReload
|
|
1160
|
+
|
|
1161
|
+
let shouldReloadDeps =
|
|
1162
|
+
typeof shouldReloadFn === 'function'
|
|
1163
|
+
? shouldReloadFn(loaderContext)
|
|
1164
|
+
: !!(shouldReloadFn ?? true)
|
|
1165
|
+
|
|
1166
|
+
const compareDeps = () => {
|
|
1167
|
+
if (typeof shouldReloadDeps === 'object') {
|
|
1168
|
+
// compare the deps to see if they've changed
|
|
1169
|
+
shouldLoad = !deepEqual(
|
|
1170
|
+
shouldReloadDeps,
|
|
1171
|
+
match.shouldReloadDeps,
|
|
1172
|
+
)
|
|
1173
|
+
} else {
|
|
1174
|
+
shouldLoad = !!shouldReloadDeps
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1155
1177
|
|
|
1156
|
-
|
|
1178
|
+
// If it's the first preload, or the route is entering, or we're
|
|
1179
|
+
// invalidating, we definitely need to load the route
|
|
1180
|
+
if (invalidate) {
|
|
1181
|
+
// Change nothing, we need to load the route
|
|
1182
|
+
} else if (preload) {
|
|
1183
|
+
if (!match.fetchCount) {
|
|
1184
|
+
// Change nothing, we need to preload the route
|
|
1185
|
+
} else {
|
|
1186
|
+
compareDeps()
|
|
1187
|
+
}
|
|
1188
|
+
} else if (match.cause === 'enter') {
|
|
1189
|
+
if (!match.fetchCount) {
|
|
1190
|
+
// Change nothing, we 100% need to load the route
|
|
1191
|
+
} else {
|
|
1192
|
+
compareDeps()
|
|
1193
|
+
}
|
|
1157
1194
|
} else {
|
|
1158
|
-
|
|
1195
|
+
compareDeps()
|
|
1159
1196
|
}
|
|
1160
|
-
}
|
|
1161
1197
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
} else {
|
|
1168
|
-
// Otherwise, load the route
|
|
1169
|
-
matches[index] = match = {
|
|
1170
|
-
...match,
|
|
1171
|
-
isFetching: true,
|
|
1198
|
+
if (typeof shouldReloadDeps === 'object') {
|
|
1199
|
+
matches[index] = match = {
|
|
1200
|
+
...match,
|
|
1201
|
+
shouldReloadDeps,
|
|
1202
|
+
}
|
|
1172
1203
|
}
|
|
1173
1204
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const component = route.options[type]
|
|
1177
|
-
|
|
1178
|
-
if ((component as any)?.preload) {
|
|
1179
|
-
await (component as any).preload()
|
|
1180
|
-
}
|
|
1181
|
-
}),
|
|
1182
|
-
)
|
|
1183
|
-
|
|
1184
|
-
const loaderPromise = route.options.loader?.(loaderContext)
|
|
1205
|
+
// If the user doesn't want the route to reload, just
|
|
1206
|
+
// resolve with the existing loader data
|
|
1185
1207
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1208
|
+
if (!shouldLoad) {
|
|
1209
|
+
loadPromise = Promise.resolve(match.loaderData)
|
|
1210
|
+
} else {
|
|
1211
|
+
if (match.fetchCount && match.status === 'success') {
|
|
1212
|
+
resolve()
|
|
1213
|
+
}
|
|
1192
1214
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1215
|
+
// Otherwise, load the route
|
|
1216
|
+
matches[index] = match = {
|
|
1217
|
+
...match,
|
|
1218
|
+
isFetching: true,
|
|
1219
|
+
fetchCount: match.fetchCount + 1,
|
|
1220
|
+
}
|
|
1197
1221
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1222
|
+
const componentsPromise = Promise.all(
|
|
1223
|
+
componentTypes.map(async (type) => {
|
|
1224
|
+
const component = route.options[type]
|
|
1201
1225
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1226
|
+
if ((component as any)?.preload) {
|
|
1227
|
+
await (component as any).preload()
|
|
1228
|
+
}
|
|
1229
|
+
}),
|
|
1230
|
+
)
|
|
1205
1231
|
|
|
1206
|
-
|
|
1207
|
-
// If the route has a pending component and a pendingMs option,
|
|
1208
|
-
// forcefully show the pending component
|
|
1209
|
-
if (pendingPromise) {
|
|
1210
|
-
pendingPromise.then(() => {
|
|
1211
|
-
if ((latestPromise = checkLatest())) return
|
|
1232
|
+
const loaderPromise = route.options.loader?.(loaderContext)
|
|
1212
1233
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1234
|
+
loadPromise = Promise.all([
|
|
1235
|
+
componentsPromise,
|
|
1236
|
+
loaderPromise,
|
|
1237
|
+
]).then((d) => d[1])
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1218
1240
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1241
|
+
matches[index] = match = {
|
|
1242
|
+
...match,
|
|
1243
|
+
loadPromise,
|
|
1222
1244
|
}
|
|
1223
1245
|
|
|
1246
|
+
updateMatch(match)
|
|
1247
|
+
|
|
1224
1248
|
try {
|
|
1225
1249
|
const loaderData = await loadPromise
|
|
1226
1250
|
if ((latestPromise = checkLatest())) return await latestPromise
|
|
@@ -1267,22 +1291,44 @@ export class Router<
|
|
|
1267
1291
|
// we already moved the pendingMatches to the matches
|
|
1268
1292
|
// state, so we need to update that specific match
|
|
1269
1293
|
if (didShowPending && pendingMinMs && match.showPending) {
|
|
1270
|
-
|
|
1271
|
-
...s,
|
|
1272
|
-
matches: s.matches?.map((d) =>
|
|
1273
|
-
d.id === match.id ? match : d,
|
|
1274
|
-
),
|
|
1275
|
-
}))
|
|
1294
|
+
updateMatch(match)
|
|
1276
1295
|
}
|
|
1277
1296
|
}
|
|
1278
1297
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1298
|
+
updateMatch(match)
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (match.fetchCount && match.status === 'success') {
|
|
1302
|
+
// Background Fetching
|
|
1303
|
+
fetch()
|
|
1304
|
+
} else {
|
|
1305
|
+
// Critical Fetching
|
|
1306
|
+
|
|
1307
|
+
// If we need to potentially show the pending component,
|
|
1308
|
+
// start a timer to show it after the pendingMs
|
|
1309
|
+
if (shouldPending) {
|
|
1310
|
+
new Promise((r) => setTimeout(r, pendingMs)).then(async () => {
|
|
1311
|
+
if ((latestPromise = checkLatest())) return latestPromise
|
|
1312
|
+
|
|
1313
|
+
didShowPending = true
|
|
1314
|
+
matches[index] = match = {
|
|
1315
|
+
...match,
|
|
1316
|
+
showPending: true,
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
updateMatch(match)
|
|
1320
|
+
resolve()
|
|
1321
|
+
})
|
|
1281
1322
|
}
|
|
1282
1323
|
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1324
|
+
await fetch()
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
resolve()
|
|
1328
|
+
// No Fetching
|
|
1329
|
+
|
|
1330
|
+
resolve()
|
|
1331
|
+
}),
|
|
1286
1332
|
)
|
|
1287
1333
|
})
|
|
1288
1334
|
|
|
@@ -1312,24 +1358,36 @@ export class Router<
|
|
|
1312
1358
|
pathChanged: pathDidChange,
|
|
1313
1359
|
})
|
|
1314
1360
|
|
|
1315
|
-
|
|
1316
|
-
let pendingMatches: RouteMatch<any, any>[] = this.matchRoutes(
|
|
1317
|
-
next.pathname,
|
|
1318
|
-
next.search,
|
|
1319
|
-
{
|
|
1320
|
-
debug: true,
|
|
1321
|
-
},
|
|
1322
|
-
)
|
|
1323
|
-
|
|
1361
|
+
let pendingMatches!: RouteMatch<any, any>[]
|
|
1324
1362
|
const previousMatches = this.state.matches
|
|
1325
1363
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1364
|
+
this.__store.batch(() => {
|
|
1365
|
+
this.__store.setState((s) => ({
|
|
1366
|
+
...s,
|
|
1367
|
+
preloadMatches: s.preloadMatches.filter((d) => {
|
|
1368
|
+
return (
|
|
1369
|
+
Date.now() - d.updatedAt <
|
|
1370
|
+
(this.options.defaultPreloadMaxAge ?? 3000)
|
|
1371
|
+
)
|
|
1372
|
+
}),
|
|
1373
|
+
}))
|
|
1374
|
+
|
|
1375
|
+
// Match the routes
|
|
1376
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search, {
|
|
1377
|
+
debug: true,
|
|
1378
|
+
})
|
|
1379
|
+
|
|
1380
|
+
// Ingest the new matches
|
|
1381
|
+
this.__store.setState((s) => ({
|
|
1382
|
+
...s,
|
|
1383
|
+
isLoading: true,
|
|
1384
|
+
location: next,
|
|
1385
|
+
pendingMatches,
|
|
1386
|
+
preloadMatches: s.preloadMatches.filter((d) => {
|
|
1387
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1388
|
+
}),
|
|
1389
|
+
}))
|
|
1390
|
+
})
|
|
1333
1391
|
|
|
1334
1392
|
try {
|
|
1335
1393
|
try {
|
|
@@ -1411,6 +1469,25 @@ export class Router<
|
|
|
1411
1469
|
throwOnError: true,
|
|
1412
1470
|
})
|
|
1413
1471
|
|
|
1472
|
+
const loadedMatchIds = Object.fromEntries(
|
|
1473
|
+
[
|
|
1474
|
+
...this.state.matches,
|
|
1475
|
+
...(this.state.pendingMatches ?? []),
|
|
1476
|
+
...this.state.preloadMatches,
|
|
1477
|
+
]?.map((d) => [d.id, true]),
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
this.__store.batch(() => {
|
|
1481
|
+
matches.forEach((match) => {
|
|
1482
|
+
if (!loadedMatchIds[match.id]) {
|
|
1483
|
+
this.__store.setState((s) => ({
|
|
1484
|
+
...s,
|
|
1485
|
+
preloadMatches: [...(s.preloadMatches as any), match],
|
|
1486
|
+
}))
|
|
1487
|
+
}
|
|
1488
|
+
})
|
|
1489
|
+
})
|
|
1490
|
+
|
|
1414
1491
|
matches = await this.loadMatches({
|
|
1415
1492
|
matches,
|
|
1416
1493
|
preload: true,
|
|
@@ -1500,7 +1577,7 @@ export class Router<
|
|
|
1500
1577
|
return {
|
|
1501
1578
|
state: {
|
|
1502
1579
|
dehydratedMatches: this.state.matches.map((d) =>
|
|
1503
|
-
pick(d, ['
|
|
1580
|
+
pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
|
|
1504
1581
|
),
|
|
1505
1582
|
},
|
|
1506
1583
|
}
|
|
@@ -1588,6 +1665,7 @@ export function getInitialRouterState(
|
|
|
1588
1665
|
location,
|
|
1589
1666
|
matches: [],
|
|
1590
1667
|
pendingMatches: [],
|
|
1668
|
+
preloadMatches: [],
|
|
1591
1669
|
lastUpdated: Date.now(),
|
|
1592
1670
|
}
|
|
1593
1671
|
}
|