@tanstack/router-core 1.171.5 → 1.171.7

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 (107) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/defer.cjs.map +1 -1
  4. package/dist/cjs/index.cjs +5 -1
  5. package/dist/cjs/index.d.cts +2 -2
  6. package/dist/cjs/invariant.cjs.map +1 -1
  7. package/dist/cjs/load-matches.cjs.map +1 -1
  8. package/dist/cjs/lru-cache.cjs.map +1 -1
  9. package/dist/cjs/manifest.cjs +43 -17
  10. package/dist/cjs/manifest.cjs.map +1 -1
  11. package/dist/cjs/manifest.d.cts +76 -24
  12. package/dist/cjs/new-process-route-tree.cjs.map +1 -1
  13. package/dist/cjs/not-found.cjs.map +1 -1
  14. package/dist/cjs/path.cjs.map +1 -1
  15. package/dist/cjs/qss.cjs.map +1 -1
  16. package/dist/cjs/redirect.cjs.map +1 -1
  17. package/dist/cjs/rewrite.cjs.map +1 -1
  18. package/dist/cjs/route.cjs.map +1 -1
  19. package/dist/cjs/router.cjs.map +1 -1
  20. package/dist/cjs/router.d.cts +31 -16
  21. package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -1
  22. package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
  23. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  24. package/dist/cjs/searchMiddleware.cjs.map +1 -1
  25. package/dist/cjs/searchParams.cjs.map +1 -1
  26. package/dist/cjs/ssr/createRequestHandler.cjs +10 -8
  27. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  28. package/dist/cjs/ssr/createRequestHandler.d.cts +2 -2
  29. package/dist/cjs/ssr/handlerCallback.cjs +46 -0
  30. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  31. package/dist/cjs/ssr/handlerCallback.d.cts +15 -1
  32. package/dist/cjs/ssr/headers.cjs.map +1 -1
  33. package/dist/cjs/ssr/json.cjs.map +1 -1
  34. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  35. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  36. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  37. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  38. package/dist/cjs/ssr/server.cjs +6 -1
  39. package/dist/cjs/ssr/server.d.cts +3 -2
  40. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  41. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  42. package/dist/cjs/ssr/ssr-server.cjs +263 -132
  43. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  44. package/dist/cjs/ssr/ssr-server.d.cts +4 -19
  45. package/dist/cjs/ssr/transformStreamWithRouter.cjs +455 -203
  46. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  47. package/dist/cjs/ssr/transformStreamWithRouter.d.cts +14 -5
  48. package/dist/cjs/stores.cjs.map +1 -1
  49. package/dist/cjs/utils.cjs.map +1 -1
  50. package/dist/esm/Matches.js.map +1 -1
  51. package/dist/esm/config.js.map +1 -1
  52. package/dist/esm/defer.js.map +1 -1
  53. package/dist/esm/index.d.ts +2 -2
  54. package/dist/esm/index.js +2 -2
  55. package/dist/esm/invariant.js.map +1 -1
  56. package/dist/esm/load-matches.js.map +1 -1
  57. package/dist/esm/lru-cache.js.map +1 -1
  58. package/dist/esm/manifest.d.ts +76 -24
  59. package/dist/esm/manifest.js +39 -17
  60. package/dist/esm/manifest.js.map +1 -1
  61. package/dist/esm/new-process-route-tree.js.map +1 -1
  62. package/dist/esm/not-found.js.map +1 -1
  63. package/dist/esm/path.js.map +1 -1
  64. package/dist/esm/qss.js.map +1 -1
  65. package/dist/esm/redirect.js.map +1 -1
  66. package/dist/esm/rewrite.js.map +1 -1
  67. package/dist/esm/route.js.map +1 -1
  68. package/dist/esm/router.d.ts +31 -16
  69. package/dist/esm/router.js.map +1 -1
  70. package/dist/esm/scroll-restoration-script/client.js.map +1 -1
  71. package/dist/esm/scroll-restoration-script/server.js.map +1 -1
  72. package/dist/esm/scroll-restoration.js.map +1 -1
  73. package/dist/esm/searchMiddleware.js.map +1 -1
  74. package/dist/esm/searchParams.js.map +1 -1
  75. package/dist/esm/ssr/createRequestHandler.d.ts +2 -2
  76. package/dist/esm/ssr/createRequestHandler.js +10 -8
  77. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  78. package/dist/esm/ssr/handlerCallback.d.ts +15 -1
  79. package/dist/esm/ssr/handlerCallback.js +42 -1
  80. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  81. package/dist/esm/ssr/headers.js.map +1 -1
  82. package/dist/esm/ssr/json.js.map +1 -1
  83. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  84. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  85. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  86. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  87. package/dist/esm/ssr/server.d.ts +3 -2
  88. package/dist/esm/ssr/server.js +2 -2
  89. package/dist/esm/ssr/ssr-client.js.map +1 -1
  90. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  91. package/dist/esm/ssr/ssr-server.d.ts +4 -19
  92. package/dist/esm/ssr/ssr-server.js +264 -133
  93. package/dist/esm/ssr/ssr-server.js.map +1 -1
  94. package/dist/esm/ssr/transformStreamWithRouter.d.ts +14 -5
  95. package/dist/esm/ssr/transformStreamWithRouter.js +455 -203
  96. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  97. package/dist/esm/stores.js.map +1 -1
  98. package/dist/esm/utils.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/index.ts +21 -1
  101. package/src/manifest.ts +151 -59
  102. package/src/router.ts +37 -19
  103. package/src/ssr/createRequestHandler.ts +14 -13
  104. package/src/ssr/handlerCallback.ts +84 -1
  105. package/src/ssr/server.ts +14 -2
  106. package/src/ssr/ssr-server.ts +418 -222
  107. package/src/ssr/transformStreamWithRouter.ts +662 -281
