@tanstack/router-core 1.171.14 → 1.171.16-pre.0
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 +3 -12
- package/dist/cjs/await-signal.cjs +21 -0
- package/dist/cjs/await-signal.cjs.map +1 -0
- package/dist/cjs/await-signal.d.cts +1 -0
- package/dist/cjs/index.cjs +3 -1
- package/dist/cjs/index.d.cts +3 -2
- package/dist/cjs/isServer/client.cjs +2 -0
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +2 -0
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +2 -0
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/load-client.cjs +1470 -0
- package/dist/cjs/load-client.cjs.map +1 -0
- package/dist/cjs/load-client.d.cts +147 -0
- package/dist/cjs/load-server.cjs +565 -0
- package/dist/cjs/load-server.cjs.map +1 -0
- package/dist/cjs/load-server.d.cts +16 -0
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +0 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +187 -445
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +62 -52
- package/dist/cjs/scroll-restoration.cjs +27 -34
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/ssr/client.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
- package/dist/cjs/ssr/handlerCallback.cjs +49 -4
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
- package/dist/cjs/ssr/server.cjs +4 -0
- package/dist/cjs/ssr/server.d.cts +2 -2
- package/dist/cjs/ssr/ssr-client.d.cts +1 -10
- package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +6 -6
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
- package/dist/cjs/ssr/types.d.cts +0 -1
- package/dist/cjs/stores.cjs +30 -104
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +7 -32
- package/dist/cjs/utils.cjs +0 -4
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.d.ts +3 -12
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/await-signal.d.ts +1 -0
- package/dist/esm/await-signal.js +21 -0
- package/dist/esm/await-signal.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +2 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +2 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +2 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/load-client.d.ts +147 -0
- package/dist/esm/load-client.js +1462 -0
- package/dist/esm/load-client.js.map +1 -0
- package/dist/esm/load-server.d.ts +16 -0
- package/dist/esm/load-server.js +565 -0
- package/dist/esm/load-server.js.map +1 -0
- package/dist/esm/redirect.d.ts +0 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +62 -52
- package/dist/esm/router.js +188 -446
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +27 -34
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/client.js +1 -1
- package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
- package/dist/esm/ssr/createRequestHandler.js +61 -11
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.d.ts +3 -0
- package/dist/esm/ssr/handlerCallback.js +47 -5
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/server.d.ts +2 -2
- package/dist/esm/ssr/server.js +3 -3
- package/dist/esm/ssr/ssr-client.d.ts +1 -10
- package/dist/esm/ssr/ssr-match-id.js +2 -2
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +6 -6
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/ssr/types.d.ts +0 -1
- package/dist/esm/stores.d.ts +7 -32
- package/dist/esm/stores.js +30 -104
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.js +1 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/Matches.ts +6 -21
- package/src/await-signal.ts +27 -0
- package/src/index.ts +1 -3
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +1 -0
- package/src/isServer/server.ts +1 -0
- package/src/load-client.ts +2701 -0
- package/src/load-server.ts +849 -0
- package/src/redirect.ts +0 -1
- package/src/route.ts +2 -11
- package/src/router.ts +380 -774
- package/src/scroll-restoration.ts +40 -56
- package/src/ssr/createRequestHandler.ts +109 -16
- package/src/ssr/handlerCallback.ts +76 -10
- package/src/ssr/server.ts +4 -1
- package/src/ssr/ssr-client.ts +1 -310
- package/src/ssr/ssr-match-id.ts +11 -2
- package/src/ssr/ssr-server.ts +9 -8
- package/src/ssr/transformStreamWithRouter.ts +37 -1
- package/src/ssr/types.ts +0 -1
- package/src/stores.ts +46 -208
- package/dist/cjs/load-matches.cjs +0 -658
- package/dist/cjs/load-matches.cjs.map +0 -1
- package/dist/cjs/load-matches.d.cts +0 -18
- package/dist/cjs/ssr/ssr-client.cjs +0 -183
- package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
- package/dist/esm/load-matches.d.ts +0 -18
- package/dist/esm/load-matches.js +0 -656
- package/dist/esm/load-matches.js.map +0 -1
- package/dist/esm/ssr/ssr-client.js +0 -183
- package/dist/esm/ssr/ssr-client.js.map +0 -1
- package/src/load-matches.ts +0 -1278
package/src/ssr/ssr-client.ts
CHANGED
|
@@ -1,310 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { isNotFound } from '../not-found'
|
|
3
|
-
import { createControlledPromise } from '../utils'
|
|
4
|
-
import { hydrateSsrMatchId } from './ssr-match-id'
|
|
5
|
-
import type { GLOBAL_SEROVAL, GLOBAL_TSR } from './constants'
|
|
6
|
-
import type { DehydratedMatch, TsrSsrGlobal } from './types'
|
|
7
|
-
import type { AnyRouteMatch } from '../Matches'
|
|
8
|
-
import type { AnyRouter } from '../router'
|
|
9
|
-
import type { RouteContextOptions } from '../route'
|
|
10
|
-
import type { AnySerializationAdapter } from './serializer/transformer'
|
|
11
|
-
|
|
12
|
-
declare global {
|
|
13
|
-
interface Window {
|
|
14
|
-
[GLOBAL_TSR]?: TsrSsrGlobal
|
|
15
|
-
[GLOBAL_SEROVAL]?: any
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function hydrateMatch(
|
|
20
|
-
match: AnyRouteMatch,
|
|
21
|
-
deyhydratedMatch: DehydratedMatch,
|
|
22
|
-
): void {
|
|
23
|
-
match.id = deyhydratedMatch.i
|
|
24
|
-
match.__beforeLoadContext = deyhydratedMatch.b
|
|
25
|
-
match.loaderData = deyhydratedMatch.l
|
|
26
|
-
match.status = deyhydratedMatch.s
|
|
27
|
-
match.ssr = deyhydratedMatch.ssr
|
|
28
|
-
match.updatedAt = deyhydratedMatch.u
|
|
29
|
-
match.error = deyhydratedMatch.e
|
|
30
|
-
// Only hydrate global-not-found when a defined value is present in the
|
|
31
|
-
// dehydrated payload. If omitted, preserve the value computed from the
|
|
32
|
-
// current client location (important for SPA fallback HTML served at unknown
|
|
33
|
-
// URLs, where dehydrated matches may come from `/` but client matching marks
|
|
34
|
-
// root as globalNotFound).
|
|
35
|
-
if (deyhydratedMatch.g !== undefined) {
|
|
36
|
-
match.globalNotFound = deyhydratedMatch.g
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function hydrate(router: AnyRouter): Promise<any> {
|
|
41
|
-
if (!window.$_TSR) {
|
|
42
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
43
|
-
throw new Error(
|
|
44
|
-
'Invariant failed: Expected to find bootstrap data on window.$_TSR, but we did not. Please file an issue!',
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
invariant()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const serializationAdapters = router.options.serializationAdapters as
|
|
52
|
-
| Array<AnySerializationAdapter>
|
|
53
|
-
| undefined
|
|
54
|
-
|
|
55
|
-
if (serializationAdapters?.length) {
|
|
56
|
-
const fromSerializableMap = new Map()
|
|
57
|
-
serializationAdapters.forEach((adapter) => {
|
|
58
|
-
fromSerializableMap.set(adapter.key, adapter.fromSerializable)
|
|
59
|
-
})
|
|
60
|
-
window.$_TSR.t = fromSerializableMap
|
|
61
|
-
window.$_TSR.buffer.forEach((script) => script())
|
|
62
|
-
}
|
|
63
|
-
window.$_TSR.initialized = true
|
|
64
|
-
|
|
65
|
-
if (!window.$_TSR.router) {
|
|
66
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
67
|
-
throw new Error(
|
|
68
|
-
'Invariant failed: Expected to find a dehydrated data on window.$_TSR.router, but we did not. Please file an issue!',
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
invariant()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const dehydratedRouter = window.$_TSR.router
|
|
76
|
-
dehydratedRouter.matches.forEach((dehydratedMatch) => {
|
|
77
|
-
dehydratedMatch.i = hydrateSsrMatchId(dehydratedMatch.i)
|
|
78
|
-
})
|
|
79
|
-
if (dehydratedRouter.lastMatchId) {
|
|
80
|
-
dehydratedRouter.lastMatchId = hydrateSsrMatchId(
|
|
81
|
-
dehydratedRouter.lastMatchId,
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
const { manifest, dehydratedData, lastMatchId } = dehydratedRouter
|
|
85
|
-
|
|
86
|
-
router.ssr = {
|
|
87
|
-
manifest,
|
|
88
|
-
}
|
|
89
|
-
const meta = document.querySelector('meta[property="csp-nonce"]') as
|
|
90
|
-
| HTMLMetaElement
|
|
91
|
-
| undefined
|
|
92
|
-
const nonce = meta?.content
|
|
93
|
-
router.options.ssr = {
|
|
94
|
-
nonce,
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Allow the user to handle custom hydration data before matching routes.
|
|
98
|
-
// This lets hydration install router config that affects matching, e.g. rewrites.
|
|
99
|
-
await router.options.hydrate?.(dehydratedData)
|
|
100
|
-
|
|
101
|
-
// Hydrate the router state
|
|
102
|
-
const matches = router.matchRoutes(router.stores.location.get())
|
|
103
|
-
|
|
104
|
-
// kick off loading the route chunks
|
|
105
|
-
const routeChunkPromise = Promise.all(
|
|
106
|
-
matches.map((match) =>
|
|
107
|
-
router.loadRouteChunk(router.looseRoutesById[match.routeId]!),
|
|
108
|
-
),
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
function setMatchForcePending(match: AnyRouteMatch) {
|
|
112
|
-
// usually the minPendingPromise is created in the Match component if a pending match is rendered
|
|
113
|
-
// however, this might be too late if the match synchronously resolves
|
|
114
|
-
const route = router.looseRoutesById[match.routeId]!
|
|
115
|
-
const pendingMinMs =
|
|
116
|
-
route.options.pendingMinMs ?? router.options.defaultPendingMinMs
|
|
117
|
-
if (pendingMinMs) {
|
|
118
|
-
const minPendingPromise = createControlledPromise<void>()
|
|
119
|
-
match._nonReactive.minPendingPromise = minPendingPromise
|
|
120
|
-
match._forcePending = true
|
|
121
|
-
|
|
122
|
-
setTimeout(() => {
|
|
123
|
-
minPendingPromise.resolve()
|
|
124
|
-
// We've handled the minPendingPromise, so we can delete it
|
|
125
|
-
router.updateMatch(match.id, (prev) => {
|
|
126
|
-
prev._nonReactive.minPendingPromise = undefined
|
|
127
|
-
return {
|
|
128
|
-
...prev,
|
|
129
|
-
_forcePending: undefined,
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
}, pendingMinMs)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function setRouteSsr(match: AnyRouteMatch) {
|
|
137
|
-
const route = router.looseRoutesById[match.routeId]
|
|
138
|
-
if (route) {
|
|
139
|
-
route.options.ssr = match.ssr
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// Right after hydration and before the first render, we need to rehydrate each match
|
|
143
|
-
// First step is to reyhdrate loaderData and __beforeLoadContext
|
|
144
|
-
let firstNonSsrMatchIndex: number | undefined = undefined
|
|
145
|
-
matches.forEach((match) => {
|
|
146
|
-
const dehydratedMatch = dehydratedRouter.matches.find(
|
|
147
|
-
(d) => d.i === match.id,
|
|
148
|
-
)
|
|
149
|
-
if (!dehydratedMatch) {
|
|
150
|
-
match._nonReactive.dehydrated = false
|
|
151
|
-
match.ssr = false
|
|
152
|
-
setRouteSsr(match)
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
hydrateMatch(match, dehydratedMatch)
|
|
157
|
-
setRouteSsr(match)
|
|
158
|
-
|
|
159
|
-
match._nonReactive.dehydrated = match.ssr !== false
|
|
160
|
-
|
|
161
|
-
if (match.ssr === 'data-only' || match.ssr === false) {
|
|
162
|
-
if (firstNonSsrMatchIndex === undefined) {
|
|
163
|
-
firstNonSsrMatchIndex = match.index
|
|
164
|
-
setMatchForcePending(match)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
router.stores.setMatches(matches)
|
|
170
|
-
|
|
171
|
-
// now that all necessary data is hydrated:
|
|
172
|
-
// 1) fully reconstruct the route context
|
|
173
|
-
// 2) execute `head()` and `scripts()` for each match
|
|
174
|
-
const activeMatches = router.stores.matches.get()
|
|
175
|
-
const location = router.stores.location.get()
|
|
176
|
-
await Promise.all(
|
|
177
|
-
activeMatches.map(async (match) => {
|
|
178
|
-
try {
|
|
179
|
-
const route = router.looseRoutesById[match.routeId]!
|
|
180
|
-
|
|
181
|
-
const parentMatch = activeMatches[match.index - 1]
|
|
182
|
-
const parentContext = parentMatch?.context ?? router.options.context
|
|
183
|
-
|
|
184
|
-
// `context()` was already executed by `matchRoutes`, however route context was not yet fully reconstructed
|
|
185
|
-
// so run it again and merge route context
|
|
186
|
-
if (route.options.context) {
|
|
187
|
-
const contextFnContext: RouteContextOptions<any, any, any, any, any> =
|
|
188
|
-
{
|
|
189
|
-
deps: match.loaderDeps,
|
|
190
|
-
params: match.params,
|
|
191
|
-
context: parentContext ?? {},
|
|
192
|
-
location,
|
|
193
|
-
navigate: (opts: any) =>
|
|
194
|
-
router.navigate({
|
|
195
|
-
...opts,
|
|
196
|
-
_fromLocation: location,
|
|
197
|
-
}),
|
|
198
|
-
buildLocation: router.buildLocation,
|
|
199
|
-
cause: match.cause,
|
|
200
|
-
abortController: match.abortController,
|
|
201
|
-
preload: false,
|
|
202
|
-
matches,
|
|
203
|
-
routeId: route.id,
|
|
204
|
-
}
|
|
205
|
-
match.__routeContext =
|
|
206
|
-
route.options.context(contextFnContext) ?? undefined
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
match.context = {
|
|
210
|
-
...parentContext,
|
|
211
|
-
...match.__routeContext,
|
|
212
|
-
...match.__beforeLoadContext,
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const assetContext = {
|
|
216
|
-
ssr: router.options.ssr,
|
|
217
|
-
matches: activeMatches,
|
|
218
|
-
match,
|
|
219
|
-
params: match.params,
|
|
220
|
-
loaderData: match.loaderData,
|
|
221
|
-
}
|
|
222
|
-
const headFnContent = await route.options.head?.(assetContext)
|
|
223
|
-
|
|
224
|
-
const scripts = await route.options.scripts?.(assetContext)
|
|
225
|
-
|
|
226
|
-
match.meta = headFnContent?.meta
|
|
227
|
-
match.links = headFnContent?.links
|
|
228
|
-
match.headScripts = headFnContent?.scripts
|
|
229
|
-
match.styles = headFnContent?.styles
|
|
230
|
-
match.scripts = scripts
|
|
231
|
-
} catch (err) {
|
|
232
|
-
if (isNotFound(err)) {
|
|
233
|
-
match.error = { isNotFound: true }
|
|
234
|
-
console.error(
|
|
235
|
-
`NotFound error during hydration for routeId: ${match.routeId}`,
|
|
236
|
-
err,
|
|
237
|
-
)
|
|
238
|
-
} else {
|
|
239
|
-
match.error = err as any
|
|
240
|
-
console.error(
|
|
241
|
-
`Error during hydration for route ${match.routeId}:`,
|
|
242
|
-
err,
|
|
243
|
-
)
|
|
244
|
-
throw err
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}),
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
const isSpaMode = matches[matches.length - 1]!.id !== lastMatchId
|
|
251
|
-
const hasSsrFalseMatches = matches.some((m) => m.ssr === false)
|
|
252
|
-
// all matches have data from the server and we are not in SPA mode so we don't need to kick of router.load()
|
|
253
|
-
if (!hasSsrFalseMatches && !isSpaMode) {
|
|
254
|
-
matches.forEach((match) => {
|
|
255
|
-
// remove the dehydrated flag since we won't run router.load() which would remove it
|
|
256
|
-
match._nonReactive.dehydrated = undefined
|
|
257
|
-
})
|
|
258
|
-
// Mark the current location as resolved so that later load cycles
|
|
259
|
-
// (e.g. preloads, invalidations) don't mistakenly detect a href change
|
|
260
|
-
// (resolvedLocation defaults to undefined and router.load() is skipped
|
|
261
|
-
// in the normal SSR hydration path).
|
|
262
|
-
router.stores.resolvedLocation.set(router.stores.location.get())
|
|
263
|
-
return routeChunkPromise
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// schedule router.load() to run after the next tick so we can store the promise in the match before loading starts
|
|
267
|
-
const loadPromise = Promise.resolve()
|
|
268
|
-
.then(() => router.load())
|
|
269
|
-
.catch((err) => {
|
|
270
|
-
console.error('Error during router hydration:', err)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
// in SPA mode we need to keep the first match below the root route pending until router.load() is finished
|
|
274
|
-
// this will prevent that other pending components are rendered but hydration is not blocked
|
|
275
|
-
if (isSpaMode) {
|
|
276
|
-
const match = matches[1]
|
|
277
|
-
if (!match) {
|
|
278
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
279
|
-
throw new Error(
|
|
280
|
-
'Invariant failed: Expected to find a match below the root match in SPA mode.',
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
invariant()
|
|
285
|
-
}
|
|
286
|
-
setMatchForcePending(match)
|
|
287
|
-
|
|
288
|
-
match._displayPending = true
|
|
289
|
-
match._nonReactive.displayPendingPromise = loadPromise
|
|
290
|
-
|
|
291
|
-
loadPromise.then(() => {
|
|
292
|
-
router.batch(() => {
|
|
293
|
-
// ensure router is not in status 'pending' anymore
|
|
294
|
-
// this usually happens in Transitioner but if loading synchronously resolves,
|
|
295
|
-
// Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false
|
|
296
|
-
if (router.stores.status.get() === 'pending') {
|
|
297
|
-
router.stores.status.set('idle')
|
|
298
|
-
router.stores.resolvedLocation.set(router.stores.location.get())
|
|
299
|
-
}
|
|
300
|
-
// hide the pending component once the load is finished
|
|
301
|
-
router.updateMatch(match.id, (prev) => ({
|
|
302
|
-
...prev,
|
|
303
|
-
_displayPending: undefined,
|
|
304
|
-
displayPendingPromise: undefined,
|
|
305
|
-
}))
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
}
|
|
309
|
-
return routeChunkPromise
|
|
310
|
-
}
|
|
1
|
+
export { hydrate } from '../load-client'
|
package/src/ssr/ssr-match-id.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export function dehydrateSsrMatchId(id: string): string {
|
|
2
|
-
return id
|
|
2
|
+
return id
|
|
3
|
+
.replaceAll('~', '~~')
|
|
4
|
+
.replaceAll('\0', '~0')
|
|
5
|
+
.replaceAll('\uFFFD', '~r')
|
|
6
|
+
.replaceAll('/', '\0')
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
export function hydrateSsrMatchId(id: string): string {
|
|
6
|
-
return id
|
|
10
|
+
return id
|
|
11
|
+
.replaceAll('\0', '/')
|
|
12
|
+
.replaceAll('\uFFFD', '/')
|
|
13
|
+
.replace(/~([~0r])/g, (_, code) =>
|
|
14
|
+
code === '0' ? '\0' : code === 'r' ? '\uFFFD' : code,
|
|
15
|
+
)
|
|
7
16
|
}
|
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { decodePath } from '../utils'
|
|
9
9
|
import { createLRUCache } from '../lru-cache'
|
|
10
10
|
import { rootRouteId } from '../root'
|
|
11
|
+
import { _getRenderedMatches } from '../load-client'
|
|
11
12
|
import minifiedTsrBootStrapScript from './tsrScript?script-string'
|
|
12
13
|
import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'
|
|
13
14
|
import { dehydrateSsrMatchId } from './ssr-match-id'
|
|
@@ -51,7 +52,7 @@ export function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {
|
|
|
51
52
|
dehydratedMatch[shorthand] = match[key]
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
|
-
if (match.
|
|
55
|
+
if (match._notFound) {
|
|
55
56
|
dehydratedMatch.g = true
|
|
56
57
|
}
|
|
57
58
|
return dehydratedMatch
|
|
@@ -389,7 +390,7 @@ export function attachRouterServerSsrUtils({
|
|
|
389
390
|
if (!manifest) return manifest
|
|
390
391
|
|
|
391
392
|
const requestAssets = getRequestAssets?.()
|
|
392
|
-
const matches = router.stores.matches.get()
|
|
393
|
+
const matches = _getRenderedMatches(router.stores.matches.get())
|
|
393
394
|
const hasAssets = hasRequestAssets(requestAssets)
|
|
394
395
|
|
|
395
396
|
if (!hasAssets && !manifest.inlineCss) {
|
|
@@ -495,8 +496,9 @@ export function attachRouterServerSsrUtils({
|
|
|
495
496
|
|
|
496
497
|
invariant()
|
|
497
498
|
}
|
|
498
|
-
let matchesToDehydrate = router.stores.matches.get()
|
|
499
|
-
|
|
499
|
+
let matchesToDehydrate = _getRenderedMatches(router.stores.matches.get())
|
|
500
|
+
const isShell = router.isShell()
|
|
501
|
+
if (isShell) {
|
|
500
502
|
// In SPA mode we only want to dehydrate the root match
|
|
501
503
|
matchesToDehydrate = matchesToDehydrate.slice(0, 1)
|
|
502
504
|
}
|
|
@@ -540,11 +542,10 @@ export function attachRouterServerSsrUtils({
|
|
|
540
542
|
manifest: manifestToDehydrate,
|
|
541
543
|
matches,
|
|
542
544
|
}
|
|
543
|
-
const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id
|
|
544
|
-
if (lastMatchId) {
|
|
545
|
-
dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)
|
|
546
|
-
}
|
|
547
545
|
const dehydratedData = await router.options.dehydrate?.()
|
|
546
|
+
if (cleanupStarted) {
|
|
547
|
+
return
|
|
548
|
+
}
|
|
548
549
|
if (dehydratedData) {
|
|
549
550
|
dehydratedRouter.dehydratedData = dehydratedData
|
|
550
551
|
}
|
|
@@ -4,6 +4,8 @@ import { TSR_SCRIPT_BARRIER_ID } from './constants'
|
|
|
4
4
|
import type { AnyRouter } from '../router'
|
|
5
5
|
|
|
6
6
|
export type TransformStreamWithRouterOptions = {
|
|
7
|
+
/** The request lifetime that owns this response stream. */
|
|
8
|
+
signal?: AbortSignal
|
|
7
9
|
/** Timeout for serialization to complete after app render finishes (default: 60000ms) */
|
|
8
10
|
timeoutMs?: number
|
|
9
11
|
/** Maximum lifetime of the stream transform (default: 120000ms). Safety net for cleanup. */
|
|
@@ -196,6 +198,22 @@ function createAbortNotifier(opts?: TransformStreamWithRouterOptions) {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
|
|
201
|
+
function listenToAbort(
|
|
202
|
+
signal: AbortSignal | undefined,
|
|
203
|
+
onAbort: (reason?: unknown) => void,
|
|
204
|
+
) {
|
|
205
|
+
if (!signal) {
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
if (signal.aborted) {
|
|
209
|
+
onAbort(signal.reason)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
const listener = () => onAbort(signal.reason)
|
|
213
|
+
signal.addEventListener('abort', listener, { once: true })
|
|
214
|
+
return () => signal.removeEventListener('abort', listener)
|
|
215
|
+
}
|
|
216
|
+
|
|
199
217
|
export function transformStreamWithRouter(
|
|
200
218
|
router: AnyRouter,
|
|
201
219
|
appStream: ReadableStream,
|
|
@@ -224,6 +242,7 @@ function makeFastPathStream(
|
|
|
224
242
|
let controller: ReadableStreamDefaultController<Uint8Array> | undefined
|
|
225
243
|
let state: MergeState = MergeState.ReadingBody
|
|
226
244
|
let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
|
245
|
+
let stopListeningToAbort: (() => void) | undefined
|
|
227
246
|
let stopListeningToInjectedHtml: (() => void) | undefined
|
|
228
247
|
const readerState = createReaderState(appStream)
|
|
229
248
|
const notifyAbort = createAbortNotifier(opts)
|
|
@@ -251,6 +270,8 @@ function makeFastPathStream(
|
|
|
251
270
|
clearTimeout(lifetimeTimeoutHandle)
|
|
252
271
|
lifetimeTimeoutHandle = undefined
|
|
253
272
|
}
|
|
273
|
+
stopListeningToAbort?.()
|
|
274
|
+
stopListeningToAbort = undefined
|
|
254
275
|
try {
|
|
255
276
|
stopListeningToInjectedHtml?.()
|
|
256
277
|
} catch {
|
|
@@ -360,6 +381,11 @@ function makeFastPathStream(
|
|
|
360
381
|
},
|
|
361
382
|
})
|
|
362
383
|
|
|
384
|
+
stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
|
|
385
|
+
safeError(reason)
|
|
386
|
+
cleanup(reason)
|
|
387
|
+
})
|
|
388
|
+
|
|
363
389
|
return stream
|
|
364
390
|
}
|
|
365
391
|
|
|
@@ -380,6 +406,7 @@ function makeMainStream(
|
|
|
380
406
|
let stopListeningToSerializationFinished: (() => void) | undefined
|
|
381
407
|
let serializationTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
|
382
408
|
let lifetimeTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
|
409
|
+
let stopListeningToAbort: (() => void) | undefined
|
|
383
410
|
let cleanedUp = false
|
|
384
411
|
|
|
385
412
|
let controller: ReadableStreamDefaultController<Uint8Array> | undefined
|
|
@@ -509,6 +536,8 @@ function makeMainStream(
|
|
|
509
536
|
}
|
|
510
537
|
stopListeningToInjectedHtml = undefined
|
|
511
538
|
stopListeningToSerializationFinished = undefined
|
|
539
|
+
stopListeningToAbort?.()
|
|
540
|
+
stopListeningToAbort = undefined
|
|
512
541
|
|
|
513
542
|
if (serializationTimeoutHandle !== undefined) {
|
|
514
543
|
clearTimeout(serializationTimeoutHandle)
|
|
@@ -763,7 +792,14 @@ function makeMainStream(
|
|
|
763
792
|
if (cleanedUp || isDone()) return stream
|
|
764
793
|
}
|
|
765
794
|
|
|
766
|
-
|
|
795
|
+
stopListeningToAbort = listenToAbort(opts?.signal, (reason) => {
|
|
796
|
+
safeError(reason)
|
|
797
|
+
cleanup(reason)
|
|
798
|
+
})
|
|
799
|
+
if (cleanedUp || isDone())
|
|
800
|
+
return stream
|
|
801
|
+
|
|
802
|
+
// Transform the appStream
|
|
767
803
|
;(async () => {
|
|
768
804
|
try {
|
|
769
805
|
while (true) {
|