@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
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport {\n createInlineCssPlaceholderAsset,\n createInlineCssStyleAsset,\n getStylesheetHref,\n isInlinableStylesheet,\n} from '../manifest'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport { rootRouteId } from '../root'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type { Manifest, RouterManagedTag } from '../manifest'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n cleanup: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n }\n onSerializationFinished: {\n type: 'onSerializationFinished'\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match.globalNotFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private router: AnyRouter | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _pendingMicrotask = false\n\n constructor(router: AnyRouter) {\n this.router = router\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n // If barrier is lifted, schedule injection (if not already scheduled)\n if (this._scriptBarrierLifted && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0 && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before emitting onSerializationFinished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this._pendingMicrotask = false\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n takeAll() {\n const bufferedScripts = this._queue\n this._queue = []\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this._queue = []\n this.router = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype ManifestLRU = LRUCache<string, FilteredRoutes>\ntype InlineCssLRU = LRUCache<string, string>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<Manifest, ManifestLRU>()\nconst inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()\n\nfunction getManifestCache(manifest: Manifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, FilteredRoutes>(MANIFEST_CACHE_SIZE)\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nfunction getInlineCssCache(manifest: Manifest): InlineCssLRU {\n const cache = inlineCssCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, string>(MANIFEST_CACHE_SIZE)\n inlineCssCaches.set(manifest, newCache)\n return newCache\n}\n\nfunction getInlineCssHrefsForMatches(\n manifest: Manifest | undefined,\n matches: Array<AnyRouteMatch>,\n) {\n const styles = manifest?.inlineCss?.styles\n if (!styles) return []\n\n const seen = new Set<string>()\n const hrefs: Array<string> = []\n\n for (const match of matches) {\n const assets = manifest?.routes[match.routeId]?.assets ?? []\n for (const asset of assets) {\n const href = getStylesheetHref(asset)\n if (!href || seen.has(href) || styles[href] === undefined) {\n continue\n }\n seen.add(href)\n hrefs.push(href)\n }\n }\n\n return hrefs\n}\n\nfunction getInlineCssForHrefs(manifest: Manifest, hrefs: Array<string>) {\n const styles = manifest.inlineCss?.styles\n if (!styles || hrefs.length === 0) return undefined\n\n const cacheKey = hrefs.join('\\0')\n if (isProd) {\n const cached = getInlineCssCache(manifest).get(cacheKey)\n if (cached !== undefined) return cached\n }\n\n const css = hrefs.map((href) => styles[href]!).join('')\n\n if (isProd) {\n getInlineCssCache(manifest).set(cacheKey, css)\n }\n\n return css\n}\n\nfunction getInlineCssAssetForMatches(\n manifest: Manifest | undefined,\n matches: Array<AnyRouteMatch>,\n) {\n if (!manifest?.inlineCss) return undefined\n\n const hrefs = getInlineCssHrefsForMatches(manifest, matches)\n const css = getInlineCssForHrefs(manifest, hrefs)\n\n return css === undefined ? undefined : createInlineCssStyleAsset(css)\n}\n\nfunction stripInlinedStylesheetAssets(\n manifest: Manifest,\n routes: FilteredRoutes,\n matches: Array<AnyRouteMatch>,\n): FilteredRoutes {\n if (!manifest.inlineCss) {\n return routes\n }\n\n const nextRoutes: FilteredRoutes = {}\n\n for (const [routeId, route] of Object.entries(routes)) {\n const assets = route.assets?.filter(\n (asset) => !isInlinableStylesheet(manifest, asset),\n )\n\n const nextRoute = { ...route }\n if (assets) {\n if (assets.length > 0) {\n nextRoute.assets = assets\n } else {\n delete nextRoute.assets\n }\n }\n nextRoutes[routeId] = nextRoute\n }\n\n if (getInlineCssAssetForMatches(manifest, matches)) {\n const rootRoute = nextRoutes[rootRouteId] ?? {}\n nextRoutes[rootRouteId] = {\n ...rootRoute,\n assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],\n }\n }\n\n return nextRoutes\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n getRequestAssets,\n includeUnmatchedRouteAssets = true,\n}: {\n router: AnyRouter\n manifest: Manifest | undefined\n getRequestAssets?: () => Array<RouterManagedTag> | undefined\n includeUnmatchedRouteAssets?: boolean\n}) {\n router.ssr = {\n get manifest() {\n if (!manifest) return manifest\n\n const requestAssets = getRequestAssets?.()\n const matches = router.stores.matches.get()\n const inlineCssAsset = getInlineCssAssetForMatches(manifest, matches)\n\n if (!requestAssets?.length && !inlineCssAsset) {\n return manifest\n }\n\n // Merge request-scoped assets into root route without mutating cached manifest\n return {\n ...manifest,\n routes: {\n ...manifest.routes,\n [rootRouteId]: {\n ...manifest.routes[rootRouteId],\n assets: [\n ...(requestAssets ?? []),\n ...(inlineCssAsset ? [inlineCssAsset] : []),\n ...(manifest.routes[rootRouteId]?.assets ?? []),\n ],\n },\n },\n }\n },\n }\n let _dehydrated = false\n let _serializationFinished = false\n const renderFinishedListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const scriptBuffer = new ScriptBuffer(router)\n let injectedHtmlBuffer = ''\n\n router.serverSsr = {\n injectHtml: (html: string) => {\n if (!html) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n // Emit event to notify subscribers that new HTML is available\n router.emit({\n type: 'onInjectedHtml',\n })\n },\n injectScript: (script: string) => {\n if (!script) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n router.serverSsr!.injectHtml(html)\n },\n dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = router.stores.matches.get()\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // For currently matched routes, send full manifest (preloads + assets).\n // For unmatched routes, include assets only when includeUnmatchedRouteAssets\n // is true; otherwise omit them entirely. Preloads for unmatched routes are\n // still excluded because they are handled via dynamic imports.\n if (manifest) {\n // Prod-only caching; in dev manifests may be replaced/updated (HMR)\n const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId)\n const manifestCacheKey = `${currentRouteIdsList.join('\\0')}\\0includeUnmatchedRouteAssets=${includeUnmatchedRouteAssets}`\n\n let filteredRoutes: FilteredRoutes | undefined\n\n if (isProd) {\n filteredRoutes = getManifestCache(manifest).get(manifestCacheKey)\n }\n\n if (!filteredRoutes) {\n const currentRouteIds = new Set(currentRouteIdsList)\n const nextFilteredRoutes: FilteredRoutes = {}\n\n for (const routeId in manifest.routes) {\n const routeManifest = manifest.routes[routeId]!\n if (currentRouteIds.has(routeId)) {\n nextFilteredRoutes[routeId] = routeManifest\n } else if (\n includeUnmatchedRouteAssets &&\n routeManifest.assets &&\n routeManifest.assets.length > 0\n ) {\n nextFilteredRoutes[routeId] = {\n assets: routeManifest.assets,\n }\n }\n }\n\n filteredRoutes = stripInlinedStylesheetAssets(\n manifest,\n nextFilteredRoutes,\n matchesToDehydrate,\n )\n\n if (isProd) {\n getManifestCache(manifest).set(manifestCacheKey, filteredRoutes)\n }\n }\n\n manifestToDehydrate = {\n routes: { ...filteredRoutes },\n }\n\n // Merge request-scoped assets into root route (without mutating cached manifest)\n if (opts?.requestAssets?.length) {\n const existingRoot = manifestToDehydrate.routes[rootRouteId]\n manifestToDehydrate.routes[rootRouteId] = {\n ...existingRoot,\n assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],\n }\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n const signalSerializationComplete = () => {\n _serializationFinished = true\n try {\n serializationFinishedListeners.forEach((l) => l())\n router.emit({ type: 'onSerializationFinished' })\n } catch (err) {\n console.error('Serialization listener error:', err)\n } finally {\n serializationFinishedListeners.length = 0\n renderFinishedListeners.length = 0\n }\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n onError: (err: unknown) => {\n console.error('Serialization error:', err)\n if (err && (err as any).stack) {\n console.error((err as any).stack)\n }\n signalSerializationComplete()\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Flush all pending scripts synchronously before signaling completion\n // This ensures all scripts are injected before onSerializationFinished is emitted\n scriptBuffer.flush()\n signalSerializationComplete()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n onRenderFinished: (listener) => renderFinishedListeners.push(listener),\n onSerializationFinished: (listener) =>\n serializationFinishedListeners.push(listener),\n setRenderFinished: () => {\n // Wrap in try-catch to ensure scriptBuffer.liftBarrier() is always called\n try {\n renderFinishedListeners.forEach((l) => l())\n } catch (err) {\n console.error('Error in render finished listener:', err)\n } finally {\n // Clear listeners after calling them to prevent memory leaks\n renderFinishedListeners.length = 0\n }\n scriptBuffer.liftBarrier()\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple cleanup calls\n if (!router.serverSsr) return\n renderFinishedListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\n}\n"],"mappings":";;;;;;;;;;;;AAsCA,MAAM,WAAW;AAEjB,MAAM,aAAa,aAAa;AAChC,MAAM,WAAW,aAAa;AAC9B,MAAM,WAAW;AAEjB,SAAgB,eAAe,OAAuC;CACpE,MAAM,kBAAmC;EACvC,GAAG,oBAAoB,MAAM,GAAG;EAChC,GAAG,MAAM;EACT,GAAG,MAAM;EACV;AASD,MAAK,MAAM,CAAC,KAAK,cAPE;EACjB,CAAC,uBAAuB,IAAI;EAC5B,CAAC,cAAc,IAAI;EACnB,CAAC,SAAS,IAAI;EACd,CAAC,OAAO,MAAM;EACf,CAGC,KAAI,MAAM,SAAS,KAAA,EACjB,iBAAgB,aAAa,MAAM;AAGvC,KAAI,MAAM,eACR,iBAAgB,IAAI;AAEtB,QAAO;;AAGT,MAAM,kBAAkB,CACtB,wBAAwB,SAAS,EACjC,kBACD;AAED,IAAM,eAAN,MAAmB;CAOjB,YAAY,QAAmB;8BAJA;oBACV;2BACO;AAG1B,OAAK,SAAS;AAEd,OAAK,SAAS,gBAAgB,OAAO;;CAGvC,QAAQ,QAAgB;AACtB,MAAI,KAAK,WAAY;AACrB,OAAK,OAAO,KAAK,OAAO;AAExB,MAAI,KAAK,wBAAwB,CAAC,KAAK,mBAAmB;AACxD,QAAK,oBAAoB;AACzB,wBAAqB;AACnB,SAAK,oBAAoB;AACzB,SAAK,uBAAuB;KAC5B;;;CAIN,cAAc;AACZ,MAAI,KAAK,wBAAwB,KAAK,WAAY;AAClD,OAAK,uBAAuB;AAC5B,MAAI,KAAK,OAAO,SAAS,KAAK,CAAC,KAAK,mBAAmB;AACrD,QAAK,oBAAoB;AACzB,wBAAqB;AACnB,SAAK,oBAAoB;AACzB,SAAK,uBAAuB;KAC5B;;;;;;;;;;CAWN,QAAQ;AACN,MAAI,CAAC,KAAK,qBAAsB;AAChC,MAAI,KAAK,WAAY;AACrB,OAAK,oBAAoB;EACzB,MAAM,kBAAkB,KAAK,SAAS;AACtC,MAAI,mBAAmB,KAAK,QAAQ,UAClC,MAAK,OAAO,UAAU,aAAa,gBAAgB;;CAIvD,UAAU;EACR,MAAM,kBAAkB,KAAK;AAC7B,OAAK,SAAS,EAAE;AAChB,MAAI,gBAAgB,WAAW,EAC7B;AAGF,MAAI,gBAAgB,WAAW,EAC7B,QAAO,gBAAgB,KAAK;AAG9B,SAAO,gBAAgB,KAAK,IAAI,GAAG;;CAGrC,wBAAwB;AACtB,MAAI,KAAK,WAAY;AAErB,MAAI,KAAK,OAAO,WAAW,EAAG;EAC9B,MAAM,kBAAkB,KAAK,SAAS;AACtC,MAAI,mBAAmB,KAAK,QAAQ,UAClC,MAAK,OAAO,UAAU,aAAa,gBAAgB;;CAIvD,UAAU;AACR,OAAK,aAAa;AAClB,OAAK,SAAS,EAAE;AAChB,OAAK,SAAS,KAAA;;;AAIlB,MAAM,SAAA,QAAA,IAAA,aAAkC;AAOxC,MAAM,sBAAsB;AAC5B,MAAM,iCAAiB,IAAI,SAAgC;AAC3D,MAAM,kCAAkB,IAAI,SAAiC;AAE7D,SAAS,iBAAiB,UAAiC;CACzD,MAAM,QAAQ,eAAe,IAAI,SAAS;AAC1C,KAAI,MAAO,QAAO;CAClB,MAAM,WAAW,eAAuC,oBAAoB;AAC5E,gBAAe,IAAI,UAAU,SAAS;AACtC,QAAO;;AAGT,SAAS,kBAAkB,UAAkC;CAC3D,MAAM,QAAQ,gBAAgB,IAAI,SAAS;AAC3C,KAAI,MAAO,QAAO;CAClB,MAAM,WAAW,eAA+B,oBAAoB;AACpE,iBAAgB,IAAI,UAAU,SAAS;AACvC,QAAO;;AAGT,SAAS,4BACP,UACA,SACA;CACA,MAAM,SAAS,UAAU,WAAW;AACpC,KAAI,CAAC,OAAQ,QAAO,EAAE;CAEtB,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,QAAuB,EAAE;AAE/B,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,SAAS,UAAU,OAAO,MAAM,UAAU,UAAU,EAAE;AAC5D,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,OAAO,kBAAkB,MAAM;AACrC,OAAI,CAAC,QAAQ,KAAK,IAAI,KAAK,IAAI,OAAO,UAAU,KAAA,EAC9C;AAEF,QAAK,IAAI,KAAK;AACd,SAAM,KAAK,KAAK;;;AAIpB,QAAO;;AAGT,SAAS,qBAAqB,UAAoB,OAAsB;CACtE,MAAM,SAAS,SAAS,WAAW;AACnC,KAAI,CAAC,UAAU,MAAM,WAAW,EAAG,QAAO,KAAA;CAE1C,MAAM,WAAW,MAAM,KAAK,KAAK;AACjC,KAAI,QAAQ;EACV,MAAM,SAAS,kBAAkB,SAAS,CAAC,IAAI,SAAS;AACxD,MAAI,WAAW,KAAA,EAAW,QAAO;;CAGnC,MAAM,MAAM,MAAM,KAAK,SAAS,OAAO,MAAO,CAAC,KAAK,GAAG;AAEvD,KAAI,OACF,mBAAkB,SAAS,CAAC,IAAI,UAAU,IAAI;AAGhD,QAAO;;AAGT,SAAS,4BACP,UACA,SACA;AACA,KAAI,CAAC,UAAU,UAAW,QAAO,KAAA;CAGjC,MAAM,MAAM,qBAAqB,UADnB,4BAA4B,UAAU,QAAQ,CACX;AAEjD,QAAO,QAAQ,KAAA,IAAY,KAAA,IAAY,0BAA0B,IAAI;;AAGvE,SAAS,6BACP,UACA,QACA,SACgB;AAChB,KAAI,CAAC,SAAS,UACZ,QAAO;CAGT,MAAM,aAA6B,EAAE;AAErC,MAAK,MAAM,CAAC,SAAS,UAAU,OAAO,QAAQ,OAAO,EAAE;EACrD,MAAM,SAAS,MAAM,QAAQ,QAC1B,UAAU,CAAC,sBAAsB,UAAU,MAAM,CACnD;EAED,MAAM,YAAY,EAAE,GAAG,OAAO;AAC9B,MAAI,OACF,KAAI,OAAO,SAAS,EAClB,WAAU,SAAS;MAEnB,QAAO,UAAU;AAGrB,aAAW,WAAW;;AAGxB,KAAI,4BAA4B,UAAU,QAAQ,EAAE;EAClD,MAAM,YAAY,WAAA,eAA2B,EAAE;AAC/C,aAAW,eAAe;GACxB,GAAG;GACH,QAAQ,CAAC,iCAAiC,EAAE,GAAI,UAAU,UAAU,EAAE,CAAE;GACzE;;AAGH,QAAO;;AAGT,SAAgB,2BAA2B,EACzC,QACA,UACA,kBACA,8BAA8B,QAM7B;AACD,QAAO,MAAM,EACX,IAAI,WAAW;AACb,MAAI,CAAC,SAAU,QAAO;EAEtB,MAAM,gBAAgB,oBAAoB;EAE1C,MAAM,iBAAiB,4BAA4B,UADnC,OAAO,OAAO,QAAQ,KAAK,CAC0B;AAErE,MAAI,CAAC,eAAe,UAAU,CAAC,eAC7B,QAAO;AAIT,SAAO;GACL,GAAG;GACH,QAAQ;IACN,GAAG,SAAS;KACX,cAAc;KACb,GAAG,SAAS,OAAO;KACnB,QAAQ;MACN,GAAI,iBAAiB,EAAE;MACvB,GAAI,iBAAiB,CAAC,eAAe,GAAG,EAAE;MAC1C,GAAI,SAAS,OAAA,aAAqB,UAAU,EAAE;MAC/C;KACF;IACF;GACF;IAEJ;CACD,IAAI,cAAc;CAClB,IAAI,yBAAyB;CAC7B,MAAM,0BAA6C,EAAE;CACrD,MAAM,iCAAoD,EAAE;CAC5D,MAAM,eAAe,IAAI,aAAa,OAAO;CAC7C,IAAI,qBAAqB;AAEzB,QAAO,YAAY;EACjB,aAAa,SAAiB;AAC5B,OAAI,CAAC,KAAM;AAEX,yBAAsB;AAEtB,UAAO,KAAK,EACV,MAAM,kBACP,CAAC;;EAEJ,eAAe,WAAmB;AAChC,OAAI,CAAC,OAAQ;GACb,MAAM,OAAO,UAAU,OAAO,QAAQ,KAAK,QAAQ,WAAW,OAAO,QAAQ,IAAI,MAAM,KAAK,GAAG,GAAG,OAAO;AACzG,UAAO,UAAW,WAAW,KAAK;;EAEpC,WAAW,OAAO,SAAuD;AACvE,OAAI,aAAa;AACf,QAAA,QAAA,IAAA,aAA6B,aAC3B,OAAM,IAAI,MAAM,kDAAkD;AAGpE,eAAW;;GAEb,IAAI,qBAAqB,OAAO,OAAO,QAAQ,KAAK;AACpD,OAAI,OAAO,SAAS,CAElB,sBAAqB,mBAAmB,MAAM,GAAG,EAAE;GAErD,MAAM,UAAU,mBAAmB,IAAI,eAAe;GAEtD,IAAI,sBAA4C,KAAA;AAKhD,OAAI,UAAU;IAEZ,MAAM,sBAAsB,mBAAmB,KAAK,MAAM,EAAE,QAAQ;IACpE,MAAM,mBAAmB,GAAG,oBAAoB,KAAK,KAAK,CAAC,gCAAgC;IAE3F,IAAI;AAEJ,QAAI,OACF,kBAAiB,iBAAiB,SAAS,CAAC,IAAI,iBAAiB;AAGnE,QAAI,CAAC,gBAAgB;KACnB,MAAM,kBAAkB,IAAI,IAAI,oBAAoB;KACpD,MAAM,qBAAqC,EAAE;AAE7C,UAAK,MAAM,WAAW,SAAS,QAAQ;MACrC,MAAM,gBAAgB,SAAS,OAAO;AACtC,UAAI,gBAAgB,IAAI,QAAQ,CAC9B,oBAAmB,WAAW;eAE9B,+BACA,cAAc,UACd,cAAc,OAAO,SAAS,EAE9B,oBAAmB,WAAW,EAC5B,QAAQ,cAAc,QACvB;;AAIL,sBAAiB,6BACf,UACA,oBACA,mBACD;AAED,SAAI,OACF,kBAAiB,SAAS,CAAC,IAAI,kBAAkB,eAAe;;AAIpE,0BAAsB,EACpB,QAAQ,EAAE,GAAG,gBAAgB,EAC9B;AAGD,QAAI,MAAM,eAAe,QAAQ;KAC/B,MAAM,eAAe,oBAAoB,OAAO;AAChD,yBAAoB,OAAO,eAAe;MACxC,GAAG;MACH,QAAQ,CAAC,GAAG,KAAK,eAAe,GAAI,cAAc,UAAU,EAAE,CAAE;MACjE;;;GAGL,MAAM,mBAAqC;IACzC,UAAU;IACV;IACD;GACD,MAAM,cAAc,mBAAmB,mBAAmB,SAAS,IAAI;AACvE,OAAI,YACF,kBAAiB,cAAc,oBAAoB,YAAY;GAEjE,MAAM,iBAAiB,MAAM,OAAO,QAAQ,aAAa;AACzD,OAAI,eACF,kBAAiB,iBAAiB;AAEpC,iBAAc;GAEd,MAAM,eAAe,EAAE,QAAQ,OAAO;GACtC,MAAM,wBAAwB,OAAO,QAAQ;GAG7C,MAAM,UAAU,wBACZ,sBACG,KAAK,MAAM,qCAAqB,GAAG,aAAa,CAAC,CACjD,OAAO,sBAAsB,GAChC;GAEJ,MAAM,oCAAoC;AACxC,6BAAyB;AACzB,QAAI;AACF,oCAA+B,SAAS,MAAM,GAAG,CAAC;AAClD,YAAO,KAAK,EAAE,MAAM,2BAA2B,CAAC;aACzC,KAAK;AACZ,aAAQ,MAAM,iCAAiC,IAAI;cAC3C;AACR,oCAA+B,SAAS;AACxC,6BAAwB,SAAS;;;AAIrC,wBAAqB,kBAAkB;IACrC,sBAAM,IAAI,KAAK;IACf;IACA,cAAc,MAAM,YAAY;KAC9B,IAAI,aAAa,UAAU,aAAa,OAAO;AAC/C,SAAI,aAAa,OACf,cAAa,WAAW,aAAa;AAEvC,kBAAa,QAAQ,WAAW;;IAElC,UAAU,QAAiB;AACzB,aAAQ,MAAM,wBAAwB,IAAI;AAC1C,SAAI,OAAQ,IAAY,MACtB,SAAQ,MAAO,IAAY,MAAM;AAEnC,kCAA6B;;IAE/B,SAAS;IACT,cAAc;AACZ,kBAAa,QAAQ,aAAa,OAAO;AAGzC,kBAAa,OAAO;AACpB,kCAA6B;;IAEhC,CAAC;;EAEJ,eAAe;AACb,UAAO;;EAET,0BAA0B;AACxB,UAAO;;EAET,mBAAmB,aAAa,wBAAwB,KAAK,SAAS;EACtE,0BAA0B,aACxB,+BAA+B,KAAK,SAAS;EAC/C,yBAAyB;AAEvB,OAAI;AACF,4BAAwB,SAAS,MAAM,GAAG,CAAC;YACpC,KAAK;AACZ,YAAQ,MAAM,sCAAsC,IAAI;aAChD;AAER,4BAAwB,SAAS;;AAEnC,gBAAa,aAAa;;EAE5B,sBAAsB;GACpB,MAAM,UAAU,aAAa,SAAS;AAUtC,UAT+C;IAC7C,KAAK;IACL,OAAO;KACL,OAAO,OAAO,QAAQ,KAAK;KAC3B,WAAW;KACX,IAAI;KACL;IACD,UAAU;IACX;;EAGH,oBAAoB;AAClB,gBAAa,aAAa;;EAE5B,mBAAmB;AACjB,OAAI,CAAC,mBACH;GAEF,MAAM,WAAW;AACjB,wBAAqB;AACrB,UAAO;;EAET,UAAU;AAER,OAAI,CAAC,OAAO,UAAW;AACvB,2BAAwB,SAAS;AACjC,kCAA+B,SAAS;AACxC,wBAAqB;AACrB,gBAAa,SAAS;AACtB,UAAO,YAAY,KAAA;;EAEtB;;;;;;;;;;;;;;;;AAiBH,SAAgB,UAAU,SAAkB;AAC1C,KAAI;AACF,SAAO,IAAI,IAAI,QAAQ,IAAI,CAAC;SACtB;AACR,QAAO;;AAST,SAAgB,iBAAiB,KAAmB,MAAqB;AAEvE,KAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,QAAQ,MAAM,MAAM;CAE3D,MAAM,SAAS,IAAI,IAAI,KAAK,KAAK;CACjC,MAAM,EAAE,MAAM,iBAAiB,+BAA+B,WAC5D,OAAO,SACR;CACD,MAAM,eAAe,IAAI,gBAAgB,OAAO,OAAO;CACvD,MAAM,iBACJ,mBACC,aAAa,OAAO,IAAI,MAAM,MAC/B,aAAa,UAAU,GACvB,OAAO;AAET,QAAO;EACL,KAAK,IAAI,IAAI,gBAAgB,OAAO,OAAO;EAC3C;EACD"}
1
+ {"version":3,"file":"ssr-server.js","names":[],"sources":["../../../src/ssr/ssr-server.ts"],"sourcesContent":["import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'\nimport { invariant } from '../invariant'\nimport {\n createInlineCssPlaceholderAsset,\n createInlineCssStyleAsset,\n getStylesheetHref,\n} from '../manifest'\nimport { decodePath } from '../utils'\nimport { createLRUCache } from '../lru-cache'\nimport { rootRouteId } from '../root'\nimport minifiedTsrBootStrapScript from './tsrScript?script-string'\nimport { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'\nimport { dehydrateSsrMatchId } from './ssr-match-id'\nimport { defaultSerovalPlugins } from './serializer/seroval-plugins'\nimport { makeSsrSerovalPlugin } from './serializer/transformer'\nimport type { LRUCache } from '../lru-cache'\nimport type { DehydratedMatch, DehydratedRouter } from './types'\nimport type { AnySerializationAdapter } from './serializer/transformer'\nimport type { AnyRouter } from '../router'\nimport type { AnyRouteMatch } from '../Matches'\nimport type {\n Manifest,\n ManifestRoute,\n ManifestRouteAssets,\n RouterManagedTag,\n ServerManifest,\n} from '../manifest'\n\ndeclare module '../router' {\n interface ServerSsr {\n setRenderFinished: () => void\n cleanup: () => void\n }\n interface RouterEvents {\n onInjectedHtml: {\n type: 'onInjectedHtml'\n }\n onSerializationFinished: {\n type: 'onSerializationFinished'\n }\n }\n}\n\nconst SCOPE_ID = 'tsr'\n\nconst TSR_PREFIX = GLOBAL_TSR + '.router='\nconst P_PREFIX = GLOBAL_TSR + '.p(()=>'\nconst P_SUFFIX = ')'\n\nexport function dehydrateMatch(match: AnyRouteMatch): DehydratedMatch {\n const dehydratedMatch: DehydratedMatch = {\n i: dehydrateSsrMatchId(match.id),\n u: match.updatedAt,\n s: match.status,\n }\n\n const properties = [\n ['__beforeLoadContext', 'b'],\n ['loaderData', 'l'],\n ['error', 'e'],\n ['ssr', 'ssr'],\n ] as const\n\n for (const [key, shorthand] of properties) {\n if (match[key] !== undefined) {\n dehydratedMatch[shorthand] = match[key]\n }\n }\n if (match.globalNotFound) {\n dehydratedMatch.g = true\n }\n return dehydratedMatch\n}\n\nconst INITIAL_SCRIPTS = [\n getCrossReferenceHeader(SCOPE_ID),\n minifiedTsrBootStrapScript,\n]\n\nclass ScriptBuffer {\n private router: AnyRouter | undefined\n private _queue: Array<string>\n private _scriptBarrierLifted = false\n private _cleanedUp = false\n private _pendingMicrotask = false\n\n constructor(router: AnyRouter) {\n this.router = router\n // Copy INITIAL_SCRIPTS to avoid mutating the shared array\n this._queue = INITIAL_SCRIPTS.slice()\n }\n\n enqueue(script: string) {\n if (this._cleanedUp) return\n this._queue.push(script)\n // If barrier is lifted, schedule injection (if not already scheduled)\n if (this._scriptBarrierLifted && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n liftBarrier() {\n if (this._scriptBarrierLifted || this._cleanedUp) return\n this._scriptBarrierLifted = true\n if (this._queue.length > 0 && !this._pendingMicrotask) {\n this._pendingMicrotask = true\n queueMicrotask(() => {\n this._pendingMicrotask = false\n this.injectBufferedScripts()\n })\n }\n }\n\n /**\n * Flushes any pending scripts synchronously.\n * Call this before emitting onSerializationFinished to ensure all scripts are injected.\n *\n * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,\n * scripts should remain in the queue so takeBufferedScripts() can retrieve them\n */\n flush() {\n if (!this._scriptBarrierLifted) return\n if (this._cleanedUp) return\n this._pendingMicrotask = false\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n takeAll() {\n const bufferedScripts = this._queue\n this._queue = []\n if (bufferedScripts.length === 0) {\n return undefined\n }\n // Optimization: if only one script, avoid join\n if (bufferedScripts.length === 1) {\n return bufferedScripts[0] + ';document.currentScript.remove()'\n }\n // Append cleanup script and join - avoid push() to not mutate then iterate\n return bufferedScripts.join(';') + ';document.currentScript.remove()'\n }\n\n injectBufferedScripts() {\n if (this._cleanedUp) return\n // Early return if queue is empty (avoids unnecessary takeAll() call)\n if (this._queue.length === 0) return\n const scriptsToInject = this.takeAll()\n if (scriptsToInject && this.router?.serverSsr) {\n this.router.serverSsr.injectScript(scriptsToInject)\n }\n }\n\n cleanup() {\n this._cleanedUp = true\n this._queue = []\n this.router = undefined\n }\n}\n\nconst isProd = process.env.NODE_ENV === 'production'\n\ntype FilteredRoutes = Manifest['routes']\n\ntype PreparedMatchedManifestRoutes = {\n routes: FilteredRoutes\n hasStrippedRoutes: boolean\n inlineCssHrefs?: Array<string>\n inlineCss?: string\n}\n\ntype ManifestLRU = LRUCache<string, PreparedMatchedManifestRoutes>\n\nconst MANIFEST_CACHE_SIZE = 100\nconst manifestCaches = new WeakMap<ServerManifest, ManifestLRU>()\n\nfunction getManifestCache(manifest: ServerManifest): ManifestLRU {\n const cache = manifestCaches.get(manifest)\n if (cache) return cache\n const newCache = createLRUCache<string, PreparedMatchedManifestRoutes>(\n MANIFEST_CACHE_SIZE,\n )\n manifestCaches.set(manifest, newCache)\n return newCache\n}\n\nfunction getInlineCssForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n if (preparedRoutes.inlineCss !== undefined) return preparedRoutes.inlineCss\n\n const styles = manifest.inlineCss?.styles\n const hrefs = preparedRoutes.inlineCssHrefs\n if (!styles || !hrefs?.length) return undefined\n\n let css = ''\n for (const href of hrefs) {\n css += styles[href]!\n }\n\n preparedRoutes.inlineCss = css\n return css\n}\n\nfunction getInlineCssAssetForPreparedRoutes(\n manifest: ServerManifest,\n preparedRoutes: PreparedMatchedManifestRoutes,\n) {\n const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes)\n\n return css === undefined ? undefined : createInlineCssStyleAsset(css)\n}\n\nfunction getMatchedRoutesCacheKey(matches: Array<AnyRouteMatch>) {\n let cacheKey = ''\n for (let i = 0; i < matches.length; i++) {\n cacheKey += (i === 0 ? '' : '\\0') + matches[i]!.routeId\n }\n return cacheKey\n}\n\nfunction getPreparedMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n cacheKey: string,\n) {\n if (isProd) {\n const cached = getManifestCache(manifest).get(cacheKey)\n if (cached) {\n return cached\n }\n }\n\n const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches)\n\n if (isProd) {\n getManifestCache(manifest).set(cacheKey, preparedRoutes)\n }\n\n return preparedRoutes\n}\n\nfunction prepareMatchedManifestRoutes(\n manifest: ServerManifest,\n matches: Array<AnyRouteMatch>,\n): PreparedMatchedManifestRoutes {\n const inlineStyles = manifest.inlineCss?.styles\n const routes: FilteredRoutes = {}\n\n if (!inlineStyles) {\n for (const match of matches) {\n const route = manifest.routes[match.routeId]\n if (route) {\n routes[match.routeId] = route\n }\n }\n return { routes, hasStrippedRoutes: false }\n }\n\n const inlineCssHrefs: Array<string> = []\n const seenInlineCssHrefs = new Set<string>()\n let hasStrippedRoutes = false\n\n for (const match of matches) {\n const routeId = match.routeId\n const route = manifest.routes[routeId]\n if (!route) {\n continue\n }\n\n const nextRoute = stripInlinedStylesheetAssetsFromRoute(\n inlineStyles,\n route,\n inlineCssHrefs,\n seenInlineCssHrefs,\n )\n\n if (nextRoute !== route) {\n hasStrippedRoutes = true\n }\n routes[routeId] = nextRoute\n }\n\n return {\n routes,\n hasStrippedRoutes,\n ...(inlineCssHrefs.length ? { inlineCssHrefs } : {}),\n }\n}\n\nfunction stripInlinedStylesheetAssetsFromRoute(\n inlineStyles: Record<string, string>,\n route: ManifestRoute,\n inlineCssHrefs: Array<string>,\n seenInlineCssHrefs: Set<string>,\n): ManifestRoute {\n const css = route.css\n if (!css) {\n return route\n }\n\n if (css.length === 0) {\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n }\n\n let cssLinks: typeof css | undefined\n for (let i = 0; i < css.length; i++) {\n const link = css[i]!\n const href = getStylesheetHref(link)\n if (inlineStyles[href] === undefined) {\n if (cssLinks) {\n cssLinks.push(link)\n }\n continue\n }\n\n if (!seenInlineCssHrefs.has(href)) {\n seenInlineCssHrefs.add(href)\n inlineCssHrefs.push(href)\n }\n\n if (!cssLinks) {\n cssLinks = css.slice(0, i)\n }\n }\n\n if (!cssLinks) {\n return route\n }\n\n if (cssLinks.length > 0) {\n return { ...route, css: cssLinks }\n }\n\n const nextRoute = { ...route }\n delete nextRoute.css\n return nextRoute\n}\n\nfunction hasRouteAssets(route: ManifestRoute) {\n return !!route.scripts?.length || !!route.css?.length\n}\n\nfunction hasRequestAssets(assets: ManifestRouteAssets | undefined) {\n return !!assets && (!!assets.preloads?.length || hasRouteAssets(assets))\n}\n\nfunction mergeRequestAssetsIntoRootRoute(\n rootRoute: ManifestRoute | undefined,\n requestAssets: ManifestRouteAssets | undefined,\n): ManifestRoute {\n const preloads = requestAssets?.preloads?.length\n ? [...requestAssets.preloads, ...(rootRoute?.preloads ?? [])]\n : rootRoute?.preloads\n const scripts = requestAssets?.scripts?.length\n ? [...requestAssets.scripts, ...(rootRoute?.scripts ?? [])]\n : rootRoute?.scripts\n const cssLinks = requestAssets?.css?.length\n ? [...requestAssets.css, ...(rootRoute?.css ?? [])]\n : rootRoute?.css\n\n return {\n ...(rootRoute ?? {}),\n ...(preloads?.length ? { preloads } : {}),\n ...(scripts?.length ? { scripts } : {}),\n ...(cssLinks?.length ? { css: cssLinks } : {}),\n }\n}\n\nexport function attachRouterServerSsrUtils({\n router,\n manifest,\n getRequestAssets,\n}: {\n router: AnyRouter\n manifest: ServerManifest | undefined\n getRequestAssets?: () => ManifestRouteAssets | undefined\n}) {\n router.ssr = {\n get manifest() {\n if (!manifest) return manifest\n\n const requestAssets = getRequestAssets?.()\n const matches = router.stores.matches.get()\n const hasAssets = hasRequestAssets(requestAssets)\n\n if (!hasAssets && !manifest.inlineCss) {\n return manifest\n }\n\n let inlineCssAsset: Manifest['inlineStyle'] | undefined\n let routes = manifest.routes\n if (manifest.inlineCss) {\n const cacheKey = getMatchedRoutesCacheKey(matches)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matches,\n cacheKey,\n )\n inlineCssAsset = getInlineCssAssetForPreparedRoutes(\n manifest,\n preparedManifest,\n )\n if (preparedManifest.hasStrippedRoutes) {\n routes = { ...manifest.routes, ...preparedManifest.routes }\n }\n }\n\n if (!hasAssets) {\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes,\n }\n }\n\n const rootRoute = routes[rootRouteId]\n\n // Merge request-scoped assets into root route without mutating cached manifest\n return {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(inlineCssAsset ? { inlineStyle: inlineCssAsset } : {}),\n routes: {\n ...routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n rootRoute,\n requestAssets,\n ),\n },\n }\n },\n }\n let _dehydrated = false\n let _serializationFinished = false\n const renderFinishedListeners: Array<() => void> = []\n const serializationFinishedListeners: Array<() => void> = []\n const scriptBuffer = new ScriptBuffer(router)\n let injectedHtmlBuffer = ''\n\n router.serverSsr = {\n injectHtml: (html: string) => {\n if (!html) return\n // Buffer the HTML so it can be retrieved via takeBufferedHtml()\n injectedHtmlBuffer += html\n // Emit event to notify subscribers that new HTML is available\n router.emit({\n type: 'onInjectedHtml',\n })\n },\n injectScript: (script: string) => {\n if (!script) return\n const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`\n router.serverSsr!.injectHtml(html)\n },\n dehydrate: async (opts?: { requestAssets?: ManifestRouteAssets }) => {\n if (_dehydrated) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error('Invariant failed: router is already dehydrated!')\n }\n\n invariant()\n }\n let matchesToDehydrate = router.stores.matches.get()\n if (router.isShell()) {\n // In SPA mode we only want to dehydrate the root match\n matchesToDehydrate = matchesToDehydrate.slice(0, 1)\n }\n const matches = matchesToDehydrate.map(dehydrateMatch)\n\n let manifestToDehydrate: Manifest | undefined = undefined\n // Only currently matched routes are dehydrated. Other route assets are\n // loaded through dynamic imports when those routes become active.\n if (manifest) {\n const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate)\n const preparedManifest = getPreparedMatchedManifestRoutes(\n manifest,\n matchesToDehydrate,\n cacheKey,\n )\n\n manifestToDehydrate = {\n ...(manifest.scriptFormat\n ? { scriptFormat: manifest.scriptFormat }\n : {}),\n ...(preparedManifest.inlineCssHrefs\n ? { inlineStyle: createInlineCssPlaceholderAsset() }\n : {}),\n routes: preparedManifest.routes,\n }\n\n // Merge request-scoped assets into root route (without mutating cached manifest)\n const requestAssets = opts?.requestAssets\n if (hasRequestAssets(requestAssets)) {\n const existingRoot = manifestToDehydrate.routes[rootRouteId]\n manifestToDehydrate.routes = {\n ...manifestToDehydrate.routes,\n [rootRouteId]: mergeRequestAssetsIntoRootRoute(\n existingRoot,\n requestAssets,\n ),\n }\n }\n }\n const dehydratedRouter: DehydratedRouter = {\n manifest: manifestToDehydrate,\n matches,\n }\n const lastMatchId = matchesToDehydrate[matchesToDehydrate.length - 1]?.id\n if (lastMatchId) {\n dehydratedRouter.lastMatchId = dehydrateSsrMatchId(lastMatchId)\n }\n const dehydratedData = await router.options.dehydrate?.()\n if (dehydratedData) {\n dehydratedRouter.dehydratedData = dehydratedData\n }\n _dehydrated = true\n\n const trackPlugins = { didRun: false }\n const serializationAdapters = router.options.serializationAdapters as\n | Array<AnySerializationAdapter>\n | undefined\n const plugins = serializationAdapters\n ? serializationAdapters\n .map((t) => makeSsrSerovalPlugin(t, trackPlugins))\n .concat(defaultSerovalPlugins)\n : defaultSerovalPlugins\n\n const signalSerializationComplete = () => {\n _serializationFinished = true\n try {\n serializationFinishedListeners.forEach((l) => l())\n router.emit({ type: 'onSerializationFinished' })\n } catch (err) {\n console.error('Serialization listener error:', err)\n } finally {\n serializationFinishedListeners.length = 0\n renderFinishedListeners.length = 0\n }\n }\n\n crossSerializeStream(dehydratedRouter, {\n refs: new Map(),\n plugins,\n onSerialize: (data, initial) => {\n let serialized = initial ? TSR_PREFIX + data : data\n if (trackPlugins.didRun) {\n serialized = P_PREFIX + serialized + P_SUFFIX\n }\n scriptBuffer.enqueue(serialized)\n },\n onError: (err: unknown) => {\n console.error('Serialization error:', err)\n if (err && (err as any).stack) {\n console.error((err as any).stack)\n }\n signalSerializationComplete()\n },\n scopeId: SCOPE_ID,\n onDone: () => {\n scriptBuffer.enqueue(GLOBAL_TSR + '.e()')\n // Flush all pending scripts synchronously before signaling completion\n // This ensures all scripts are injected before onSerializationFinished is emitted\n scriptBuffer.flush()\n signalSerializationComplete()\n },\n })\n },\n isDehydrated() {\n return _dehydrated\n },\n isSerializationFinished() {\n return _serializationFinished\n },\n onRenderFinished: (listener) => renderFinishedListeners.push(listener),\n onSerializationFinished: (listener) =>\n serializationFinishedListeners.push(listener),\n setRenderFinished: () => {\n // Wrap in try-catch to ensure scriptBuffer.liftBarrier() is always called\n try {\n renderFinishedListeners.forEach((l) => l())\n } catch (err) {\n console.error('Error in render finished listener:', err)\n } finally {\n // Clear listeners after calling them to prevent memory leaks\n renderFinishedListeners.length = 0\n }\n scriptBuffer.liftBarrier()\n },\n takeBufferedScripts() {\n const scripts = scriptBuffer.takeAll()\n const serverBufferedScript: RouterManagedTag = {\n tag: 'script',\n attrs: {\n nonce: router.options.ssr?.nonce,\n className: '$tsr',\n id: TSR_SCRIPT_BARRIER_ID,\n },\n children: scripts,\n }\n return serverBufferedScript\n },\n liftScriptBarrier() {\n scriptBuffer.liftBarrier()\n },\n takeBufferedHtml() {\n if (!injectedHtmlBuffer) {\n return undefined\n }\n const buffered = injectedHtmlBuffer\n injectedHtmlBuffer = ''\n return buffered\n },\n cleanup() {\n // Guard against multiple cleanup calls\n if (!router.serverSsr) return\n renderFinishedListeners.length = 0\n serializationFinishedListeners.length = 0\n injectedHtmlBuffer = ''\n scriptBuffer.cleanup()\n router.serverSsr = undefined\n },\n }\n}\n\n/**\n * Get the origin for the request.\n *\n * SECURITY: We intentionally do NOT trust the Origin header for determining\n * the router's origin. The Origin header can be spoofed by attackers, which\n * could lead to SSRF-like vulnerabilities where redirects are constructed\n * using a malicious origin (CVE-2024-34351).\n *\n * Instead, we derive the origin from request.url, which is typically set by\n * the server infrastructure (not client-controlled headers).\n *\n * For applications behind proxies that need to trust forwarded headers,\n * use the router's `origin` option to explicitly configure a trusted origin.\n */\nexport function getOrigin(request: Request) {\n try {\n return new URL(request.url).origin\n } catch {}\n return 'http://localhost'\n}\n\n// server and browser can decode/encode characters differently in paths and search params.\n// Server generally strictly follows the WHATWG URL Standard, while browsers may differ for legacy reasons.\n// for example, in paths \"|\" is not encoded on the server but is encoded on chromium (and not on firefox) while \"대\" is encoded on both sides.\n// Another anomaly is that in Node new URLSearchParams and new URL also decode/encode characters differently.\n// new URLSearchParams() encodes \"|\" while new URL() does not, and in this instance\n// chromium treats search params differently than paths, i.e. \"|\" is not encoded in search params.\nexport function getNormalizedURL(url: string | URL, base?: string | URL) {\n // ensure backslashes are encoded correctly in the URL\n if (typeof url === 'string') url = url.replace('\\\\', '%5C')\n\n const rawUrl = new URL(url, base)\n const { path: decodedPathname, handledProtocolRelativeURL } = decodePath(\n rawUrl.pathname,\n )\n const searchParams = new URLSearchParams(rawUrl.search)\n const normalizedHref =\n decodedPathname +\n (searchParams.size > 0 ? '?' : '') +\n searchParams.toString() +\n rawUrl.hash\n\n return {\n url: new URL(normalizedHref, rawUrl.origin),\n handledProtocolRelativeURL,\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.171.4",
3
+ "version": "1.171.6",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -70,17 +70,37 @@ export type {
70
70
  export type { ParsedLocation } from './location'
71
71
  export type {
72
72
  Manifest,
73
+ ServerManifest,
74
+ ManifestRoute,
75
+ ManifestRouteAssets,
76
+ ServerManifestRoute,
77
+ ManifestCssLink,
78
+ ManifestInlineCss,
79
+ ServerManifestInlineCss,
80
+ InlineCssTemplate,
81
+ ManifestScript,
73
82
  RouterManagedTag,
83
+ RouterManagedTitleTag,
84
+ RouterManagedMetaTag,
85
+ RouterManagedInlineCssTag,
86
+ RouterManagedScriptTag,
87
+ RouterManagedLinkTag,
88
+ RouterManagedStyleTag,
74
89
  AssetCrossOrigin,
75
90
  AssetCrossOriginConfig,
76
91
  ManifestAssetLink,
92
+ ScriptFormat,
77
93
  } from './manifest'
78
94
  export {
95
+ DEV_STYLES_ATTR,
96
+ appendUniqueUserTags,
79
97
  createInlineCssStyleAsset,
80
98
  getAssetCrossOrigin,
99
+ getManifestScriptFormat,
100
+ getScriptPreloadAttrs,
81
101
  getStylesheetHref,
82
- isInlinableStylesheet,
83
102
  resolveManifestAssetLink,
103
+ resolveManifestCssLink,
84
104
  } from './manifest'
85
105
  export { isMatch } from './Matches'
86
106
  export type {
package/src/manifest.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export type AssetCrossOrigin = 'anonymous' | 'use-credentials'
2
+ export type ScriptFormat = 'module' | 'iife'
3
+
4
+ export const DEV_STYLES_ATTR = 'data-tanstack-router-dev-styles'
2
5
 
3
6
  export type AssetCrossOriginConfig =
4
7
  | AssetCrossOrigin
5
- | Partial<Record<'modulepreload' | 'stylesheet', AssetCrossOrigin>>
8
+ | Partial<Record<'script' | 'stylesheet', AssetCrossOrigin>>
6
9
 
7
10
  export type ManifestAssetLink =
8
11
  | string
@@ -13,7 +16,7 @@ export type ManifestAssetLink =
13
16
 
14
17
  export function getAssetCrossOrigin(
15
18
  assetCrossOrigin: AssetCrossOriginConfig | undefined,
16
- kind: 'modulepreload' | 'stylesheet',
19
+ kind: 'script' | 'stylesheet',
17
20
  ): AssetCrossOrigin | undefined {
18
21
  if (!assetCrossOrigin) {
19
22
  return undefined
@@ -26,6 +29,35 @@ export function getAssetCrossOrigin(
26
29
  return assetCrossOrigin[kind]
27
30
  }
28
31
 
32
+ export function getManifestScriptFormat(
33
+ manifest: { scriptFormat?: ScriptFormat } | undefined,
34
+ ): ScriptFormat {
35
+ return manifest?.scriptFormat ?? 'module'
36
+ }
37
+
38
+ export function getScriptPreloadAttrs(
39
+ manifest: { scriptFormat?: ScriptFormat } | undefined,
40
+ link: ManifestAssetLink,
41
+ assetCrossOrigin?: AssetCrossOriginConfig,
42
+ ): {
43
+ rel: 'modulepreload' | 'preload'
44
+ as?: 'script'
45
+ href: string
46
+ crossOrigin?: AssetCrossOrigin
47
+ } {
48
+ const preloadLink = resolveManifestAssetLink(link)
49
+ const crossOrigin =
50
+ getAssetCrossOrigin(assetCrossOrigin, 'script') ?? preloadLink.crossOrigin
51
+
52
+ return {
53
+ ...(getManifestScriptFormat(manifest) === 'iife'
54
+ ? { rel: 'preload', as: 'script' }
55
+ : { rel: 'modulepreload' }),
56
+ href: preloadLink.href,
57
+ ...(crossOrigin ? { crossOrigin } : {}),
58
+ }
59
+ }
60
+
29
61
  export function resolveManifestAssetLink(link: ManifestAssetLink) {
30
62
  if (typeof link === 'string') {
31
63
  return { href: link, crossOrigin: undefined }
@@ -35,87 +67,147 @@ export function resolveManifestAssetLink(link: ManifestAssetLink) {
35
67
  }
36
68
 
37
69
  export type Manifest = {
38
- inlineCss?: {
39
- styles: Record<string, string>
40
- templates?: Record<
41
- string,
42
- {
43
- strings: Array<string>
44
- urls: Array<string>
45
- }
46
- >
47
- }
48
- routes: Record<
49
- string,
50
- {
51
- filePath?: string
52
- preloads?: Array<ManifestAssetLink>
53
- assets?: Array<RouterManagedTag>
54
- }
55
- >
70
+ scriptFormat?: ScriptFormat
71
+ inlineStyle?: ManifestInlineCss
72
+ routes: Record<string, ManifestRoute>
73
+ }
74
+
75
+ export type ServerManifest = {
76
+ scriptFormat?: ScriptFormat
77
+ inlineCss?: ServerManifestInlineCss
78
+ routes: Record<string, ServerManifestRoute>
79
+ }
80
+
81
+ export type ServerManifestInlineCss = {
82
+ styles: Record<string, string>
83
+ templates?: Record<string, InlineCssTemplate>
84
+ }
85
+
86
+ export type InlineCssTemplate = {
87
+ strings: Array<string>
88
+ urls: Array<string>
89
+ }
90
+
91
+ export type ManifestRoute = {
92
+ filePath?: string
93
+ preloads?: Array<ManifestAssetLink>
94
+ scripts?: Array<ManifestScript>
95
+ css?: Array<ManifestCssLink>
96
+ }
97
+
98
+ export type ServerManifestRoute = ManifestRoute
99
+
100
+ export type ManifestRouteAssets = Pick<
101
+ ManifestRoute,
102
+ 'preloads' | 'scripts' | 'css'
103
+ >
104
+
105
+ export type RouterManagedTitleTag = {
106
+ tag: 'title'
107
+ attrs?: Record<string, any>
108
+ children: string
109
+ }
110
+
111
+ export type RouterManagedMetaTag = {
112
+ tag: 'meta'
113
+ attrs?: Record<string, any>
114
+ children?: never
115
+ }
116
+
117
+ export type RouterManagedLinkTag = {
118
+ tag: 'link'
119
+ attrs?: Record<string, any>
120
+ children?: never
121
+ }
122
+
123
+ export type RouterManagedScriptTag = {
124
+ tag: 'script'
125
+ attrs?: Record<string, any>
126
+ children?: string
127
+ }
128
+
129
+ export type ManifestScript = Omit<RouterManagedScriptTag, 'tag'>
130
+
131
+ export type RouterManagedStyleTag = {
132
+ tag: 'style'
133
+ attrs?: Record<string, any>
134
+ children?: string
135
+ inlineCss?: true
56
136
  }
57
137
 
58
138
  export type RouterManagedTag =
59
- | {
60
- tag: 'title'
61
- attrs?: Record<string, any>
62
- children: string
63
- }
64
- | {
65
- tag: 'meta' | 'link'
66
- attrs?: Record<string, any>
67
- children?: never
68
- }
69
- | {
70
- tag: 'script'
71
- attrs?: Record<string, any>
72
- children?: string
139
+ | RouterManagedTitleTag
140
+ | RouterManagedMetaTag
141
+ | RouterManagedLinkTag
142
+ | RouterManagedScriptTag
143
+ | RouterManagedStyleTag
144
+
145
+ export function appendUniqueUserTags(
146
+ target: Array<RouterManagedTag>,
147
+ tags: Array<RouterManagedTag>,
148
+ ) {
149
+ if (tags.length === 0) {
150
+ return
151
+ }
152
+
153
+ if (tags.length === 1) {
154
+ target.push(tags[0]!)
155
+ return
156
+ }
157
+
158
+ const seen = new Set<string>()
159
+ for (const tag of tags) {
160
+ const key = JSON.stringify(tag)
161
+ if (seen.has(key)) {
162
+ continue
73
163
  }
164
+ seen.add(key)
165
+ target.push(tag)
166
+ }
167
+ }
168
+
169
+ export type ManifestCssLink =
170
+ | string
74
171
  | {
75
- tag: 'style'
76
- attrs?: Record<string, any>
77
- children?: string
78
- inlineCss?: true
172
+ href: string
173
+ crossOrigin?: AssetCrossOrigin
174
+ [DEV_STYLES_ATTR]?: true
79
175
  }
80
176
 
81
- export function getStylesheetHref(asset: RouterManagedTag) {
82
- if (asset.tag !== 'link') return undefined
83
-
84
- const rel = asset.attrs?.rel
85
- const href = asset.attrs?.href
86
- if (typeof href !== 'string') return undefined
177
+ export type ManifestInlineCss = {
178
+ attrs?: Record<string, any>
179
+ children?: string
180
+ }
87
181
 
88
- const relTokens = typeof rel === 'string' ? rel.split(/\s+/) : []
89
- if (!relTokens.includes('stylesheet')) return undefined
182
+ export type RouterManagedInlineCssTag = RouterManagedStyleTag & {
183
+ inlineCss: true
184
+ }
90
185
 
91
- return href
186
+ export function getStylesheetHref(asset: ManifestCssLink) {
187
+ return resolveManifestCssLink(asset).href
92
188
  }
93
189
 
94
- export function isInlinableStylesheet(
95
- manifest: Manifest | undefined,
96
- asset: RouterManagedTag,
97
- ) {
98
- const href = getStylesheetHref(asset)
99
- return !!href && manifest?.inlineCss?.styles[href] !== undefined
190
+ export function resolveManifestCssLink(link: ManifestCssLink) {
191
+ if (typeof link === 'string') {
192
+ return { href: link, crossOrigin: undefined }
193
+ }
194
+
195
+ return link
100
196
  }
101
197
 
102
- export function createInlineCssStyleAsset(css: string): RouterManagedTag {
198
+ export function createInlineCssStyleAsset(css: string): ManifestInlineCss {
103
199
  return {
104
- tag: 'style',
105
200
  attrs: {
106
201
  suppressHydrationWarning: true,
107
202
  },
108
- inlineCss: true,
109
203
  children: css,
110
204
  }
111
205
  }
112
206
 
113
- export function createInlineCssPlaceholderAsset(): RouterManagedTag {
207
+ export function createInlineCssPlaceholderAsset(): ManifestInlineCss {
114
208
  return {
115
- tag: 'style',
116
209
  attrs: {
117
210
  suppressHydrationWarning: true,
118
211
  },
119
- inlineCss: true,
120
212
  }
121
213
  }
package/src/router.ts CHANGED
@@ -98,7 +98,11 @@ import type {
98
98
  CommitLocationOptions,
99
99
  NavigateFn,
100
100
  } from './RouterProvider'
101
- import type { Manifest, RouterManagedTag } from './manifest'
101
+ import type {
102
+ Manifest,
103
+ ManifestRouteAssets,
104
+ RouterManagedTag,
105
+ } from './manifest'
102
106
  import type { AnySchema, AnyValidator } from './validators'
103
107
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
104
108
  import type { NotFoundError } from './not-found'
@@ -803,9 +807,7 @@ export interface ServerSsr {
803
807
  setRenderFinished: () => void
804
808
  cleanup: () => void
805
809
  onSerializationFinished: (listener: () => void) => void
806
- dehydrate: (opts?: {
807
- requestAssets?: Array<RouterManagedTag>
808
- }) => Promise<void>
810
+ dehydrate: (opts?: { requestAssets?: ManifestRouteAssets }) => Promise<void>
809
811
  takeBufferedScripts: () => RouterManagedTag | undefined
810
812
  /**
811
813
  * Takes any buffered HTML that was injected.
@@ -1,62 +1,43 @@
1
- export default function (options: { storageKey: string; key?: string }) {
1
+ export default function (storageKey: string, key?: string) {
2
2
  let byKey
3
3
 
4
4
  try {
5
- byKey = JSON.parse(sessionStorage.getItem(options.storageKey) || '{}')
6
- } catch (error) {
7
- console.error(error)
5
+ byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
6
+ } catch {
8
7
  return
9
8
  }
10
9
 
11
- const resolvedKey = options.key || window.history.state?.__TSR_key
12
- const elementEntries = resolvedKey ? byKey[resolvedKey] : undefined
10
+ const elementEntries = byKey?.[key || history.state?.__TSR_key]
13
11
  let windowRestored = false
14
12
 
15
- if (elementEntries && typeof elementEntries === 'object') {
16
- for (const elementSelector in elementEntries) {
17
- const entry = elementEntries[elementSelector]
18
-
19
- if (!entry || typeof entry !== 'object') {
20
- continue
21
- }
22
-
23
- const scrollX = entry.scrollX
24
- const scrollY = entry.scrollY
25
-
26
- if (!Number.isFinite(scrollX) || !Number.isFinite(scrollY)) {
27
- continue
28
- }
13
+ for (const elementSelector in elementEntries) {
14
+ const entry = elementEntries[elementSelector]
15
+ const scrollX = entry?.scrollX
16
+ const scrollY = entry?.scrollY
29
17
 
18
+ if (Number.isFinite(scrollX) && Number.isFinite(scrollY)) {
30
19
  if (elementSelector === 'window') {
31
- window.scrollTo({
32
- top: scrollY,
33
- left: scrollX,
34
- })
20
+ scrollTo(scrollX, scrollY)
35
21
  windowRestored = true
36
22
  } else if (elementSelector) {
37
- let element
38
-
39
23
  try {
40
- element = document.querySelector(elementSelector)
41
- } catch {
42
- continue
43
- }
44
-
45
- if (element) {
46
- element.scrollLeft = scrollX
47
- element.scrollTop = scrollY
48
- }
24
+ const element = document.querySelector(elementSelector)
25
+ if (element) {
26
+ element.scrollLeft = scrollX
27
+ element.scrollTop = scrollY
28
+ }
29
+ } catch {}
49
30
  }
50
31
  }
51
32
  }
52
33
 
53
34
  if (windowRestored) return
54
35
 
55
- const hash = window.location.hash.split('#', 2)[1]
36
+ const hash = location.hash.slice(1)
56
37
 
57
38
  if (hash) {
58
39
  const hashScrollIntoViewOptions =
59
- window.history.state?.__hashScrollIntoViewOptions ?? true
40
+ history.state?.__hashScrollIntoViewOptions ?? true
60
41
 
61
42
  if (hashScrollIntoViewOptions) {
62
43
  const el = document.getElementById(hash)
@@ -68,5 +49,5 @@ export default function (options: { storageKey: string; key?: string }) {
68
49
  return
69
50
  }
70
51
 
71
- window.scrollTo({ top: 0, left: 0 })
52
+ scrollTo(0, 0)
72
53
  }
@@ -6,25 +6,16 @@ import {
6
6
  import { escapeHtml } from '../utils'
7
7
  import type { AnyRouter } from '../router'
8
8
 
9
- type InlineScrollRestorationScriptOptions = {
10
- storageKey: string
11
- key?: string
12
- }
13
-
14
9
  const defaultInlineScrollRestorationScript = `(${minifiedScrollRestorationScript})(${escapeHtml(
15
- JSON.stringify({
16
- storageKey,
17
- } satisfies InlineScrollRestorationScriptOptions),
10
+ JSON.stringify(storageKey),
18
11
  )})`
19
12
 
20
- function getScrollRestorationScript(
21
- options: InlineScrollRestorationScriptOptions,
22
- ) {
23
- if (options.storageKey === storageKey && options.key === undefined) {
13
+ function getScrollRestorationScript(key?: string) {
14
+ if (key === undefined) {
24
15
  return defaultInlineScrollRestorationScript
25
16
  }
26
17
 
27
- return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(options))})`
18
+ return `(${minifiedScrollRestorationScript})(${escapeHtml(JSON.stringify(storageKey))},${escapeHtml(JSON.stringify(key))})`
28
19
  }
29
20
 
30
21
  export function getScrollRestorationScriptForRouter(router: AnyRouter) {
@@ -48,8 +39,5 @@ export function getScrollRestorationScriptForRouter(router: AnyRouter) {
48
39
  return defaultInlineScrollRestorationScript
49
40
  }
50
41
 
51
- return getScrollRestorationScript({
52
- storageKey,
53
- key: userKey,
54
- })
42
+ return getScrollRestorationScript(userKey)
55
43
  }