@@ -31,38 +31,42 @@ function dehydrateMatch(match) {
31
31
  }
32
32
  const INITIAL_SCRIPTS = [(0, seroval.getCrossReferenceHeader)(SCOPE_ID), require_tsrScript.default];
33
33
  var ScriptBuffer = class {
34
- constructor(router) {
34
+ constructor(injectScript) {
35
35
  this._scriptBarrierLifted = false;
36
36
  this._cleanedUp = false;
37
- this._pendingMicrotask = false;
38
- this.router = router;
37
+ this._microtaskVersion = 0;
38
+ this._pendingMicrotaskVersion = 0;
39
+ this.injectScript = injectScript;
39
40
  this._queue = INITIAL_SCRIPTS.slice();
40
41
  }
41
42
  enqueue(script) {
42
43
  if (this._cleanedUp) return;
43
44
  this._queue.push(script);
44
- if (this._scriptBarrierLifted && !this._pendingMicrotask) {
45
- this._pendingMicrotask = true;
46
- queueMicrotask(() => {
47
- this._pendingMicrotask = false;
48
- this.injectBufferedScripts();
49
- });
50
- }
45
+ if (this._scriptBarrierLifted) this.scheduleInjectBufferedScripts();
51
46
  }
52
47
  liftBarrier() {
53
48
  if (this._scriptBarrierLifted || this._cleanedUp) return;
54
49
  this._scriptBarrierLifted = true;
55
- if (this._queue.length > 0 && !this._pendingMicrotask) {
56
- this._pendingMicrotask = true;
57
- queueMicrotask(() => {
58
- this._pendingMicrotask = false;
59
- this.injectBufferedScripts();
60
- });
61
- }
50
+ if (this._queue.length > 0) this.scheduleInjectBufferedScripts();
51
+ }
52
+ scheduleInjectBufferedScripts() {
53
+ if (this._pendingMicrotaskVersion !== 0) return;
54
+ const pendingVersion = ++this._microtaskVersion;
55
+ this._pendingMicrotaskVersion = pendingVersion;
56
+ queueMicrotask(() => {
57
+ if (this._pendingMicrotaskVersion !== pendingVersion) return;
58
+ this._pendingMicrotaskVersion = 0;
59
+ this.injectBufferedScripts();
60
+ });
61
+ }
62
+ clearPendingMicrotask() {
63
+ if (this._pendingMicrotaskVersion === 0) return;
64
+ this._pendingMicrotaskVersion = 0;
65
+ this._microtaskVersion++;
62
66
  }
63
67
  /**
64
68
  * Flushes any pending scripts synchronously.
65
- * Call this before emitting onSerializationFinished to ensure all scripts are injected.
69
+ * Call this before signaling serialization finished to ensure all scripts are injected.
66
70
  *
67
71
  * IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,
68
72
  * scripts should remain in the queue so takeBufferedScripts() can retrieve them
@@ -70,33 +74,38 @@ var ScriptBuffer = class {
70
74
  flush() {
71
75
  if (!this._scriptBarrierLifted) return;
72
76
  if (this._cleanedUp) return;
73
- this._pendingMicrotask = false;
74
- const scriptsToInject = this.takeAll();
75
- if (scriptsToInject && this.router?.serverSsr) this.router.serverSsr.injectScript(scriptsToInject);
77
+ this.clearPendingMicrotask();
78
+ this.injectBufferedScripts();
76
79
  }
77
80
  takeAll() {
78
- const bufferedScripts = this._queue;
79
- this._queue = [];
81
+ return this.takeScripts(this._queue.length);
82
+ }
83
+ takeScripts(count) {
84
+ if (count <= 0) return void 0;
85
+ const bufferedScripts = this._queue.splice(0, count);
80
86
  if (bufferedScripts.length === 0) return;
81
87
  if (bufferedScripts.length === 1) return bufferedScripts[0] + ";document.currentScript.remove()";
82
88
  return bufferedScripts.join(";") + ";document.currentScript.remove()";
83
89
  }
90
+ hasPending() {
91
+ return this._queue.length > 0;
92
+ }
84
93
  injectBufferedScripts() {
85
94
  if (this._cleanedUp) return;
86
95
  if (this._queue.length === 0) return;
87
96
  const scriptsToInject = this.takeAll();
88
- if (scriptsToInject && this.router?.serverSsr) this.router.serverSsr.injectScript(scriptsToInject);
97
+ if (scriptsToInject) this.injectScript?.(scriptsToInject);
89
98
  }
90
99
  cleanup() {
91
100
  this._cleanedUp = true;
101
+ this.clearPendingMicrotask();
92
102
  this._queue = [];
93
- this.router = void 0;
103
+ this.injectScript = void 0;
94
104
  }
95
105
  };
96
106
  const isProd = process.env.NODE_ENV === "production";
97
107
  const MANIFEST_CACHE_SIZE = 100;
98
108
  const manifestCaches = /* @__PURE__ */ new WeakMap();
99
- const inlineCssCaches = /* @__PURE__ */ new WeakMap();
100
109
  function getManifestCache(manifest) {
101
110
  const cache = manifestCaches.get(manifest);
102
111
  if (cache) return cache;
@@ -104,102 +113,178 @@ function getManifestCache(manifest) {
104
113
  manifestCaches.set(manifest, newCache);
105
114
  return newCache;
106
115
  }
107
- function getInlineCssCache(manifest) {
108
- const cache = inlineCssCaches.get(manifest);
109
- if (cache) return cache;
110
- const newCache = require_lru_cache.createLRUCache(MANIFEST_CACHE_SIZE);
111
- inlineCssCaches.set(manifest, newCache);
112
- return newCache;
116
+ function getInlineCssForPreparedRoutes(manifest, preparedRoutes) {
117
+ if (preparedRoutes.inlineCss !== void 0) return preparedRoutes.inlineCss;
118
+ const styles = manifest.inlineCss?.styles;
119
+ const hrefs = preparedRoutes.inlineCssHrefs;
120
+ if (!styles || !hrefs?.length) return void 0;
121
+ let css = "";
122
+ for (const href of hrefs) css += styles[href];
123
+ preparedRoutes.inlineCss = css;
124
+ return css;
113
125
  }
114
- function getInlineCssHrefsForMatches(manifest, matches) {
115
- const styles = manifest?.inlineCss?.styles;
116
- if (!styles) return [];
117
- const seen = /* @__PURE__ */ new Set();
118
- const hrefs = [];
119
- for (const match of matches) {
120
- const assets = manifest?.routes[match.routeId]?.assets ?? [];
121
- for (const asset of assets) {
122
- const href = require_manifest.getStylesheetHref(asset);
123
- if (!href || seen.has(href) || styles[href] === void 0) continue;
124
- seen.add(href);
125
- hrefs.push(href);
126
- }
127
- }
128
- return hrefs;
126
+ function getInlineCssAssetForPreparedRoutes(manifest, preparedRoutes) {
127
+ const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes);
128
+ return css === void 0 ? void 0 : require_manifest.createInlineCssStyleAsset(css);
129
129
  }
130
- function getInlineCssForHrefs(manifest, hrefs) {
131
- const styles = manifest.inlineCss?.styles;
132
- if (!styles || hrefs.length === 0) return void 0;
133
- const cacheKey = hrefs.join("\0");
130
+ function getMatchedRoutesCacheKey(matches) {
131
+ let cacheKey = "";
132
+ for (let i = 0; i < matches.length; i++) cacheKey += (i === 0 ? "" : "\0") + matches[i].routeId;
133
+ return cacheKey;
134
+ }
135
+ function getPreparedMatchedManifestRoutes(manifest, matches, cacheKey) {
134
136
  if (isProd) {
135
- const cached = getInlineCssCache(manifest).get(cacheKey);
136
- if (cached !== void 0) return cached;
137
+ const cached = getManifestCache(manifest).get(cacheKey);
138
+ if (cached) return cached;
137
139
  }
138
- const css = hrefs.map((href) => styles[href]).join("");
139
- if (isProd) getInlineCssCache(manifest).set(cacheKey, css);
140
- return css;
140
+ const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches);
141
+ if (isProd) getManifestCache(manifest).set(cacheKey, preparedRoutes);
142
+ return preparedRoutes;
141
143
  }
142
- function getInlineCssAssetForMatches(manifest, matches) {
143
- if (!manifest?.inlineCss) return void 0;
144
- const css = getInlineCssForHrefs(manifest, getInlineCssHrefsForMatches(manifest, matches));
145
- return css === void 0 ? void 0 : require_manifest.createInlineCssStyleAsset(css);
144
+ function prepareMatchedManifestRoutes(manifest, matches) {
145
+ const inlineStyles = manifest.inlineCss?.styles;
146
+ const routes = {};
147
+ if (!inlineStyles) {
148
+ for (const match of matches) {
149
+ const route = manifest.routes[match.routeId];
150
+ if (route) routes[match.routeId] = route;
151
+ }
152
+ return {
153
+ routes,
154
+ hasStrippedRoutes: false
155
+ };
156
+ }
157
+ const inlineCssHrefs = [];
158
+ const seenInlineCssHrefs = /* @__PURE__ */ new Set();
159
+ let hasStrippedRoutes = false;
160
+ for (const match of matches) {
161
+ const routeId = match.routeId;
162
+ const route = manifest.routes[routeId];
163
+ if (!route) continue;
164
+ const nextRoute = stripInlinedStylesheetAssetsFromRoute(inlineStyles, route, inlineCssHrefs, seenInlineCssHrefs);
165
+ if (nextRoute !== route) hasStrippedRoutes = true;
166
+ routes[routeId] = nextRoute;
167
+ }
168
+ return {
169
+ routes,
170
+ hasStrippedRoutes,
171
+ ...inlineCssHrefs.length ? { inlineCssHrefs } : {}
172
+ };
146
173
  }
147
- function stripInlinedStylesheetAssets(manifest, routes, matches) {
148
- if (!manifest.inlineCss) return routes;
149
- const nextRoutes = {};
150
- for (const [routeId, route] of Object.entries(routes)) {
151
- const assets = route.assets?.filter((asset) => !require_manifest.isInlinableStylesheet(manifest, asset));
174
+ function stripInlinedStylesheetAssetsFromRoute(inlineStyles, route, inlineCssHrefs, seenInlineCssHrefs) {
175
+ const css = route.css;
176
+ if (!css) return route;
177
+ if (css.length === 0) {
152
178
  const nextRoute = { ...route };
153
- if (assets) if (assets.length > 0) nextRoute.assets = assets;
154
- else delete nextRoute.assets;
155
- nextRoutes[routeId] = nextRoute;
179
+ delete nextRoute.css;
180
+ return nextRoute;
156
181
  }
157
- if (getInlineCssAssetForMatches(manifest, matches)) {
158
- const rootRoute = nextRoutes["__root__"] ?? {};
159
- nextRoutes[require_root.rootRouteId] = {
160
- ...rootRoute,
161
- assets: [require_manifest.createInlineCssPlaceholderAsset(), ...rootRoute.assets ?? []]
162
- };
182
+ let cssLinks;
183
+ for (let i = 0; i < css.length; i++) {
184
+ const link = css[i];
185
+ const href = require_manifest.getStylesheetHref(link);
186
+ if (inlineStyles[href] === void 0) {
187
+ if (cssLinks) cssLinks.push(link);
188
+ continue;
189
+ }
190
+ if (!seenInlineCssHrefs.has(href)) {
191
+ seenInlineCssHrefs.add(href);
192
+ inlineCssHrefs.push(href);
193
+ }
194
+ if (!cssLinks) cssLinks = css.slice(0, i);
163
195
  }
164
- return nextRoutes;
196
+ if (!cssLinks) return route;
197
+ if (cssLinks.length > 0) return {
198
+ ...route,
199
+ css: cssLinks
200
+ };
201
+ const nextRoute = { ...route };
202
+ delete nextRoute.css;
203
+ return nextRoute;
165
204
  }
166
- function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includeUnmatchedRouteAssets = true }) {
205
+ function hasRouteAssets(route) {
206
+ return !!route.scripts?.length || !!route.css?.length;
207
+ }
208
+ function hasRequestAssets(assets) {
209
+ return !!assets && (!!assets.preloads?.length || hasRouteAssets(assets));
210
+ }
211
+ function mergeRequestAssetsIntoRootRoute(rootRoute, requestAssets) {
212
+ const preloads = requestAssets?.preloads?.length ? [...requestAssets.preloads, ...rootRoute?.preloads ?? []] : rootRoute?.preloads;
213
+ const scripts = requestAssets?.scripts?.length ? [...requestAssets.scripts, ...rootRoute?.scripts ?? []] : rootRoute?.scripts;
214
+ const cssLinks = requestAssets?.css?.length ? [...requestAssets.css, ...rootRoute?.css ?? []] : rootRoute?.css;
215
+ return {
216
+ ...rootRoute ?? {},
217
+ ...preloads?.length ? { preloads } : {},
218
+ ...scripts?.length ? { scripts } : {},
219
+ ...cssLinks?.length ? { css: cssLinks } : {}
220
+ };
221
+ }
222
+ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets }) {
167
223
  router.ssr = { get manifest() {
168
224
  if (!manifest) return manifest;
169
225
  const requestAssets = getRequestAssets?.();
170
- const inlineCssAsset = getInlineCssAssetForMatches(manifest, router.stores.matches.get());
171
- if (!requestAssets?.length && !inlineCssAsset) return manifest;
226
+ const matches = router.stores.matches.get();
227
+ const hasAssets = hasRequestAssets(requestAssets);
228
+ if (!hasAssets && !manifest.inlineCss) return manifest;
229
+ let inlineCssAsset;
230
+ let routes = manifest.routes;
231
+ if (manifest.inlineCss) {
232
+ const preparedManifest = getPreparedMatchedManifestRoutes(manifest, matches, getMatchedRoutesCacheKey(matches));
233
+ inlineCssAsset = getInlineCssAssetForPreparedRoutes(manifest, preparedManifest);
234
+ if (preparedManifest.hasStrippedRoutes) routes = {
235
+ ...manifest.routes,
236
+ ...preparedManifest.routes
237
+ };
238
+ }
239
+ if (!hasAssets) return {
240
+ ...manifest.scriptFormat ? { scriptFormat: manifest.scriptFormat } : {},
241
+ ...inlineCssAsset ? { inlineStyle: inlineCssAsset } : {},
242
+ routes
243
+ };
244
+ const rootRoute = routes[require_root.rootRouteId];
172
245
  return {
173
- ...manifest,
246
+ ...manifest.scriptFormat ? { scriptFormat: manifest.scriptFormat } : {},
247
+ ...inlineCssAsset ? { inlineStyle: inlineCssAsset } : {},
174
248
  routes: {
175
- ...manifest.routes,
176
- [require_root.rootRouteId]: {
177
- ...manifest.routes[require_root.rootRouteId],
178
- assets: [
179
- ...requestAssets ?? [],
180
- ...inlineCssAsset ? [inlineCssAsset] : [],
181
- ...manifest.routes["__root__"]?.assets ?? []
182
- ]
183
- }
249
+ ...routes,
250
+ [require_root.rootRouteId]: mergeRequestAssetsIntoRootRoute(rootRoute, requestAssets)
184
251
  }
185
252
  };
186
253
  } };
187
254
  let _dehydrated = false;
188
255
  let _serializationFinished = false;
256
+ let streamFastPathReserved = false;
189
257
  const renderFinishedListeners = [];
258
+ const injectedHtmlListeners = [];
190
259
  const serializationFinishedListeners = [];
191
- const scriptBuffer = new ScriptBuffer(router);
260
+ const cleanupListeners = [];
261
+ let cleanupStarted = false;
192
262
  let injectedHtmlBuffer = "";
193
- router.serverSsr = {
263
+ const callListeners = (listeners, errorPrefix) => {
264
+ const snapshot = listeners.slice();
265
+ for (const l of snapshot) try {
266
+ l();
267
+ } catch (err) {
268
+ console.error(`${errorPrefix}:`, err);
269
+ }
270
+ };
271
+ const removeListener = (listeners, listener) => {
272
+ const index = listeners.indexOf(listener);
273
+ if (index >= 0) listeners.splice(index, 1);
274
+ };
275
+ const scriptBuffer = new ScriptBuffer((script) => {
276
+ serverSsr.injectScript(script);
277
+ });
278
+ const serverSsr = {
194
279
  injectHtml: (html) => {
195
- if (!html) return;
280
+ if (!html || cleanupStarted) return;
196
281
  injectedHtmlBuffer += html;
197
- router.emit({ type: "onInjectedHtml" });
282
+ callListeners(injectedHtmlListeners, "SSR injected HTML listener error");
198
283
  },
199
284
  injectScript: (script) => {
200
- if (!script) return;
285
+ if (!script || cleanupStarted) return;
201
286
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ""}>${script}<\/script>`;
202
- router.serverSsr.injectHtml(html);
287
+ serverSsr.injectHtml(html);
203
288
  },
204
289
  dehydrate: async (opts) => {
205
290
  if (_dehydrated) {
@@ -211,27 +296,19 @@ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includ
211
296
  const matches = matchesToDehydrate.map(dehydrateMatch);
212
297
  let manifestToDehydrate = void 0;
213
298
  if (manifest) {
214
- const currentRouteIdsList = matchesToDehydrate.map((m) => m.routeId);
215
- const manifestCacheKey = `${currentRouteIdsList.join("\0")}\0includeUnmatchedRouteAssets=${includeUnmatchedRouteAssets}`;
216
- let filteredRoutes;
217
- if (isProd) filteredRoutes = getManifestCache(manifest).get(manifestCacheKey);
218
- if (!filteredRoutes) {
219
- const currentRouteIds = new Set(currentRouteIdsList);
220
- const nextFilteredRoutes = {};
221
- for (const routeId in manifest.routes) {
222
- const routeManifest = manifest.routes[routeId];
223
- if (currentRouteIds.has(routeId)) nextFilteredRoutes[routeId] = routeManifest;
224
- else if (includeUnmatchedRouteAssets && routeManifest.assets && routeManifest.assets.length > 0) nextFilteredRoutes[routeId] = { assets: routeManifest.assets };
225
- }
226
- filteredRoutes = stripInlinedStylesheetAssets(manifest, nextFilteredRoutes, matchesToDehydrate);
227
- if (isProd) getManifestCache(manifest).set(manifestCacheKey, filteredRoutes);
228
- }
229
- manifestToDehydrate = { routes: { ...filteredRoutes } };
230
- if (opts?.requestAssets?.length) {
299
+ const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate);
300
+ const preparedManifest = getPreparedMatchedManifestRoutes(manifest, matchesToDehydrate, cacheKey);
301
+ manifestToDehydrate = {
302
+ ...manifest.scriptFormat ? { scriptFormat: manifest.scriptFormat } : {},
303
+ ...preparedManifest.inlineCssHrefs ? { inlineStyle: require_manifest.createInlineCssPlaceholderAsset() } : {},
304
+ routes: preparedManifest.routes
305
+ };
306
+ const requestAssets = opts?.requestAssets;
307
+ if (hasRequestAssets(requestAssets)) {
231
308
  const existingRoot = manifestToDehydrate.routes[require_root.rootRouteId];
232
- manifestToDehydrate.routes[require_root.rootRouteId] = {
233
- ...existingRoot,
234
- assets: [...opts.requestAssets, ...existingRoot?.assets ?? []]
309
+ manifestToDehydrate.routes = {
310
+ ...manifestToDehydrate.routes,
311
+ [require_root.rootRouteId]: mergeRequestAssetsIntoRootRoute(existingRoot, requestAssets)
235
312
  };
236
313
  }
237
314
  }
@@ -247,18 +324,25 @@ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includ
247
324
  const trackPlugins = { didRun: false };
248
325
  const serializationAdapters = router.options.serializationAdapters;
249
326
  const plugins = serializationAdapters ? serializationAdapters.map((t) => /* @__PURE__ */ require_transformer.makeSsrSerovalPlugin(t, trackPlugins)).concat(require_seroval_plugins.defaultSerovalPlugins) : require_seroval_plugins.defaultSerovalPlugins;
327
+ let serializationCompleteSignaled = false;
250
328
  const signalSerializationComplete = () => {
329
+ if (serializationCompleteSignaled || cleanupStarted) return;
330
+ serializationCompleteSignaled = true;
251
331
  _serializationFinished = true;
252
- try {
253
- serializationFinishedListeners.forEach((l) => l());
254
- router.emit({ type: "onSerializationFinished" });
332
+ const listeners = serializationFinishedListeners.slice();
333
+ serializationFinishedListeners.length = 0;
334
+ for (const l of listeners) try {
335
+ l();
255
336
  } catch (err) {
256
337
  console.error("Serialization listener error:", err);
257
- } finally {
258
- serializationFinishedListeners.length = 0;
259
- renderFinishedListeners.length = 0;
260
338
  }
261
339
  };
340
+ const finishScriptSerialization = () => {
341
+ if (serializationCompleteSignaled || cleanupStarted) return;
342
+ scriptBuffer.enqueue(require_constants.GLOBAL_TSR + ".e()");
343
+ scriptBuffer.flush();
344
+ signalSerializationComplete();
345
+ };
262
346
  (0, seroval.crossSerializeStream)(dehydratedRouter, {
263
347
  refs: /* @__PURE__ */ new Map(),
264
348
  plugins,
@@ -270,13 +354,11 @@ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includ
270
354
  onError: (err) => {
271
355
  console.error("Serialization error:", err);
272
356
  if (err && err.stack) console.error(err.stack);
273
- signalSerializationComplete();
357
+ finishScriptSerialization();
274
358
  },
275
359
  scopeId: SCOPE_ID,
276
360
  onDone: () => {
277
- scriptBuffer.enqueue(require_constants.GLOBAL_TSR + ".e()");
278
- scriptBuffer.flush();
279
- signalSerializationComplete();
361
+ finishScriptSerialization();
280
362
  }
281
363
  });
282
364
  },
@@ -286,20 +368,54 @@ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includ
286
368
  isSerializationFinished() {
287
369
  return _serializationFinished;
288
370
  },
289
- onRenderFinished: (listener) => renderFinishedListeners.push(listener),
290
- onSerializationFinished: (listener) => serializationFinishedListeners.push(listener),
371
+ reserveStreamFastPath() {
372
+ if (!cleanupStarted && _serializationFinished && !streamFastPathReserved && renderFinishedListeners.length === 0 && !injectedHtmlBuffer && !scriptBuffer.hasPending()) {
373
+ streamFastPathReserved = true;
374
+ return true;
375
+ }
376
+ return false;
377
+ },
378
+ onInjectedHtml: (listener) => {
379
+ if (cleanupStarted) return () => {};
380
+ injectedHtmlListeners.push(listener);
381
+ return () => removeListener(injectedHtmlListeners, listener);
382
+ },
383
+ onRenderFinished: (listener) => {
384
+ if (cleanupStarted || streamFastPathReserved) return;
385
+ renderFinishedListeners.push(listener);
386
+ },
387
+ onSerializationFinished: (listener) => {
388
+ if (cleanupStarted) return () => {};
389
+ if (_serializationFinished && !cleanupStarted) {
390
+ try {
391
+ listener();
392
+ } catch (err) {
393
+ console.error("Serialization listener error:", err);
394
+ }
395
+ return () => {};
396
+ }
397
+ serializationFinishedListeners.push(listener);
398
+ return () => removeListener(serializationFinishedListeners, listener);
399
+ },
400
+ onCleanup: (listener) => {
401
+ if (cleanupStarted) return;
402
+ cleanupListeners.push(listener);
403
+ },
291
404
  setRenderFinished: () => {
292
- try {
293
- renderFinishedListeners.forEach((l) => l());
405
+ if (cleanupStarted) return;
406
+ scriptBuffer.liftBarrier();
407
+ const listeners = renderFinishedListeners.slice();
408
+ renderFinishedListeners.length = 0;
409
+ for (const l of listeners) try {
410
+ l();
294
411
  } catch (err) {
295
412
  console.error("Error in render finished listener:", err);
296
- } finally {
297
- renderFinishedListeners.length = 0;
298
413
  }
299
- scriptBuffer.liftBarrier();
414
+ if (_serializationFinished) scriptBuffer.flush();
300
415
  },
301
416
  takeBufferedScripts() {
302
417
  const scripts = scriptBuffer.takeAll();
418
+ if (!scripts) return void 0;
303
419
  return {
304
420
  tag: "script",
305
421
  attrs: {
@@ -320,14 +436,29 @@ function attachRouterServerSsrUtils({ router, manifest, getRequestAssets, includ
320
436
  return buffered;
321
437
  },
322
438
  cleanup() {
323
- if (!router.serverSsr) return;
439
+ if (cleanupStarted) return;
440
+ cleanupStarted = true;
441
+ const listeners = cleanupListeners.slice();
442
+ cleanupListeners.length = 0;
443
+ for (const l of listeners) try {
444
+ l();
445
+ } catch (err) {
446
+ console.error("Error in SSR cleanup listener:", err);
447
+ }
324
448
  renderFinishedListeners.length = 0;
449
+ injectedHtmlListeners.length = 0;
325
450
  serializationFinishedListeners.length = 0;
326
451
  injectedHtmlBuffer = "";
327
452
  scriptBuffer.cleanup();
328
453
  router.serverSsr = void 0;
329
454
  }
330
455
  };
456
+ router.serverSsr = serverSsr;
457
+ for (const listener of router.serverSsrLifecycle?.onServerSsrAttach ?? []) try {
458
+ listener(serverSsr);
459
+ } catch (err) {
460
+ console.error("SSR attach listener error:", err);
461
+ }
331
462
  }
332
463
  /**
333
464
  * Get the origin for the request.