@tanstack/router-core 1.171.4 → 1.171.6

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.
Files changed (49) hide show
  1. package/dist/cjs/index.cjs +5 -1
  2. package/dist/cjs/index.d.cts +2 -2
  3. package/dist/cjs/manifest.cjs +43 -17
  4. package/dist/cjs/manifest.cjs.map +1 -1
  5. package/dist/cjs/manifest.d.cts +76 -24
  6. package/dist/cjs/router.cjs.map +1 -1
  7. package/dist/cjs/router.d.cts +2 -2
  8. package/dist/cjs/scroll-restoration-inline.cjs +1 -1
  9. package/dist/cjs/scroll-restoration-inline.cjs.map +1 -1
  10. package/dist/cjs/scroll-restoration-inline.d.cts +1 -4
  11. package/dist/cjs/scroll-restoration-script/server.cjs +5 -8
  12. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
  13. package/dist/cjs/scroll-restoration.cjs +32 -32
  14. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  15. package/dist/cjs/ssr/createRequestHandler.cjs +2 -1
  16. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  17. package/dist/cjs/ssr/createRequestHandler.d.cts +2 -2
  18. package/dist/cjs/ssr/ssr-server.cjs +132 -83
  19. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  20. package/dist/cjs/ssr/ssr-server.d.cts +4 -5
  21. package/dist/esm/index.d.ts +2 -2
  22. package/dist/esm/index.js +2 -2
  23. package/dist/esm/manifest.d.ts +76 -24
  24. package/dist/esm/manifest.js +39 -17
  25. package/dist/esm/manifest.js.map +1 -1
  26. package/dist/esm/router.d.ts +2 -2
  27. package/dist/esm/router.js.map +1 -1
  28. package/dist/esm/scroll-restoration-inline.d.ts +1 -4
  29. package/dist/esm/scroll-restoration-inline.js +1 -1
  30. package/dist/esm/scroll-restoration-inline.js.map +1 -1
  31. package/dist/esm/scroll-restoration-script/server.js +5 -8
  32. package/dist/esm/scroll-restoration-script/server.js.map +1 -1
  33. package/dist/esm/scroll-restoration.js +32 -32
  34. package/dist/esm/scroll-restoration.js.map +1 -1
  35. package/dist/esm/ssr/createRequestHandler.d.ts +2 -2
  36. package/dist/esm/ssr/createRequestHandler.js +2 -1
  37. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  38. package/dist/esm/ssr/ssr-server.d.ts +4 -5
  39. package/dist/esm/ssr/ssr-server.js +133 -84
  40. package/dist/esm/ssr/ssr-server.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/index.ts +21 -1
  43. package/src/manifest.ts +151 -59
  44. package/src/router.ts +6 -4
  45. package/src/scroll-restoration-inline.ts +19 -38
  46. package/src/scroll-restoration-script/server.ts +5 -17
  47. package/src/scroll-restoration.ts +35 -40
  48. package/src/ssr/createRequestHandler.ts +6 -5
  49. package/src/ssr/ssr-server.ts +239 -141
@@ -302,60 +302,51 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
302
302
  }
303
303
  }
304
304
 
305
- if (!shouldResetScroll) {
306
- return
307
- }
308
-
309
305
  ignoreScroll = true
310
306
 
