@tanstack/router-core 1.131.3 → 1.131.5
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/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +7 -5
- package/dist/cjs/router.cjs +91 -102
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-client.cjs +41 -42
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/esm/Matches.d.ts +7 -5
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/router.js +91 -102
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +41 -42
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.ts +14 -10
- package/src/router.ts +95 -106
- package/src/ssr/ssr-client.ts +42 -41
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import invariant from "tiny-invariant";
|
|
2
2
|
import { batch } from "@tanstack/store";
|
|
3
3
|
import { createControlledPromise } from "../utils.js";
|
|
4
|
-
function hydrateMatch(deyhydratedMatch) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
error: deyhydratedMatch.e
|
|
13
|
-
};
|
|
4
|
+
function hydrateMatch(match, deyhydratedMatch) {
|
|
5
|
+
match.id = deyhydratedMatch.i;
|
|
6
|
+
match.__beforeLoadContext = deyhydratedMatch.b;
|
|
7
|
+
match.loaderData = deyhydratedMatch.l;
|
|
8
|
+
match.status = deyhydratedMatch.s;
|
|
9
|
+
match.ssr = deyhydratedMatch.ssr;
|
|
10
|
+
match.updatedAt = deyhydratedMatch.u;
|
|
11
|
+
match.error = deyhydratedMatch.e;
|
|
14
12
|
}
|
|
15
13
|
async function hydrate(router) {
|
|
16
14
|
var _a, _b, _c;
|
|
@@ -34,15 +32,17 @@ async function hydrate(router) {
|
|
|
34
32
|
const pendingMinMs = route.options.pendingMinMs ?? router.options.defaultPendingMinMs;
|
|
35
33
|
if (pendingMinMs) {
|
|
36
34
|
const minPendingPromise = createControlledPromise();
|
|
37
|
-
match.minPendingPromise = minPendingPromise;
|
|
35
|
+
match._nonReactive.minPendingPromise = minPendingPromise;
|
|
38
36
|
match._forcePending = true;
|
|
39
37
|
setTimeout(() => {
|
|
40
38
|
minPendingPromise.resolve();
|
|
41
|
-
router.updateMatch(match.id, (prev) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
router.updateMatch(match.id, (prev) => {
|
|
40
|
+
prev._nonReactive.minPendingPromise = void 0;
|
|
41
|
+
return {
|
|
42
|
+
...prev,
|
|
43
|
+
_forcePending: void 0
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
46
|
}, pendingMinMs);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -52,15 +52,12 @@ async function hydrate(router) {
|
|
|
52
52
|
(d) => d.i === match.id
|
|
53
53
|
);
|
|
54
54
|
if (!dehydratedMatch) {
|
|
55
|
-
|
|
55
|
+
match._nonReactive.dehydrated = false;
|
|
56
|
+
match.ssr = false;
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
match._dehydrated = false;
|
|
61
|
-
} else {
|
|
62
|
-
match._dehydrated = true;
|
|
63
|
-
}
|
|
59
|
+
hydrateMatch(match, dehydratedMatch);
|
|
60
|
+
match._nonReactive.dehydrated = match.ssr !== false;
|
|
64
61
|
if (match.ssr === "data-only" || match.ssr === false) {
|
|
65
62
|
if (firstNonSsrMatchIndex === void 0) {
|
|
66
63
|
firstNonSsrMatchIndex = match.index;
|
|
@@ -77,23 +74,25 @@ async function hydrate(router) {
|
|
|
77
74
|
await ((_c = (_b = router.options).hydrate) == null ? void 0 : _c.call(_b, dehydratedData));
|
|
78
75
|
await Promise.all(
|
|
79
76
|
router.state.matches.map(async (match) => {
|
|
80
|
-
var _a2, _b2, _c2, _d
|
|
77
|
+
var _a2, _b2, _c2, _d;
|
|
81
78
|
const route = router.looseRoutesById[match.routeId];
|
|
82
79
|
const parentMatch = router.state.matches[match.index - 1];
|
|
83
|
-
const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? router.options.context
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? router.options.context;
|
|
81
|
+
if (route.options.context) {
|
|
82
|
+
const contextFnContext = {
|
|
83
|
+
deps: match.loaderDeps,
|
|
84
|
+
params: match.params,
|
|
85
|
+
context: parentContext ?? {},
|
|
86
|
+
location: router.state.location,
|
|
87
|
+
navigate: (opts) => router.navigate({ ...opts, _fromLocation: router.state.location }),
|
|
88
|
+
buildLocation: router.buildLocation,
|
|
89
|
+
cause: match.cause,
|
|
90
|
+
abortController: match.abortController,
|
|
91
|
+
preload: false,
|
|
92
|
+
matches
|
|
93
|
+
};
|
|
94
|
+
match.__routeContext = route.options.context(contextFnContext) ?? void 0;
|
|
95
|
+
}
|
|
97
96
|
match.context = {
|
|
98
97
|
...parentContext,
|
|
99
98
|
...match.__routeContext,
|
|
@@ -105,8 +104,8 @@ async function hydrate(router) {
|
|
|
105
104
|
params: match.params,
|
|
106
105
|
loaderData: match.loaderData
|
|
107
106
|
};
|
|
108
|
-
const headFnContent = await ((
|
|
109
|
-
const scripts = await ((
|
|
107
|
+
const headFnContent = await ((_b2 = (_a2 = route.options).head) == null ? void 0 : _b2.call(_a2, assetContext));
|
|
108
|
+
const scripts = await ((_d = (_c2 = route.options).scripts) == null ? void 0 : _d.call(_c2, assetContext));
|
|
110
109
|
match.meta = headFnContent == null ? void 0 : headFnContent.meta;
|
|
111
110
|
match.links = headFnContent == null ? void 0 : headFnContent.links;
|
|
112
111
|
match.headScripts = headFnContent == null ? void 0 : headFnContent.scripts;
|
|
@@ -118,7 +117,7 @@ async function hydrate(router) {
|
|
|
118
117
|
const hasSsrFalseMatches = matches.some((m) => m.ssr === false);
|
|
119
118
|
if (!hasSsrFalseMatches && !isSpaMode) {
|
|
120
119
|
matches.forEach((match) => {
|
|
121
|
-
match.
|
|
120
|
+
match._nonReactive.dehydrated = void 0;
|
|
122
121
|
});
|
|
123
122
|
return routeChunkPromise;
|
|
124
123
|
}
|
|
@@ -133,7 +132,7 @@ async function hydrate(router) {
|
|
|
133
132
|
);
|
|
134
133
|
setMatchForcePending(match);
|
|
135
134
|
match._displayPending = true;
|
|
136
|
-
match.displayPendingPromise = loadPromise;
|
|
135
|
+
match._nonReactive.displayPendingPromise = loadPromise;
|
|
137
136
|
loadPromise.then(() => {
|
|
138
137
|
batch(() => {
|
|
139
138
|
if (router.__store.state.status === "pending") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-client.js","sources":["../../../src/ssr/ssr-client.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport { batch } from '@tanstack/store'\nimport { createControlledPromise } from '../utils'\nimport type { AnyRouteMatch, MakeRouteMatch } from '../Matches'\nimport type { AnyRouter } from '../router'\nimport type { Manifest } from '../manifest'\nimport type { RouteContextOptions } from '../route'\nimport type { GLOBAL_TSR } from './ssr-server'\n\ndeclare global {\n interface Window {\n [GLOBAL_TSR]?: TsrSsrGlobal\n }\n}\n\nexport interface TsrSsrGlobal {\n router?: DehydratedRouter\n // clean scripts, shortened since this is sent for each streamed script\n c: () => void\n}\n\nfunction hydrateMatch(\n deyhydratedMatch: DehydratedMatch,\n): Partial<MakeRouteMatch> {\n return {\n id: deyhydratedMatch.i,\n __beforeLoadContext: deyhydratedMatch.b,\n loaderData: deyhydratedMatch.l,\n status: deyhydratedMatch.s,\n ssr: deyhydratedMatch.ssr,\n updatedAt: deyhydratedMatch.u,\n error: deyhydratedMatch.e,\n }\n}\nexport interface DehydratedMatch {\n i: MakeRouteMatch['id']\n b?: MakeRouteMatch['__beforeLoadContext']\n l?: MakeRouteMatch['loaderData']\n e?: MakeRouteMatch['error']\n u: MakeRouteMatch['updatedAt']\n s: MakeRouteMatch['status']\n ssr?: MakeRouteMatch['ssr']\n}\n\nexport interface DehydratedRouter {\n manifest: Manifest | undefined\n dehydratedData?: any\n lastMatchId?: string\n matches: Array<DehydratedMatch>\n}\n\nexport async function hydrate(router: AnyRouter): Promise<any> {\n invariant(\n window.$_TSR?.router,\n 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',\n )\n\n const { manifest, dehydratedData, lastMatchId } = window.$_TSR.router\n\n router.ssr = {\n manifest,\n }\n\n // Hydrate the router state\n const matches = router.matchRoutes(router.state.location)\n\n // kick off loading the route chunks\n const routeChunkPromise = Promise.all(\n matches.map((match) => {\n const route = router.looseRoutesById[match.routeId]!\n return router.loadRouteChunk(route)\n }),\n )\n\n function setMatchForcePending(match: AnyRouteMatch) {\n // usually the minPendingPromise is created in the Match component if a pending match is rendered\n // however, this might be too late if the match synchronously resolves\n const route = router.looseRoutesById[match.routeId]!\n const pendingMinMs =\n route.options.pendingMinMs ?? router.options.defaultPendingMinMs\n if (pendingMinMs) {\n const minPendingPromise = createControlledPromise<void>()\n match.minPendingPromise = minPendingPromise\n match._forcePending = true\n\n setTimeout(() => {\n minPendingPromise.resolve()\n // We've handled the minPendingPromise, so we can delete it\n router.updateMatch(match.id, (prev) => ({\n ...prev,\n minPendingPromise: undefined,\n _forcePending: undefined,\n }))\n }, pendingMinMs)\n }\n }\n\n // Right after hydration and before the first render, we need to rehydrate each match\n // First step is to reyhdrate loaderData and __beforeLoadContext\n let firstNonSsrMatchIndex: number | undefined = undefined\n matches.forEach((match) => {\n const dehydratedMatch = window.$_TSR!.router!.matches.find(\n (d) => d.i === match.id,\n )\n if (!dehydratedMatch) {\n Object.assign(match, { dehydrated: false, ssr: false })\n return\n }\n\n Object.assign(match, hydrateMatch(dehydratedMatch))\n\n if (match.ssr === false) {\n match._dehydrated = false\n } else {\n match._dehydrated = true\n }\n\n if (match.ssr === 'data-only' || match.ssr === false) {\n if (firstNonSsrMatchIndex === undefined) {\n firstNonSsrMatchIndex = match.index\n setMatchForcePending(match)\n }\n }\n })\n\n router.__store.setState((s) => {\n return {\n ...s,\n matches,\n }\n })\n\n // Allow the user to handle custom hydration data\n await router.options.hydrate?.(dehydratedData)\n\n // now that all necessary data is hydrated:\n // 1) fully reconstruct the route context\n // 2) execute `head()` and `scripts()` for each match\n await Promise.all(\n router.state.matches.map(async (match) => {\n const route = router.looseRoutesById[match.routeId]!\n\n const parentMatch = router.state.matches[match.index - 1]\n const parentContext = parentMatch?.context ?? router.options.context ?? {}\n\n // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed\n // so run it again and merge route context\n const contextFnContext: RouteContextOptions<any, any, any, any> = {\n deps: match.loaderDeps,\n params: match.params,\n context: parentContext,\n location: router.state.location,\n navigate: (opts: any) =>\n router.navigate({ ...opts, _fromLocation: router.state.location }),\n buildLocation: router.buildLocation,\n cause: match.cause,\n abortController: match.abortController,\n preload: false,\n matches,\n }\n match.__routeContext = route.options.context?.(contextFnContext) ?? {}\n\n match.context = {\n ...parentContext,\n ...match.__routeContext,\n ...match.__beforeLoadContext,\n }\n\n const assetContext = {\n matches: router.state.matches,\n match,\n params: match.params,\n loaderData: match.loaderData,\n }\n const headFnContent = await route.options.head?.(assetContext)\n\n const scripts = await route.options.scripts?.(assetContext)\n\n match.meta = headFnContent?.meta\n match.links = headFnContent?.links\n match.headScripts = headFnContent?.scripts\n match.styles = headFnContent?.styles\n match.scripts = scripts\n }),\n )\n\n const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId\n const hasSsrFalseMatches = matches.some((m) => m.ssr === false)\n // all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load()\n if (!hasSsrFalseMatches && !isSpaMode) {\n matches.forEach((match) => {\n // remove the _dehydrate flag since we won't run router.load() which would remove it\n match._dehydrated = undefined\n })\n return routeChunkPromise\n }\n\n // schedule router.load() to run after the next tick so we can store the promise in the match before loading starts\n const loadPromise = Promise.resolve()\n .then(() => router.load())\n .catch((err) => {\n console.error('Error during router hydration:', err)\n })\n\n // in SPA mode we need to keep the first match below the root route pending until router.load() is finished\n // this will prevent that other pending components are rendered but hydration is not blocked\n if (isSpaMode) {\n const match = matches[1]\n invariant(\n match,\n 'Expected to find a match below the root match in SPA mode.',\n )\n setMatchForcePending(match)\n\n match._displayPending = true\n match.displayPendingPromise = loadPromise\n\n loadPromise.then(() => {\n batch(() => {\n // ensure router is not in status 'pending' anymore\n // this usually happens in Transitioner but if loading synchronously resolves,\n // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false\n if (router.__store.state.status === 'pending') {\n router.__store.setState((s) => ({\n ...s,\n status: 'idle',\n resolvedLocation: s.location,\n }))\n }\n // hide the pending component once the load is finished\n router.updateMatch(match.id, (prev) => {\n return {\n ...prev,\n _displayPending: undefined,\n displayPendingPromise: undefined,\n }\n })\n })\n })\n }\n return routeChunkPromise\n}\n"],"names":["_b","_a","_c"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr-client.js","sources":["../../../src/ssr/ssr-client.ts"],"sourcesContent":["import invariant from 'tiny-invariant'\nimport { batch } from '@tanstack/store'\nimport { createControlledPromise } from '../utils'\nimport type { AnyRouteMatch, MakeRouteMatch } from '../Matches'\nimport type { AnyRouter } from '../router'\nimport type { Manifest } from '../manifest'\nimport type { RouteContextOptions } from '../route'\nimport type { GLOBAL_TSR } from './ssr-server'\n\ndeclare global {\n interface Window {\n [GLOBAL_TSR]?: TsrSsrGlobal\n }\n}\n\nexport interface TsrSsrGlobal {\n router?: DehydratedRouter\n // clean scripts, shortened since this is sent for each streamed script\n c: () => void\n}\n\nfunction hydrateMatch(\n match: AnyRouteMatch,\n deyhydratedMatch: DehydratedMatch,\n): void {\n match.id = deyhydratedMatch.i\n match.__beforeLoadContext = deyhydratedMatch.b\n match.loaderData = deyhydratedMatch.l\n match.status = deyhydratedMatch.s\n match.ssr = deyhydratedMatch.ssr\n match.updatedAt = deyhydratedMatch.u\n match.error = deyhydratedMatch.e\n}\nexport interface DehydratedMatch {\n i: MakeRouteMatch['id']\n b?: MakeRouteMatch['__beforeLoadContext']\n l?: MakeRouteMatch['loaderData']\n e?: MakeRouteMatch['error']\n u: MakeRouteMatch['updatedAt']\n s: MakeRouteMatch['status']\n ssr?: MakeRouteMatch['ssr']\n}\n\nexport interface DehydratedRouter {\n manifest: Manifest | undefined\n dehydratedData?: any\n lastMatchId?: string\n matches: Array<DehydratedMatch>\n}\n\nexport async function hydrate(router: AnyRouter): Promise<any> {\n invariant(\n window.$_TSR?.router,\n 'Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',\n )\n\n const { manifest, dehydratedData, lastMatchId } = window.$_TSR.router\n\n router.ssr = {\n manifest,\n }\n\n // Hydrate the router state\n const matches = router.matchRoutes(router.state.location)\n\n // kick off loading the route chunks\n const routeChunkPromise = Promise.all(\n matches.map((match) => {\n const route = router.looseRoutesById[match.routeId]!\n return router.loadRouteChunk(route)\n }),\n )\n\n function setMatchForcePending(match: AnyRouteMatch) {\n // usually the minPendingPromise is created in the Match component if a pending match is rendered\n // however, this might be too late if the match synchronously resolves\n const route = router.looseRoutesById[match.routeId]!\n const pendingMinMs =\n route.options.pendingMinMs ?? router.options.defaultPendingMinMs\n if (pendingMinMs) {\n const minPendingPromise = createControlledPromise<void>()\n match._nonReactive.minPendingPromise = minPendingPromise\n match._forcePending = true\n\n setTimeout(() => {\n minPendingPromise.resolve()\n // We've handled the minPendingPromise, so we can delete it\n router.updateMatch(match.id, (prev) => {\n prev._nonReactive.minPendingPromise = undefined\n return {\n ...prev,\n _forcePending: undefined,\n }\n })\n }, pendingMinMs)\n }\n }\n\n // Right after hydration and before the first render, we need to rehydrate each match\n // First step is to reyhdrate loaderData and __beforeLoadContext\n let firstNonSsrMatchIndex: number | undefined = undefined\n matches.forEach((match) => {\n const dehydratedMatch = window.$_TSR!.router!.matches.find(\n (d) => d.i === match.id,\n )\n if (!dehydratedMatch) {\n match._nonReactive.dehydrated = false\n match.ssr = false\n return\n }\n\n hydrateMatch(match, dehydratedMatch)\n\n match._nonReactive.dehydrated = match.ssr !== false\n\n if (match.ssr === 'data-only' || match.ssr === false) {\n if (firstNonSsrMatchIndex === undefined) {\n firstNonSsrMatchIndex = match.index\n setMatchForcePending(match)\n }\n }\n })\n\n router.__store.setState((s) => {\n return {\n ...s,\n matches,\n }\n })\n\n // Allow the user to handle custom hydration data\n await router.options.hydrate?.(dehydratedData)\n\n // now that all necessary data is hydrated:\n // 1) fully reconstruct the route context\n // 2) execute `head()` and `scripts()` for each match\n await Promise.all(\n router.state.matches.map(async (match) => {\n const route = router.looseRoutesById[match.routeId]!\n\n const parentMatch = router.state.matches[match.index - 1]\n const parentContext = parentMatch?.context ?? router.options.context\n\n // `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed\n // so run it again and merge route context\n if (route.options.context) {\n const contextFnContext: RouteContextOptions<any, any, any, any> = {\n deps: match.loaderDeps,\n params: match.params,\n context: parentContext ?? {},\n location: router.state.location,\n navigate: (opts: any) =>\n router.navigate({ ...opts, _fromLocation: router.state.location }),\n buildLocation: router.buildLocation,\n cause: match.cause,\n abortController: match.abortController,\n preload: false,\n matches,\n }\n match.__routeContext =\n route.options.context(contextFnContext) ?? undefined\n }\n\n match.context = {\n ...parentContext,\n ...match.__routeContext,\n ...match.__beforeLoadContext,\n }\n\n const assetContext = {\n matches: router.state.matches,\n match,\n params: match.params,\n loaderData: match.loaderData,\n }\n const headFnContent = await route.options.head?.(assetContext)\n\n const scripts = await route.options.scripts?.(assetContext)\n\n match.meta = headFnContent?.meta\n match.links = headFnContent?.links\n match.headScripts = headFnContent?.scripts\n match.styles = headFnContent?.styles\n match.scripts = scripts\n }),\n )\n\n const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId\n const hasSsrFalseMatches = matches.some((m) => m.ssr === false)\n // all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load()\n if (!hasSsrFalseMatches && !isSpaMode) {\n matches.forEach((match) => {\n // remove the dehydrated flag since we won't run router.load() which would remove it\n match._nonReactive.dehydrated = undefined\n })\n return routeChunkPromise\n }\n\n // schedule router.load() to run after the next tick so we can store the promise in the match before loading starts\n const loadPromise = Promise.resolve()\n .then(() => router.load())\n .catch((err) => {\n console.error('Error during router hydration:', err)\n })\n\n // in SPA mode we need to keep the first match below the root route pending until router.load() is finished\n // this will prevent that other pending components are rendered but hydration is not blocked\n if (isSpaMode) {\n const match = matches[1]\n invariant(\n match,\n 'Expected to find a match below the root match in SPA mode.',\n )\n setMatchForcePending(match)\n\n match._displayPending = true\n match._nonReactive.displayPendingPromise = loadPromise\n\n loadPromise.then(() => {\n batch(() => {\n // ensure router is not in status 'pending' anymore\n // this usually happens in Transitioner but if loading synchronously resolves,\n // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false\n if (router.__store.state.status === 'pending') {\n router.__store.setState((s) => ({\n ...s,\n status: 'idle',\n resolvedLocation: s.location,\n }))\n }\n // hide the pending component once the load is finished\n router.updateMatch(match.id, (prev) => {\n return {\n ...prev,\n _displayPending: undefined,\n displayPendingPromise: undefined,\n }\n })\n })\n })\n }\n return routeChunkPromise\n}\n"],"names":["_b","_a","_c"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Matches.ts
CHANGED
|
@@ -136,14 +136,21 @@ export interface RouteMatch<
|
|
|
136
136
|
paramsError: unknown
|
|
137
137
|
searchError: unknown
|
|
138
138
|
updatedAt: number
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
_nonReactive: {
|
|
140
|
+
/** @internal */
|
|
141
|
+
beforeLoadPromise?: ControlledPromise<void>
|
|
142
|
+
/** @internal */
|
|
143
|
+
loaderPromise?: ControlledPromise<void>
|
|
144
|
+
/** @internal */
|
|
145
|
+
pendingTimeout?: ReturnType<typeof setTimeout>
|
|
146
|
+
loadPromise?: ControlledPromise<void>
|
|
147
|
+
displayPendingPromise?: Promise<void>
|
|
148
|
+
minPendingPromise?: ControlledPromise<void>
|
|
149
|
+
dehydrated?: boolean
|
|
150
|
+
}
|
|
144
151
|
loaderData?: TLoaderData
|
|
145
152
|
/** @internal */
|
|
146
|
-
__routeContext
|
|
153
|
+
__routeContext?: Record<string, unknown>
|
|
147
154
|
/** @internal */
|
|
148
155
|
__beforeLoadContext?: Record<string, unknown>
|
|
149
156
|
context: TAllContext
|
|
@@ -158,12 +165,9 @@ export interface RouteMatch<
|
|
|
158
165
|
headers?: Record<string, string>
|
|
159
166
|
globalNotFound?: boolean
|
|
160
167
|
staticData: StaticDataRouteOption
|
|
161
|
-
|
|
162
|
-
pendingTimeout?: ReturnType<typeof setTimeout>
|
|
168
|
+
/** This attribute is not reactive */
|
|
163
169
|
ssr?: boolean | 'data-only'
|
|
164
|
-
_dehydrated?: boolean
|
|
165
170
|
_forcePending?: boolean
|
|
166
|
-
displayPendingPromise?: Promise<void>
|
|
167
171
|
_displayPending?: boolean
|
|
168
172
|
}
|
|
169
173
|
|
package/src/router.ts
CHANGED
|
@@ -1146,8 +1146,8 @@ export class RouterCore<
|
|
|
1146
1146
|
const parentMatchId = parentMatch?.id
|
|
1147
1147
|
|
|
1148
1148
|
const parentContext = !parentMatchId
|
|
1149
|
-
? ((this.options.context as any) ??
|
|
1150
|
-
: (parentMatch.context ?? this.options.context ??
|
|
1149
|
+
? ((this.options.context as any) ?? undefined)
|
|
1150
|
+
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1151
1151
|
|
|
1152
1152
|
return parentContext
|
|
1153
1153
|
}
|
|
@@ -1169,12 +1169,12 @@ export class RouterCore<
|
|
|
1169
1169
|
] = (() => {
|
|
1170
1170
|
// Validate the search params and stabilize them
|
|
1171
1171
|
const parentSearch = parentMatch?.search ?? next.search
|
|
1172
|
-
const parentStrictSearch = parentMatch?._strictSearch ??
|
|
1172
|
+
const parentStrictSearch = parentMatch?._strictSearch ?? undefined
|
|
1173
1173
|
|
|
1174
1174
|
try {
|
|
1175
1175
|
const strictSearch =
|
|
1176
1176
|
validateSearch(route.options.validateSearch, { ...parentSearch }) ??
|
|
1177
|
-
|
|
1177
|
+
undefined
|
|
1178
1178
|
|
|
1179
1179
|
return [
|
|
1180
1180
|
{
|
|
@@ -1284,7 +1284,10 @@ export class RouterCore<
|
|
|
1284
1284
|
isFetching: false,
|
|
1285
1285
|
error: undefined,
|
|
1286
1286
|
paramsError: parseErrors[index],
|
|
1287
|
-
__routeContext:
|
|
1287
|
+
__routeContext: undefined,
|
|
1288
|
+
_nonReactive: {
|
|
1289
|
+
loadPromise: createControlledPromise(),
|
|
1290
|
+
},
|
|
1288
1291
|
__beforeLoadContext: undefined,
|
|
1289
1292
|
context: {},
|
|
1290
1293
|
abortController: new AbortController(),
|
|
@@ -1300,7 +1303,6 @@ export class RouterCore<
|
|
|
1300
1303
|
headScripts: undefined,
|
|
1301
1304
|
meta: undefined,
|
|
1302
1305
|
staticData: route.options.staticData || {},
|
|
1303
|
-
loadPromise: createControlledPromise(),
|
|
1304
1306
|
fullPath: route.fullPath,
|
|
1305
1307
|
}
|
|
1306
1308
|
}
|
|
@@ -1335,22 +1337,25 @@ export class RouterCore<
|
|
|
1335
1337
|
const parentContext = getParentContext(parentMatch)
|
|
1336
1338
|
|
|
1337
1339
|
// Update the match's context
|
|
1338
|
-
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1339
|
-
deps: match.loaderDeps,
|
|
1340
|
-
params: match.params,
|
|
1341
|
-
context: parentContext,
|
|
1342
|
-
location: next,
|
|
1343
|
-
navigate: (opts: any) =>
|
|
1344
|
-
this.navigate({ ...opts, _fromLocation: next }),
|
|
1345
|
-
buildLocation: this.buildLocation,
|
|
1346
|
-
cause: match.cause,
|
|
1347
|
-
abortController: match.abortController,
|
|
1348
|
-
preload: !!match.preload,
|
|
1349
|
-
matches,
|
|
1350
|
-
}
|
|
1351
1340
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1341
|
+
if (route.options.context) {
|
|
1342
|
+
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1343
|
+
deps: match.loaderDeps,
|
|
1344
|
+
params: match.params,
|
|
1345
|
+
context: parentContext ?? {},
|
|
1346
|
+
location: next,
|
|
1347
|
+
navigate: (opts: any) =>
|
|
1348
|
+
this.navigate({ ...opts, _fromLocation: next }),
|
|
1349
|
+
buildLocation: this.buildLocation,
|
|
1350
|
+
cause: match.cause,
|
|
1351
|
+
abortController: match.abortController,
|
|
1352
|
+
preload: !!match.preload,
|
|
1353
|
+
matches,
|
|
1354
|
+
}
|
|
1355
|
+
// Get the route context
|
|
1356
|
+
match.__routeContext =
|
|
1357
|
+
route.options.context(contextFnContext) ?? undefined
|
|
1358
|
+
}
|
|
1354
1359
|
|
|
1355
1360
|
match.context = {
|
|
1356
1361
|
...parentContext,
|
|
@@ -1388,13 +1393,8 @@ export class RouterCore<
|
|
|
1388
1393
|
if (!match) return
|
|
1389
1394
|
|
|
1390
1395
|
match.abortController.abort()
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
return {
|
|
1394
|
-
...prev,
|
|
1395
|
-
pendingTimeout: undefined,
|
|
1396
|
-
}
|
|
1397
|
-
})
|
|
1396
|
+
match._nonReactive.pendingTimeout = undefined
|
|
1397
|
+
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1398
1398
|
}
|
|
1399
1399
|
|
|
1400
1400
|
cancelMatches = () => {
|
|
@@ -1489,13 +1489,9 @@ export class RouterCore<
|
|
|
1489
1489
|
parseCache: this.parsePathnameCache,
|
|
1490
1490
|
}).interpolatedPath
|
|
1491
1491
|
|
|
1492
|
-
const destRoutes = this.matchRoutes(
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
{
|
|
1496
|
-
_buildLocation: true,
|
|
1497
|
-
},
|
|
1498
|
-
).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1492
|
+
const destRoutes = this.matchRoutes(interpolatedNextTo, undefined, {
|
|
1493
|
+
_buildLocation: true,
|
|
1494
|
+
}).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1499
1495
|
|
|
1500
1496
|
// If there are any params, we need to stringify them
|
|
1501
1497
|
if (Object.keys(nextParams).length > 0) {
|
|
@@ -2133,8 +2129,10 @@ export class RouterCore<
|
|
|
2133
2129
|
}
|
|
2134
2130
|
}
|
|
2135
2131
|
|
|
2136
|
-
match.beforeLoadPromise?.resolve()
|
|
2137
|
-
match.loaderPromise?.resolve()
|
|
2132
|
+
match._nonReactive.beforeLoadPromise?.resolve()
|
|
2133
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
2134
|
+
match._nonReactive.beforeLoadPromise = undefined
|
|
2135
|
+
match._nonReactive.loaderPromise = undefined
|
|
2138
2136
|
|
|
2139
2137
|
updateMatch(match.id, (prev) => ({
|
|
2140
2138
|
...prev,
|
|
@@ -2145,15 +2143,13 @@ export class RouterCore<
|
|
|
2145
2143
|
: 'error',
|
|
2146
2144
|
isFetching: false,
|
|
2147
2145
|
error: err,
|
|
2148
|
-
beforeLoadPromise: undefined,
|
|
2149
|
-
loaderPromise: undefined,
|
|
2150
2146
|
}))
|
|
2151
2147
|
|
|
2152
2148
|
if (!(err as any).routeId) {
|
|
2153
2149
|
;(err as any).routeId = match.routeId
|
|
2154
2150
|
}
|
|
2155
2151
|
|
|
2156
|
-
match.loadPromise?.resolve()
|
|
2152
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2157
2153
|
|
|
2158
2154
|
if (isRedirect(err)) {
|
|
2159
2155
|
rendered = true
|
|
@@ -2173,7 +2169,7 @@ export class RouterCore<
|
|
|
2173
2169
|
const shouldSkipLoader = (matchId: string) => {
|
|
2174
2170
|
const match = this.getMatch(matchId)!
|
|
2175
2171
|
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
2176
|
-
if (!this.isServer && match.
|
|
2172
|
+
if (!this.isServer && match._nonReactive.dehydrated) {
|
|
2177
2173
|
return true
|
|
2178
2174
|
}
|
|
2179
2175
|
|
|
@@ -2216,8 +2212,9 @@ export class RouterCore<
|
|
|
2216
2212
|
}
|
|
2217
2213
|
|
|
2218
2214
|
updateMatch(matchId, (prev) => {
|
|
2219
|
-
prev.beforeLoadPromise?.resolve()
|
|
2220
|
-
prev.
|
|
2215
|
+
prev._nonReactive.beforeLoadPromise?.resolve()
|
|
2216
|
+
prev._nonReactive.beforeLoadPromise = undefined
|
|
2217
|
+
prev._nonReactive.loadPromise?.resolve()
|
|
2221
2218
|
|
|
2222
2219
|
return {
|
|
2223
2220
|
...prev,
|
|
@@ -2226,7 +2223,6 @@ export class RouterCore<
|
|
|
2226
2223
|
isFetching: false,
|
|
2227
2224
|
updatedAt: Date.now(),
|
|
2228
2225
|
abortController: new AbortController(),
|
|
2229
|
-
beforeLoadPromise: undefined,
|
|
2230
2226
|
}
|
|
2231
2227
|
})
|
|
2232
2228
|
}
|
|
@@ -2296,10 +2292,7 @@ export class RouterCore<
|
|
|
2296
2292
|
}
|
|
2297
2293
|
}
|
|
2298
2294
|
}
|
|
2299
|
-
|
|
2300
|
-
...prev,
|
|
2301
|
-
ssr,
|
|
2302
|
-
}))
|
|
2295
|
+
existingMatch.ssr = ssr
|
|
2303
2296
|
}
|
|
2304
2297
|
|
|
2305
2298
|
if (shouldSkipLoader(matchId)) {
|
|
@@ -2321,9 +2314,10 @@ export class RouterCore<
|
|
|
2321
2314
|
|
|
2322
2315
|
let executeBeforeLoad = true
|
|
2323
2316
|
const setupPendingTimeout = () => {
|
|
2317
|
+
const match = this.getMatch(matchId)!
|
|
2324
2318
|
if (
|
|
2325
2319
|
shouldPending &&
|
|
2326
|
-
|
|
2320
|
+
match._nonReactive.pendingTimeout === undefined
|
|
2327
2321
|
) {
|
|
2328
2322
|
const pendingTimeout = setTimeout(() => {
|
|
2329
2323
|
try {
|
|
@@ -2332,22 +2326,19 @@ export class RouterCore<
|
|
|
2332
2326
|
triggerOnReady()
|
|
2333
2327
|
} catch {}
|
|
2334
2328
|
}, pendingMs)
|
|
2335
|
-
|
|
2336
|
-
...prev,
|
|
2337
|
-
pendingTimeout,
|
|
2338
|
-
}))
|
|
2329
|
+
match._nonReactive.pendingTimeout = pendingTimeout
|
|
2339
2330
|
}
|
|
2340
2331
|
}
|
|
2341
2332
|
if (
|
|
2342
2333
|
// If we are in the middle of a load, either of these will be present
|
|
2343
2334
|
// (not to be confused with `loadPromise`, which is always defined)
|
|
2344
|
-
existingMatch.beforeLoadPromise ||
|
|
2345
|
-
existingMatch.loaderPromise
|
|
2335
|
+
existingMatch._nonReactive.beforeLoadPromise ||
|
|
2336
|
+
existingMatch._nonReactive.loaderPromise
|
|
2346
2337
|
) {
|
|
2347
2338
|
setupPendingTimeout()
|
|
2348
2339
|
|
|
2349
2340
|
// Wait for the beforeLoad to resolve before we continue
|
|
2350
|
-
await existingMatch.beforeLoadPromise
|
|
2341
|
+
await existingMatch._nonReactive.beforeLoadPromise
|
|
2351
2342
|
const match = this.getMatch(matchId)!
|
|
2352
2343
|
if (match.status === 'error') {
|
|
2353
2344
|
executeBeforeLoad = true
|
|
@@ -2361,17 +2352,15 @@ export class RouterCore<
|
|
|
2361
2352
|
if (executeBeforeLoad) {
|
|
2362
2353
|
// If we are not in the middle of a load OR the previous load failed, start it
|
|
2363
2354
|
try {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
}
|
|
2374
|
-
})
|
|
2355
|
+
const match = this.getMatch(matchId)!
|
|
2356
|
+
match._nonReactive.beforeLoadPromise =
|
|
2357
|
+
createControlledPromise<void>()
|
|
2358
|
+
// explicitly capture the previous loadPromise
|
|
2359
|
+
const prevLoadPromise = match._nonReactive.loadPromise
|
|
2360
|
+
match._nonReactive.loadPromise =
|
|
2361
|
+
createControlledPromise<void>(() => {
|
|
2362
|
+
prevLoadPromise?.resolve()
|
|
2363
|
+
})
|
|
2375
2364
|
|
|
2376
2365
|
const { paramsError, searchError } = this.getMatch(matchId)!
|
|
2377
2366
|
|
|
@@ -2388,7 +2377,7 @@ export class RouterCore<
|
|
|
2388
2377
|
const abortController = new AbortController()
|
|
2389
2378
|
|
|
2390
2379
|
const parentMatchContext =
|
|
2391
|
-
parentMatch?.context ?? this.options.context ??
|
|
2380
|
+
parentMatch?.context ?? this.options.context ?? undefined
|
|
2392
2381
|
|
|
2393
2382
|
updateMatch(matchId, (prev) => ({
|
|
2394
2383
|
...prev,
|
|
@@ -2453,11 +2442,11 @@ export class RouterCore<
|
|
|
2453
2442
|
}
|
|
2454
2443
|
|
|
2455
2444
|
updateMatch(matchId, (prev) => {
|
|
2456
|
-
prev.beforeLoadPromise?.resolve()
|
|
2445
|
+
prev._nonReactive.beforeLoadPromise?.resolve()
|
|
2446
|
+
prev._nonReactive.beforeLoadPromise = undefined
|
|
2457
2447
|
|
|
2458
2448
|
return {
|
|
2459
2449
|
...prev,
|
|
2460
|
-
beforeLoadPromise: undefined,
|
|
2461
2450
|
isFetching: false,
|
|
2462
2451
|
}
|
|
2463
2452
|
})
|
|
@@ -2507,8 +2496,8 @@ export class RouterCore<
|
|
|
2507
2496
|
|
|
2508
2497
|
const potentialPendingMinPromise = async () => {
|
|
2509
2498
|
const latestMatch = this.getMatch(matchId)!
|
|
2510
|
-
if (latestMatch.minPendingPromise) {
|
|
2511
|
-
await latestMatch.minPendingPromise
|
|
2499
|
+
if (latestMatch._nonReactive.minPendingPromise) {
|
|
2500
|
+
await latestMatch._nonReactive.minPendingPromise
|
|
2512
2501
|
}
|
|
2513
2502
|
}
|
|
2514
2503
|
|
|
@@ -2524,7 +2513,7 @@ export class RouterCore<
|
|
|
2524
2513
|
}
|
|
2525
2514
|
}
|
|
2526
2515
|
// there is a loaderPromise, so we are in the middle of a load
|
|
2527
|
-
else if (prevMatch.loaderPromise) {
|
|
2516
|
+
else if (prevMatch._nonReactive.loaderPromise) {
|
|
2528
2517
|
// do not block if we already have stale data we can show
|
|
2529
2518
|
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2530
2519
|
// and we don't want to swallow errors
|
|
@@ -2535,7 +2524,7 @@ export class RouterCore<
|
|
|
2535
2524
|
) {
|
|
2536
2525
|
return this.getMatch(matchId)!
|
|
2537
2526
|
}
|
|
2538
|
-
await prevMatch.loaderPromise
|
|
2527
|
+
await prevMatch._nonReactive.loaderPromise
|
|
2539
2528
|
const match = this.getMatch(matchId)!
|
|
2540
2529
|
if (match.error) {
|
|
2541
2530
|
handleRedirectAndNotFound(match, match.error)
|
|
@@ -2592,13 +2581,16 @@ export class RouterCore<
|
|
|
2592
2581
|
? shouldReloadOption(getLoaderContext())
|
|
2593
2582
|
: shouldReloadOption
|
|
2594
2583
|
|
|
2595
|
-
updateMatch(matchId, (prev) =>
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2584
|
+
updateMatch(matchId, (prev) => {
|
|
2585
|
+
prev._nonReactive.loaderPromise =
|
|
2586
|
+
createControlledPromise<void>()
|
|
2587
|
+
return {
|
|
2588
|
+
...prev,
|
|
2589
|
+
preload:
|
|
2590
|
+
!!preload &&
|
|
2591
|
+
!this.state.matches.some((d) => d.id === matchId),
|
|
2592
|
+
}
|
|
2593
|
+
})
|
|
2602
2594
|
|
|
2603
2595
|
const runLoader = async () => {
|
|
2604
2596
|
try {
|
|
@@ -2682,11 +2674,13 @@ export class RouterCore<
|
|
|
2682
2674
|
} catch (err) {
|
|
2683
2675
|
const head = await executeHead()
|
|
2684
2676
|
|
|
2685
|
-
updateMatch(matchId, (prev) =>
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2677
|
+
updateMatch(matchId, (prev) => {
|
|
2678
|
+
prev._nonReactive.loaderPromise = undefined
|
|
2679
|
+
return {
|
|
2680
|
+
...prev,
|
|
2681
|
+
...head,
|
|
2682
|
+
}
|
|
2683
|
+
})
|
|
2690
2684
|
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2691
2685
|
}
|
|
2692
2686
|
}
|
|
@@ -2703,14 +2697,10 @@ export class RouterCore<
|
|
|
2703
2697
|
;(async () => {
|
|
2704
2698
|
try {
|
|
2705
2699
|
await runLoader()
|
|
2706
|
-
const
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
updateMatch(matchId, (prev) => ({
|
|
2711
|
-
...prev,
|
|
2712
|
-
loaderPromise: undefined,
|
|
2713
|
-
}))
|
|
2700
|
+
const match = this.getMatch(matchId)!
|
|
2701
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
2702
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2703
|
+
match._nonReactive.loaderPromise = undefined
|
|
2714
2704
|
} catch (err) {
|
|
2715
2705
|
if (isRedirect(err)) {
|
|
2716
2706
|
await this.navigate(err.options)
|
|
@@ -2734,25 +2724,23 @@ export class RouterCore<
|
|
|
2734
2724
|
}
|
|
2735
2725
|
}
|
|
2736
2726
|
if (!loaderIsRunningAsync) {
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
loadPromise?.resolve()
|
|
2727
|
+
const match = this.getMatch(matchId)!
|
|
2728
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
2729
|
+
match._nonReactive.loadPromise?.resolve()
|
|
2741
2730
|
}
|
|
2742
2731
|
|
|
2743
2732
|
updateMatch(matchId, (prev) => {
|
|
2744
|
-
clearTimeout(prev.pendingTimeout)
|
|
2733
|
+
clearTimeout(prev._nonReactive.pendingTimeout)
|
|
2734
|
+
prev._nonReactive.pendingTimeout = undefined
|
|
2735
|
+
if (!loaderIsRunningAsync)
|
|
2736
|
+
prev._nonReactive.loaderPromise = undefined
|
|
2737
|
+
prev._nonReactive.dehydrated = undefined
|
|
2745
2738
|
return {
|
|
2746
2739
|
...prev,
|
|
2747
2740
|
isFetching: loaderIsRunningAsync
|
|
2748
2741
|
? prev.isFetching
|
|
2749
2742
|
: false,
|
|
2750
|
-
loaderPromise: loaderIsRunningAsync
|
|
2751
|
-
? prev.loaderPromise
|
|
2752
|
-
: undefined,
|
|
2753
2743
|
invalid: false,
|
|
2754
|
-
pendingTimeout: undefined,
|
|
2755
|
-
_dehydrated: undefined,
|
|
2756
2744
|
}
|
|
2757
2745
|
})
|
|
2758
2746
|
return this.getMatch(matchId)!
|
|
@@ -2798,7 +2786,7 @@ export class RouterCore<
|
|
|
2798
2786
|
invalid: true,
|
|
2799
2787
|
...(opts?.forcePending || d.status === 'error'
|
|
2800
2788
|
? ({ status: 'pending', error: undefined } as const)
|
|
2801
|
-
:
|
|
2789
|
+
: undefined),
|
|
2802
2790
|
}
|
|
2803
2791
|
}
|
|
2804
2792
|
return d
|
|
@@ -3569,7 +3557,8 @@ function applySearchMiddleware({
|
|
|
3569
3557
|
try {
|
|
3570
3558
|
const validatedSearch = {
|
|
3571
3559
|
...result,
|
|
3572
|
-
...(validateSearch(route.options.validateSearch, result) ??
|
|
3560
|
+
...(validateSearch(route.options.validateSearch, result) ??
|
|
3561
|
+
undefined),
|
|
3573
3562
|
}
|
|
3574
3563
|
return validatedSearch
|
|
3575
3564
|
} catch {
|