311
307
  try {
312
308
  const hash = event.toLocation.hash
313
309
  const hashScrollIntoViewOptions =
314
310
  event.toLocation.state.__hashScrollIntoViewOptions ?? true
315
- const action = locationHistoryActions.get(event.toLocation)
316
- const skipWindowRestore =
317
- hash &&
318
- hashScrollIntoViewOptions &&
319
- (action === 'PUSH' || action === 'REPLACE')
320
-
321
- const elementEntries = router.isScrollRestoring
322
- ? scrollRestorationCache[cacheKey]
323
- : undefined
324
311
  let windowRestored = false
325
312
 
326
- if (elementEntries) {
327
- for (const elementSelector in elementEntries) {
328
- const { scrollX, scrollY } = elementEntries[elementSelector]!
313
+ if (shouldResetScroll) {
314
+ const action = locationHistoryActions.get(event.toLocation)
315
+ const skipWindowRestore =
316
+ hash &&
317
+ hashScrollIntoViewOptions &&
318
+ (action === 'PUSH' || action === 'REPLACE')
329
319
 
330
- if (elementSelector === windowScrollTarget) {
331
- if (skipWindowRestore) {
332
- continue
333
- }
320
+ const elementEntries = router.isScrollRestoring
321
+ ? scrollRestorationCache[cacheKey]
322
+ : undefined
334
323
 
335
- scrollTo({
336
- top: scrollY,
337
- left: scrollX,
338
- behavior,
339
- })
340
- windowRestored = true
341
- } else {
342
- const element = getElement(elementSelector)
343
- if (element) {
344
- element.scrollLeft = scrollX
345
- element.scrollTop = scrollY
324
+ if (elementEntries) {
325
+ for (const elementSelector in elementEntries) {
326
+ const { scrollX, scrollY } = elementEntries[elementSelector]!
327
+
328
+ if (elementSelector === windowScrollTarget) {
329
+ if (skipWindowRestore) {
330
+ continue
331
+ }
332
+
333
+ scrollTo({
334
+ top: scrollY,
335
+ left: scrollX,
336
+ behavior,
337
+ })
338
+ windowRestored = true
339
+ } else {
340
+ const element = getElement(elementSelector)
341
+ if (element) {
342
+ element.scrollLeft = scrollX
343
+ element.scrollTop = scrollY
344
+ }
346
345
  }
347
346
  }
348
347
  }
349
- }
350
348
 
351
- if (!windowRestored) {
352
- if (hash) {
353
- if (hashScrollIntoViewOptions) {
354
- document
355
- .getElementById(hash)
356
- ?.scrollIntoView(hashScrollIntoViewOptions)
357
- }
358
- } else {
349
+ if (!windowRestored && !hash) {
359
350
  const scrollOptions = {
360
351
  top: 0,
361
352
  left: 0,
@@ -371,6 +362,10 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
371
362
  }
372
363
  }
373
364
  }
365
+
366
+ if (!windowRestored && hash && hashScrollIntoViewOptions) {
367
+ document.getElementById(hash)?.scrollIntoView(hashScrollIntoViewOptions)
368
+ }
374
369
  } finally {
375
370
  ignoreScroll = false
376
371
  }
@@ -8,7 +8,7 @@ import {
8
8
  import type { HandlerCallback } from './handlerCallback'
9
9
  import type { AnyHeaders } from './headers'
10
10
  import type { AnyRouter } from '../router'
11
- import type { Manifest } from '../manifest'
11
+ import type { ServerManifest } from '../manifest'
12
12
 
13
13
  export type RequestHandler<TRouter extends AnyRouter> = (
14
14
  cb: HandlerCallback<TRouter>,
@@ -21,7 +21,7 @@ export function createRequestHandler<TRouter extends AnyRouter>({
21
21
  }: {
22
22
  createRouter: () => TRouter
23
23
  request: Request
24
- getRouterManifest?: () => Manifest | Promise<Manifest>
24
+ getRouterManifest?: () => ServerManifest | Promise<ServerManifest>
25
25
  }): RequestHandler<TRouter> {
26
26
  return async (cb) => {
27
27
  const router = createRouter()
@@ -78,9 +78,10 @@ export function createRequestHandler<TRouter extends AnyRouter>({
78
78
  }
79
79
 
80
80
  function getRequestHeaders(opts: { router: AnyRouter }): Headers {
81
- const matchHeaders = opts.router.stores.matches
82
- .get()
83
- .map<AnyHeaders>((match) => match.headers)
81
+ const matchHeaders: Array<AnyHeaders> = []
82
+ for (const match of opts.router.stores.matches.get()) {
83
+ matchHeaders.push(match.headers)
84
+ }
84
85
 
85
86
  // Handle Redirects
86
87
  const redirect = opts.router.stores.redirect.get()
@@ -4,7 +4,6 @@ import {
4
4
  createInlineCssPlaceholderAsset,
5
5
  createInlineCssStyleAsset,
6
6
  getStylesheetHref,
7
- isInlinableStylesheet,
8
7
  } from '../manifest'
9
8
  import { decodePath } from '../utils'
10
9
  import { createLRUCache } from '../lru-cache'
@@ -19,7 +18,13 @@ import type { DehydratedMatch, DehydratedRouter } from './types'
19
18
  import type { AnySerializationAdapter } from './serializer/transformer'
20
19
  import type { AnyRouter } from '../router'
21
20
  import type { AnyRouteMatch } from '../Matches'
22
- import type { Manifest, RouterManagedTag } from '../manifest'
21
+ import type {
22
+ Manifest,
23
+ ManifestRoute,
24
+ ManifestRouteAssets,
25
+ RouterManagedTag,
26
+ ServerManifest,
27
+ } from '../manifest'
23
28
 
24
29
  declare module '../router' {
25
30
  interface ServerSsr {
@@ -162,133 +167,222 @@ const isProd = process.env.NODE_ENV === 'production'
162
167
 
163
168
  type FilteredRoutes = Manifest['routes']
164
169
 
165
- type ManifestLRU = LRUCache<string, FilteredRoutes>
166
- type InlineCssLRU = LRUCache<string, string>
170
+ type PreparedMatchedManifestRoutes = {
171
+ routes: FilteredRoutes
172
+ hasStrippedRoutes: boolean
173
+ inlineCssHrefs?: Array<string>
174
+ inlineCss?: string
175
+ }
176
+
177
+ type ManifestLRU = LRUCache<string, PreparedMatchedManifestRoutes>
167
178
 
168
179
  const MANIFEST_CACHE_SIZE = 100
169
- const manifestCaches = new WeakMap<Manifest, ManifestLRU>()
170
- const inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()
180
+ const manifestCaches = new WeakMap<ServerManifest, ManifestLRU>()
171
181
 
172
- function getManifestCache(manifest: Manifest): ManifestLRU {
182
+ function getManifestCache(manifest: ServerManifest): ManifestLRU {
173
183
  const cache = manifestCaches.get(manifest)
174
184
  if (cache) return cache
175
- const newCache = createLRUCache<string, FilteredRoutes>(MANIFEST_CACHE_SIZE)
185
+ const newCache = createLRUCache<string, PreparedMatchedManifestRoutes>(
186
+ MANIFEST_CACHE_SIZE,
187
+ )
176
188
  manifestCaches.set(manifest, newCache)
177
189
  return newCache
178
190
  }
179
191
 
180
- function getInlineCssCache(manifest: Manifest): InlineCssLRU {
181
- const cache = inlineCssCaches.get(manifest)
182
- if (cache) return cache
183
- const newCache = createLRUCache<string, string>(MANIFEST_CACHE_SIZE)
184
- inlineCssCaches.set(manifest, newCache)
185
- return newCache
186
- }
187
-
188
- function getInlineCssHrefsForMatches(
189
- manifest: Manifest | undefined,
190
- matches: Array<AnyRouteMatch>,
192
+ function getInlineCssForPreparedRoutes(
193
+ manifest: ServerManifest,
194
+ preparedRoutes: PreparedMatchedManifestRoutes,
191
195
  ) {
192
- const styles = manifest?.inlineCss?.styles
193
- if (!styles) return []
196
+ if (preparedRoutes.inlineCss !== undefined) return preparedRoutes.inlineCss
194
197
 
195
- const seen = new Set<string>()
196
- const hrefs: Array<string> = []
198
+ const styles = manifest.inlineCss?.styles
199
+ const hrefs = preparedRoutes.inlineCssHrefs
200
+ if (!styles || !hrefs?.length) return undefined
197
201
 
198
- for (const match of matches) {
199
- const assets = manifest?.routes[match.routeId]?.assets ?? []
200
- for (const asset of assets) {
201
- const href = getStylesheetHref(asset)
202
- if (!href || seen.has(href) || styles[href] === undefined) {
203
- continue
204
- }
205
- seen.add(href)
206
- hrefs.push(href)
207
- }
202
+ let css = ''
203
+ for (const href of hrefs) {
204
+ css += styles[href]!
208
205
  }
209
206
 
210
- return hrefs
207
+ preparedRoutes.inlineCss = css
208
+ return css
211
209
  }
212
210
 
213
- function getInlineCssForHrefs(manifest: Manifest, hrefs: Array<string>) {
214
- const styles = manifest.inlineCss?.styles
215
- if (!styles || hrefs.length === 0) return undefined
211
+ function getInlineCssAssetForPreparedRoutes(
212
+ manifest: ServerManifest,
213
+ preparedRoutes: PreparedMatchedManifestRoutes,
214
+ ) {
215
+ const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes)
216
216
 
217
- const cacheKey = hrefs.join('\0')
217
+ return css === undefined ? undefined : createInlineCssStyleAsset(css)
218
+ }
219
+
220
+ function getMatchedRoutesCacheKey(matches: Array<AnyRouteMatch>) {
221
+ let cacheKey = ''
222
+ for (let i = 0; i < matches.length; i++) {
223
+ cacheKey += (i === 0 ? '' : '\0') + matches[i]!.routeId
224
+ }
225
+ return cacheKey
226
+ }
227
+
228
+ function getPreparedMatchedManifestRoutes(
229
+ manifest: ServerManifest,
230
+ matches: Array<AnyRouteMatch>,
231
+ cacheKey: string,
232
+ ) {
218
233
  if (isProd) {
219
- const cached = getInlineCssCache(manifest).get(cacheKey)
220
- if (cached !== undefined) return cached
234
+ const cached = getManifestCache(manifest).get(cacheKey)
235
+ if (cached) {
236
+ return cached
237
+ }
221
238
  }
222
239
 
223
- const css = hrefs.map((href) => styles[href]!).join('')
240
+ const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches)
224
241
 
225
242
  if (isProd) {
226
- getInlineCssCache(manifest).set(cacheKey, css)
243
+ getManifestCache(manifest).set(cacheKey, preparedRoutes)
227
244
  }
228
245
 
229
- return css
246
+ return preparedRoutes
230
247
  }
231
248
 
232
- function getInlineCssAssetForMatches(
233
- manifest: Manifest | undefined,
249
+ function prepareMatchedManifestRoutes(
250
+ manifest: ServerManifest,
234
251
  matches: Array<AnyRouteMatch>,
235
- ) {
236
- if (!manifest?.inlineCss) return undefined
252
+ ): PreparedMatchedManifestRoutes {
253
+ const inlineStyles = manifest.inlineCss?.styles
254
+ const routes: FilteredRoutes = {}
255
+
256
+ if (!inlineStyles) {
257
+ for (const match of matches) {
258
+ const route = manifest.routes[match.routeId]
259
+ if (route) {
260
+ routes[match.routeId] = route
261
+ }
262
+ }
263
+ return { routes, hasStrippedRoutes: false }
264
+ }
237
265
 
238
- const hrefs = getInlineCssHrefsForMatches(manifest, matches)
239
- const css = getInlineCssForHrefs(manifest, hrefs)
266
+ const inlineCssHrefs: Array<string> = []
267
+ const seenInlineCssHrefs = new Set<string>()
268
+ let hasStrippedRoutes = false
240
269
 
241
- return css === undefined ? undefined : createInlineCssStyleAsset(css)
242
- }
270
+ for (const match of matches) {
271
+ const routeId = match.routeId
272
+ const route = manifest.routes[routeId]
273
+ if (!route) {
274
+ continue
275
+ }
243
276
 
244
- function stripInlinedStylesheetAssets(
245
- manifest: Manifest,
246
- routes: FilteredRoutes,
247
- matches: Array<AnyRouteMatch>,
248
- ): FilteredRoutes {
249
- if (!manifest.inlineCss) {
250
- return routes
277
+ const nextRoute = stripInlinedStylesheetAssetsFromRoute(
278
+ inlineStyles,
279
+ route,
280
+ inlineCssHrefs,
281
+ seenInlineCssHrefs,
282
+ )
283
+
284
+ if (nextRoute !== route) {
285
+ hasStrippedRoutes = true
286
+ }
287
+ routes[routeId] = nextRoute
251
288
  }
252
289
 
253
- const nextRoutes: FilteredRoutes = {}
290
+ return {
291
+ routes,
292
+ hasStrippedRoutes,
293
+ ...(inlineCssHrefs.length ? { inlineCssHrefs } : {}),
294
+ }
295
+ }
254
296
 
255
- for (const [routeId, route] of Object.entries(routes)) {
256
- const assets = route.assets?.filter(
257
- (asset) => !isInlinableStylesheet(manifest, asset),
258
- )
297
+ function stripInlinedStylesheetAssetsFromRoute(
298
+ inlineStyles: Record<string, string>,
299
+ route: ManifestRoute,
300
+ inlineCssHrefs: Array<string>,
301
+ seenInlineCssHrefs: Set<string>,
302
+ ): ManifestRoute {
303
+ const css = route.css
304
+ if (!css) {
305
+ return route
306
+ }
259
307
 
308
+ if (css.length === 0) {
260
309
  const nextRoute = { ...route }
261
- if (assets) {
262
- if (assets.length > 0) {
263
- nextRoute.assets = assets
264
- } else {
265
- delete nextRoute.assets
310
+ delete nextRoute.css
311
+ return nextRoute
312
+ }
313
+
314
+ let cssLinks: typeof css | undefined
315
+ for (let i = 0; i < css.length; i++) {
316
+ const link = css[i]!
317
+ const href = getStylesheetHref(link)
318
+ if (inlineStyles[href] === undefined) {
319
+ if (cssLinks) {
320
+ cssLinks.push(link)
266
321
  }
322
+ continue
267
323
  }
268
- nextRoutes[routeId] = nextRoute
269
- }
270
324
 
271
- if (getInlineCssAssetForMatches(manifest, matches)) {
272
- const rootRoute = nextRoutes[rootRouteId] ?? {}
273
- nextRoutes[rootRouteId] = {
274
- ...rootRoute,
275
- assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],
325
+ if (!seenInlineCssHrefs.has(href)) {
326
+ seenInlineCssHrefs.add(href)
327
+ inlineCssHrefs.push(href)
328
+ }
329
+
330
+ if (!cssLinks) {
331
+ cssLinks = css.slice(0, i)
276
332
  }
277
333
  }
278
334
 
279
- return nextRoutes
335
+ if (!cssLinks) {
336
+ return route
337
+ }
338
+
339
+ if (cssLinks.length > 0) {
340
+ return { ...route, css: cssLinks }
341
+ }
342
+
343
+ const nextRoute = { ...route }
344
+ delete nextRoute.css
345
+ return nextRoute
346
+ }
347
+
348
+ function hasRouteAssets(route: ManifestRoute) {
349
+ return !!route.scripts?.length || !!route.css?.length
350
+ }
351
+
352
+ function hasRequestAssets(assets: ManifestRouteAssets | undefined) {
353
+ return !!assets && (!!assets.preloads?.length || hasRouteAssets(assets))
354
+ }
355
+
356
+ function mergeRequestAssetsIntoRootRoute(
357
+ rootRoute: ManifestRoute | undefined,
358
+ requestAssets: ManifestRouteAssets | undefined,
359
+ ): ManifestRoute {
360
+ const preloads = requestAssets?.preloads?.length
361
+ ? [...requestAssets.preloads, ...(rootRoute?.preloads ?? [])]
362
+ : rootRoute?.preloads
363
+ const scripts = requestAssets?.scripts?.length
364
+ ? [...requestAssets.scripts, ...(rootRoute?.scripts ?? [])]
365
+ : rootRoute?.scripts
366
+ const cssLinks = requestAssets?.css?.length
367
+ ? [...requestAssets.css, ...(rootRoute?.css ?? [])]
368
+ : rootRoute?.css
369
+
370
+ return {
371
+ ...(rootRoute ?? {}),
372
+ ...(preloads?.length ? { preloads } : {}),
373
+ ...(scripts?.length ? { scripts } : {}),
374
+ ...(cssLinks?.length ? { css: cssLinks } : {}),
375
+ }
280
376
  }
281
377
 
282
378
  export function attachRouterServerSsrUtils({
283
379
  router,
284
380
  manifest,
285
381
  getRequestAssets,
286
- includeUnmatchedRouteAssets = true,
287
382
  }: {
288
383
  router: AnyRouter
289
- manifest: Manifest | undefined
290
- getRequestAssets?: () => Array<RouterManagedTag> | undefined
291
- includeUnmatchedRouteAssets?: boolean
384
+ manifest: ServerManifest | undefined
385
+ getRequestAssets?: () => ManifestRouteAssets | undefined
292
386
  }) {
293
387
  router.ssr = {
294
388
  get manifest() {
@@ -296,25 +390,54 @@ export function attachRouterServerSsrUtils({
296
390
 
297
391
  const requestAssets = getRequestAssets?.()
298
392
  const matches = router.stores.matches.get()
299
- const inlineCssAsset = getInlineCssAssetForMatches(manifest, matches)
393
+ const hasAssets = hasRequestAssets(requestAssets)
300
394
 
301
- if (!requestAssets?.length && !inlineCssAsset) {
395
+ if (!hasAssets && !manifest.inlineCss) {
302
396
  return manifest
303
397
  }
304
398
 
399
+ let inlineCssAsset: Manifest['inlineStyle'] | undefined
400
+ let routes = manifest.routes
401
+ if (manifest.inlineCss) {
402
+ const cacheKey = getMatchedRoutesCacheKey(matches)
403
+ const preparedManifest = getPreparedMatchedManifestRoutes(
404
+ manifest,
405
+ matches,
406
+ cacheKey,
407
+ )
408
+ inlineCssAsset = getInlineCssAssetForPreparedRoutes(
409
+ manifest,
410
+ preparedManifest,
411
+ )
412
+ if (preparedManifest.hasStrippedRoutes) {
413
+ routes = { ...manifest.routes, ...preparedManifest.routes }
414
+ }
415
+ }
416
+
417
+ if (!hasAssets) {
418
+ return {
419
+ ...(manifest.scriptFormat
420
+ ? { scriptFormat: manifest.scriptFormat }
421
+ : {}),
422
+ ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),
423
+ routes,
424
+ }
425
+ }
426
+
427
+ const rootRoute = routes[rootRouteId]
428
+
305
429
  // Merge request-scoped assets into root route without mutating cached manifest
306
430
  return {
307
- ...manifest,
431
+ ...(manifest.scriptFormat
432
+ ? { scriptFormat: manifest.scriptFormat }
433
+ : {}),
434
+ ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),
308
435
  routes: {
309
- ...manifest.routes,
310
- [rootRouteId]: {
311
- ...manifest.routes[rootRouteId],
312
- assets: [
313
- ...(requestAssets ?? []),
314
- ...(inlineCssAsset ? [inlineCssAsset] : []),
315
- ...(manifest.routes[rootRouteId]?.assets ?? []),
316
- ],
317
- },
436
+ ...routes,
437
+ [rootRouteId]: mergeRequestAssetsIntoRootRoute(
438
+ rootRoute,
439
+ requestAssets,
440
+ ),
318
441
  },
319
442
  }
320
443
  },
@@ -341,7 +464,7 @@ export function attachRouterServerSsrUtils({
341
464
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
342
465
  router.serverSsr!.injectHtml(html)
343
466
  },
344
- dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {
467
+ dehydrate: async (opts?: { requestAssets?: ManifestRouteAssets }) => {
345
468
  if (_dehydrated) {
346
469
  if (process.env.NODE_ENV !== 'production') {
347
470
  throw new Error('Invariant failed: router is already dehydrated!')
@@ -357,61 +480,36 @@ export function attachRouterServerSsrUtils({
357
480
  const matches = matchesToDehydrate.map(dehydrateMatch)
358
481
 
359
482
  let manifestToDehydrate: Manifest | undefined = undefined
360
- // For currently matched routes, send full manifest (preloads + assets).
361
- // For unmatched routes, include assets only when includeUnmatchedRouteAssets
362
- // is true; otherwise omit them entirely. Preloads for unmatched routes are
363
- // still excluded because they are handled via dynamic imports.
483
+ // Only currently matched routes are dehydrated. Other route assets are
484
+ // loaded through dynamic imports when those routes become active.
364
485
  if (manifest) {
365
- // Prod-only caching; in dev manifests may be replaced/updated (HMR)
366
- const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)
367
- const manifestCacheKey = `${currentRouteIdsList.join('\0')}\0includeUnmatchedRouteAssets=${includeUnmatchedRouteAssets}`
368
-
369
- let filteredRoutes: FilteredRoutes | undefined
370
-
371
- if (isProd) {
372
- filteredRoutes = getManifestCache(manifest).get(manifestCacheKey)
373
- }
374
-
375
- if (!filteredRoutes) {
376
- const currentRouteIds = new Set(currentRouteIdsList)
377
- const nextFilteredRoutes: FilteredRoutes = {}
378
-
379
- for (const routeId in manifest.routes) {
380
- const routeManifest = manifest.routes[routeId]!
381
- if (currentRouteIds.has(routeId)) {
382
- nextFilteredRoutes[routeId] = routeManifest
383
- } else if (
384
- includeUnmatchedRouteAssets &&
385
- routeManifest.assets &&
386
- routeManifest.assets.length > 0
387
- ) {
388
- nextFilteredRoutes[routeId] = {
389
- assets: routeManifest.assets,
390
- }
391
- }
392
- }
393
-
394
- filteredRoutes = stripInlinedStylesheetAssets(
395
- manifest,
396
- nextFilteredRoutes,
397
- matchesToDehydrate,
398
- )
399
-
400
- if (isProd) {
401
- getManifestCache(manifest).set(manifestCacheKey, filteredRoutes)
402
- }
403
- }
486
+ const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate)
487
+ const preparedManifest = getPreparedMatchedManifestRoutes(
488
+ manifest,
489
+ matchesToDehydrate,
490
+ cacheKey,
491
+ )
404
492
 
405
493
  manifestToDehydrate = {
406
- routes: { ...filteredRoutes },
494
+ ...(manifest.scriptFormat
495
+ ? { scriptFormat: manifest.scriptFormat }
496
+ : {}),
497
+ ...(preparedManifest.inlineCssHrefs
498
+ ? { inlineStyle: createInlineCssPlaceholderAsset() }
499
+ : {}),
500
+ routes: preparedManifest.routes,
407
501
  }
408
502
 
409
503
  // Merge request-scoped assets into root route (without mutating cached manifest)
410
- if (opts?.requestAssets?.length) {
504
+ const requestAssets = opts?.requestAssets
505
+ if (hasRequestAssets(requestAssets)) {
411
506
  const existingRoot = manifestToDehydrate.routes[rootRouteId]
412
- manifestToDehydrate.routes[rootRouteId] = {
413
- ...existingRoot,
414
- assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],
507
+ manifestToDehydrate.routes = {
508
+ ...manifestToDehydrate.routes,
509
+ [rootRouteId]: mergeRequestAssetsIntoRootRoute(
510
+ existingRoot,
511
+ requestAssets,
512
+ ),
415
513
  }
416
514
  }
417
515
  }