@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.
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/defer.cjs.map +1 -1
- package/dist/cjs/index.cjs +5 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/invariant.cjs.map +1 -1
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/lru-cache.cjs.map +1 -1
- package/dist/cjs/manifest.cjs +43 -17
- package/dist/cjs/manifest.cjs.map +1 -1
- package/dist/cjs/manifest.d.cts +76 -24
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/rewrite.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +31 -16
- package/dist/cjs/scroll-restoration-script/client.cjs.map +1 -1
- package/dist/cjs/scroll-restoration-script/server.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchMiddleware.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +10 -8
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.d.cts +2 -2
- package/dist/cjs/ssr/handlerCallback.cjs +46 -0
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.d.cts +15 -1
- package/dist/cjs/ssr/headers.cjs.map +1 -1
- package/dist/cjs/ssr/json.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
- package/dist/cjs/ssr/server.cjs +6 -1
- package/dist/cjs/ssr/server.d.cts +3 -2
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +263 -132
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +4 -19
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +455 -203
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +14 -5
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/defer.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/invariant.js.map +1 -1
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/lru-cache.js.map +1 -1
- package/dist/esm/manifest.d.ts +76 -24
- package/dist/esm/manifest.js +39 -17
- package/dist/esm/manifest.js.map +1 -1
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/rewrite.js.map +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +31 -16
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration-script/client.js.map +1 -1
- package/dist/esm/scroll-restoration-script/server.js.map +1 -1
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchMiddleware.js.map +1 -1
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.d.ts +2 -2
- package/dist/esm/ssr/createRequestHandler.js +10 -8
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.d.ts +15 -1
- package/dist/esm/ssr/handlerCallback.js +42 -1
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/headers.js.map +1 -1
- package/dist/esm/ssr/json.js.map +1 -1
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
- package/dist/esm/ssr/serializer/transformer.js.map +1 -1
- package/dist/esm/ssr/server.d.ts +3 -2
- package/dist/esm/ssr/server.js +2 -2
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +4 -19
- package/dist/esm/ssr/ssr-server.js +264 -133
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +14 -5
- package/dist/esm/ssr/transformStreamWithRouter.js +455 -203
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +21 -1
- package/src/manifest.ts +151 -59
- package/src/router.ts +37 -19
- package/src/ssr/createRequestHandler.ts +14 -13
- package/src/ssr/handlerCallback.ts +84 -1
- package/src/ssr/server.ts +14 -2
- package/src/ssr/ssr-server.ts +418 -222
- package/src/ssr/transformStreamWithRouter.ts +662 -281
package/src/ssr/ssr-server.ts
CHANGED
|
@@ -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'
|
|
@@ -17,24 +16,15 @@ import { makeSsrSerovalPlugin } from './serializer/transformer'
|
|
|
17
16
|
import type { LRUCache } from '../lru-cache'
|
|
18
17
|
import type { DehydratedMatch, DehydratedRouter } from './types'
|
|
19
18
|
import type { AnySerializationAdapter } from './serializer/transformer'
|
|
20
|
-
import type { AnyRouter } from '../router'
|
|
19
|
+
import type { AnyRouter, ServerSsr } from '../router'
|
|
21
20
|
import type { AnyRouteMatch } from '../Matches'
|
|
22
|
-
import type {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
interface RouterEvents {
|
|
30
|
-
onInjectedHtml: {
|
|
31
|
-
type: 'onInjectedHtml'
|
|
32
|
-
}
|
|
33
|
-
onSerializationFinished: {
|
|
34
|
-
type: 'onSerializationFinished'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
21
|
+
import type {
|
|
22
|
+
Manifest,
|
|
23
|
+
ManifestRoute,
|
|
24
|
+
ManifestRouteAssets,
|
|
25
|
+
RouterManagedTag,
|
|
26
|
+
ServerManifest,
|
|
27
|
+
} from '../manifest'
|
|
38
28
|
|
|
39
29
|
const SCOPE_ID = 'tsr'
|
|
40
30
|
|
|
@@ -73,14 +63,15 @@ const INITIAL_SCRIPTS = [
|
|
|
73
63
|
]
|
|
74
64
|
|
|
75
65
|
class ScriptBuffer {
|
|
76
|
-
private
|
|
66
|
+
private injectScript: ((script: string) => void) | undefined
|
|
77
67
|
private _queue: Array<string>
|
|
78
68
|
private _scriptBarrierLifted = false
|
|
79
69
|
private _cleanedUp = false
|
|
80
|
-
private
|
|
70
|
+
private _microtaskVersion = 0
|
|
71
|
+
private _pendingMicrotaskVersion = 0
|
|
81
72
|
|
|
82
|
-
constructor(
|
|
83
|
-
this.
|
|
73
|
+
constructor(injectScript: (script: string) => void) {
|
|
74
|
+
this.injectScript = injectScript
|
|
84
75
|
// Copy INITIAL_SCRIPTS to avoid mutating the shared array
|
|
85
76
|
this._queue = INITIAL_SCRIPTS.slice()
|
|
86
77
|
}
|
|
@@ -88,31 +79,39 @@ class ScriptBuffer {
|
|
|
88
79
|
enqueue(script: string) {
|
|
89
80
|
if (this._cleanedUp) return
|
|
90
81
|
this._queue.push(script)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this._pendingMicrotask = true
|
|
94
|
-
queueMicrotask(() => {
|
|
95
|
-
this._pendingMicrotask = false
|
|
96
|
-
this.injectBufferedScripts()
|
|
97
|
-
})
|
|
82
|
+
if (this._scriptBarrierLifted) {
|
|
83
|
+
this.scheduleInjectBufferedScripts()
|
|
98
84
|
}
|
|
99
85
|
}
|
|
100
86
|
|
|
101
87
|
liftBarrier() {
|
|
102
88
|
if (this._scriptBarrierLifted || this._cleanedUp) return
|
|
103
89
|
this._scriptBarrierLifted = true
|
|
104
|
-
if (this._queue.length > 0
|
|
105
|
-
this.
|
|
106
|
-
queueMicrotask(() => {
|
|
107
|
-
this._pendingMicrotask = false
|
|
108
|
-
this.injectBufferedScripts()
|
|
109
|
-
})
|
|
90
|
+
if (this._queue.length > 0) {
|
|
91
|
+
this.scheduleInjectBufferedScripts()
|
|
110
92
|
}
|
|
111
93
|
}
|
|
112
94
|
|
|
95
|
+
scheduleInjectBufferedScripts() {
|
|
96
|
+
if (this._pendingMicrotaskVersion !== 0) return
|
|
97
|
+
const pendingVersion = ++this._microtaskVersion
|
|
98
|
+
this._pendingMicrotaskVersion = pendingVersion
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
if (this._pendingMicrotaskVersion !== pendingVersion) return
|
|
101
|
+
this._pendingMicrotaskVersion = 0
|
|
102
|
+
this.injectBufferedScripts()
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
clearPendingMicrotask() {
|
|
107
|
+
if (this._pendingMicrotaskVersion === 0) return
|
|
108
|
+
this._pendingMicrotaskVersion = 0
|
|
109
|
+
this._microtaskVersion++
|
|
110
|
+
}
|
|
111
|
+
|
|
113
112
|
/**
|
|
114
113
|
* Flushes any pending scripts synchronously.
|
|
115
|
-
* Call this before
|
|
114
|
+
* Call this before signaling serialization finished to ensure all scripts are injected.
|
|
116
115
|
*
|
|
117
116
|
* IMPORTANT: Only injects if the barrier has been lifted. Before the barrier is lifted,
|
|
118
117
|
* scripts should remain in the queue so takeBufferedScripts() can retrieve them
|
|
@@ -120,16 +119,17 @@ class ScriptBuffer {
|
|
|
120
119
|
flush() {
|
|
121
120
|
if (!this._scriptBarrierLifted) return
|
|
122
121
|
if (this._cleanedUp) return
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
if (scriptsToInject && this.router?.serverSsr) {
|
|
126
|
-
this.router.serverSsr.injectScript(scriptsToInject)
|
|
127
|
-
}
|
|
122
|
+
this.clearPendingMicrotask()
|
|
123
|
+
this.injectBufferedScripts()
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
takeAll() {
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
return this.takeScripts(this._queue.length)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
takeScripts(count: number) {
|
|
131
|
+
if (count <= 0) return undefined
|
|
132
|
+
const bufferedScripts = this._queue.splice(0, count)
|
|
133
133
|
if (bufferedScripts.length === 0) {
|
|
134
134
|
return undefined
|
|
135
135
|
}
|
|
@@ -141,20 +141,25 @@ class ScriptBuffer {
|
|
|
141
141
|
return bufferedScripts.join(';') + ';document.currentScript.remove()'
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
hasPending() {
|
|
145
|
+
return this._queue.length > 0
|
|
146
|
+
}
|
|
147
|
+
|
|
144
148
|
injectBufferedScripts() {
|
|
145
149
|
if (this._cleanedUp) return
|
|
146
150
|
// Early return if queue is empty (avoids unnecessary takeAll() call)
|
|
147
151
|
if (this._queue.length === 0) return
|
|
148
152
|
const scriptsToInject = this.takeAll()
|
|
149
|
-
if (scriptsToInject
|
|
150
|
-
this.
|
|
153
|
+
if (scriptsToInject) {
|
|
154
|
+
this.injectScript?.(scriptsToInject)
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
cleanup() {
|
|
155
159
|
this._cleanedUp = true
|
|
160
|
+
this.clearPendingMicrotask()
|
|
156
161
|
this._queue = []
|
|
157
|
-
this.
|
|
162
|
+
this.injectScript = undefined
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -162,133 +167,222 @@ const isProd = process.env.NODE_ENV === 'production'
|
|
|
162
167
|
|
|
163
168
|
type FilteredRoutes = Manifest['routes']
|
|
164
169
|
|
|
165
|
-
type
|
|
166
|
-
|
|
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<
|
|
170
|
-
const inlineCssCaches = new WeakMap<Manifest, InlineCssLRU>()
|
|
180
|
+
const manifestCaches = new WeakMap<ServerManifest, ManifestLRU>()
|
|
171
181
|
|
|
172
|
-
function getManifestCache(manifest:
|
|
182
|
+
function getManifestCache(manifest: ServerManifest): ManifestLRU {
|
|
173
183
|
const cache = manifestCaches.get(manifest)
|
|
174
184
|
if (cache) return cache
|
|
175
|
-
const newCache = createLRUCache<string,
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
193
|
-
if (!styles) return []
|
|
196
|
+
if (preparedRoutes.inlineCss !== undefined) return preparedRoutes.inlineCss
|
|
194
197
|
|
|
195
|
-
const
|
|
196
|
-
const hrefs
|
|
198
|
+
const styles = manifest.inlineCss?.styles
|
|
199
|
+
const hrefs = preparedRoutes.inlineCssHrefs
|
|
200
|
+
if (!styles || !hrefs?.length) return undefined
|
|
197
201
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
207
|
+
preparedRoutes.inlineCss = css
|
|
208
|
+
return css
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
function getInlineCssAssetForPreparedRoutes(
|
|
212
|
+
manifest: ServerManifest,
|
|
213
|
+
preparedRoutes: PreparedMatchedManifestRoutes,
|
|
214
|
+
) {
|
|
215
|
+
const css = getInlineCssForPreparedRoutes(manifest, preparedRoutes)
|
|
216
216
|
|
|
217
|
-
|
|
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 =
|
|
220
|
-
if (cached
|
|
234
|
+
const cached = getManifestCache(manifest).get(cacheKey)
|
|
235
|
+
if (cached) {
|
|
236
|
+
return cached
|
|
237
|
+
}
|
|
221
238
|
}
|
|
222
239
|
|
|
223
|
-
const
|
|
240
|
+
const preparedRoutes = prepareMatchedManifestRoutes(manifest, matches)
|
|
224
241
|
|
|
225
242
|
if (isProd) {
|
|
226
|
-
|
|
243
|
+
getManifestCache(manifest).set(cacheKey, preparedRoutes)
|
|
227
244
|
}
|
|
228
245
|
|
|
229
|
-
return
|
|
246
|
+
return preparedRoutes
|
|
230
247
|
}
|
|
231
248
|
|
|
232
|
-
function
|
|
233
|
-
manifest:
|
|
249
|
+
function prepareMatchedManifestRoutes(
|
|
250
|
+
manifest: ServerManifest,
|
|
234
251
|
matches: Array<AnyRouteMatch>,
|
|
235
|
-
) {
|
|
236
|
-
|
|
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
|
|
239
|
-
const
|
|
266
|
+
const inlineCssHrefs: Array<string> = []
|
|
267
|
+
const seenInlineCssHrefs = new Set<string>()
|
|
268
|
+
let hasStrippedRoutes = false
|
|
240
269
|
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
290
|
+
return {
|
|
291
|
+
routes,
|
|
292
|
+
hasStrippedRoutes,
|
|
293
|
+
...(inlineCssHrefs.length ? { inlineCssHrefs } : {}),
|
|
294
|
+
}
|
|
295
|
+
}
|
|
254
296
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!seenInlineCssHrefs.has(href)) {
|
|
326
|
+
seenInlineCssHrefs.add(href)
|
|
327
|
+
inlineCssHrefs.push(href)
|
|
267
328
|
}
|
|
268
|
-
nextRoutes[routeId] = nextRoute
|
|
269
|
-
}
|
|
270
329
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
nextRoutes[rootRouteId] = {
|
|
274
|
-
...rootRoute,
|
|
275
|
-
assets: [createInlineCssPlaceholderAsset(), ...(rootRoute.assets ?? [])],
|
|
330
|
+
if (!cssLinks) {
|
|
331
|
+
cssLinks = css.slice(0, i)
|
|
276
332
|
}
|
|
277
333
|
}
|
|
278
334
|
|
|
279
|
-
|
|
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:
|
|
290
|
-
getRequestAssets?: () =>
|
|
291
|
-
includeUnmatchedRouteAssets?: boolean
|
|
384
|
+
manifest: ServerManifest | undefined
|
|
385
|
+
getRequestAssets?: () => ManifestRouteAssets | undefined
|
|
292
386
|
}) {
|
|
293
387
|
router.ssr = {
|
|
294
388
|
get manifest() {
|
|
@@ -296,52 +390,104 @@ export function attachRouterServerSsrUtils({
|
|
|
296
390
|
|
|
297
391
|
const requestAssets = getRequestAssets?.()
|
|
298
392
|
const matches = router.stores.matches.get()
|
|
299
|
-
const
|
|
393
|
+
const hasAssets = hasRequestAssets(requestAssets)
|
|
300
394
|
|
|
301
|
-
if (!
|
|
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
|
-
...
|
|
310
|
-
[rootRouteId]:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
},
|
|
321
444
|
}
|
|
322
445
|
let _dehydrated = false
|
|
323
446
|
let _serializationFinished = false
|
|
447
|
+
let streamFastPathReserved = false
|
|
324
448
|
const renderFinishedListeners: Array<() => void> = []
|
|
449
|
+
const injectedHtmlListeners: Array<() => void> = []
|
|
325
450
|
const serializationFinishedListeners: Array<() => void> = []
|
|
326
|
-
const
|
|
451
|
+
const cleanupListeners: Array<() => void> = []
|
|
452
|
+
let cleanupStarted = false
|
|
327
453
|
let injectedHtmlBuffer = ''
|
|
328
454
|
|
|
329
|
-
|
|
455
|
+
const callListeners = (listeners: Array<() => void>, errorPrefix: string) => {
|
|
456
|
+
const snapshot = listeners.slice()
|
|
457
|
+
for (const l of snapshot) {
|
|
458
|
+
try {
|
|
459
|
+
l()
|
|
460
|
+
} catch (err) {
|
|
461
|
+
console.error(`${errorPrefix}:`, err)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const removeListener = (
|
|
467
|
+
listeners: Array<() => void>,
|
|
468
|
+
listener: () => void,
|
|
469
|
+
) => {
|
|
470
|
+
const index = listeners.indexOf(listener)
|
|
471
|
+
if (index >= 0) listeners.splice(index, 1)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const scriptBuffer = new ScriptBuffer((script) => {
|
|
475
|
+
serverSsr.injectScript(script)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
const serverSsr: ServerSsr = {
|
|
330
479
|
injectHtml: (html: string) => {
|
|
331
|
-
if (!html) return
|
|
480
|
+
if (!html || cleanupStarted) return
|
|
332
481
|
// Buffer the HTML so it can be retrieved via takeBufferedHtml()
|
|
333
482
|
injectedHtmlBuffer += html
|
|
334
|
-
|
|
335
|
-
router.emit({
|
|
336
|
-
type: 'onInjectedHtml',
|
|
337
|
-
})
|
|
483
|
+
callListeners(injectedHtmlListeners, 'SSR injected HTML listener error')
|
|
338
484
|
},
|
|
339
485
|
injectScript: (script: string) => {
|
|
340
|
-
if (!script) return
|
|
486
|
+
if (!script || cleanupStarted) return
|
|
341
487
|
const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
|
|
342
|
-
|
|
488
|
+
serverSsr.injectHtml(html)
|
|
343
489
|
},
|
|
344
|
-
dehydrate: async (opts?: { requestAssets?:
|
|
490
|
+
dehydrate: async (opts?: { requestAssets?: ManifestRouteAssets }) => {
|
|
345
491
|
if (_dehydrated) {
|
|
346
492
|
if (process.env.NODE_ENV !== 'production') {
|
|
347
493
|
throw new Error('Invariant failed: router is already dehydrated!')
|
|
@@ -357,61 +503,36 @@ export function attachRouterServerSsrUtils({
|
|
|
357
503
|
const matches = matchesToDehydrate.map(dehydrateMatch)
|
|
358
504
|
|
|
359
505
|
let manifestToDehydrate: Manifest | undefined = undefined
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
// is true; otherwise omit them entirely. Preloads for unmatched routes are
|
|
363
|
-
// still excluded because they are handled via dynamic imports.
|
|
506
|
+
// Only currently matched routes are dehydrated. Other route assets are
|
|
507
|
+
// loaded through dynamic imports when those routes become active.
|
|
364
508
|
if (manifest) {
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
}
|
|
509
|
+
const cacheKey = getMatchedRoutesCacheKey(matchesToDehydrate)
|
|
510
|
+
const preparedManifest = getPreparedMatchedManifestRoutes(
|
|
511
|
+
manifest,
|
|
512
|
+
matchesToDehydrate,
|
|
513
|
+
cacheKey,
|
|
514
|
+
)
|
|
404
515
|
|
|
405
516
|
manifestToDehydrate = {
|
|
406
|
-
|
|
517
|
+
...(manifest.scriptFormat
|
|
518
|
+
? { scriptFormat: manifest.scriptFormat }
|
|
519
|
+
: {}),
|
|
520
|
+
...(preparedManifest.inlineCssHrefs
|
|
521
|
+
? { inlineStyle: createInlineCssPlaceholderAsset() }
|
|
522
|
+
: {}),
|
|
523
|
+
routes: preparedManifest.routes,
|
|
407
524
|
}
|
|
408
525
|
|
|
409
526
|
// Merge request-scoped assets into root route (without mutating cached manifest)
|
|
410
|
-
|
|
527
|
+
const requestAssets = opts?.requestAssets
|
|
528
|
+
if (hasRequestAssets(requestAssets)) {
|
|
411
529
|
const existingRoot = manifestToDehydrate.routes[rootRouteId]
|
|
412
|
-
manifestToDehydrate.routes
|
|
413
|
-
...
|
|
414
|
-
|
|
530
|
+
manifestToDehydrate.routes = {
|
|
531
|
+
...manifestToDehydrate.routes,
|
|
532
|
+
[rootRouteId]: mergeRequestAssetsIntoRootRoute(
|
|
533
|
+
existingRoot,
|
|
534
|
+
requestAssets,
|
|
535
|
+
),
|
|
415
536
|
}
|
|
416
537
|
}
|
|
417
538
|
}
|
|
@@ -439,19 +560,34 @@ export function attachRouterServerSsrUtils({
|
|
|
439
560
|
.concat(defaultSerovalPlugins)
|
|
440
561
|
: defaultSerovalPlugins
|
|
441
562
|
|
|
563
|
+
let serializationCompleteSignaled = false
|
|
442
564
|
const signalSerializationComplete = () => {
|
|
565
|
+
if (serializationCompleteSignaled || cleanupStarted) return
|
|
566
|
+
serializationCompleteSignaled = true
|
|
443
567
|
_serializationFinished = true
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
568
|
+
|
|
569
|
+
const listeners = serializationFinishedListeners.slice()
|
|
570
|
+
serializationFinishedListeners.length = 0
|
|
571
|
+
|
|
572
|
+
for (const l of listeners) {
|
|
573
|
+
try {
|
|
574
|
+
l()
|
|
575
|
+
} catch (err) {
|
|
576
|
+
console.error('Serialization listener error:', err)
|
|
577
|
+
}
|
|
452
578
|
}
|
|
453
579
|
}
|
|
454
580
|
|
|
581
|
+
const finishScriptSerialization = () => {
|
|
582
|
+
if (serializationCompleteSignaled || cleanupStarted) return
|
|
583
|
+
scriptBuffer.enqueue(GLOBAL_TSR + '.e()')
|
|
584
|
+
// Must synchronously notify injected HTML listeners before signaling
|
|
585
|
+
// completion; otherwise the held </body> tail could flush ahead of the
|
|
586
|
+
// end script.
|
|
587
|
+
scriptBuffer.flush()
|
|
588
|
+
signalSerializationComplete()
|
|
589
|
+
}
|
|
590
|
+
|
|
455
591
|
crossSerializeStream(dehydratedRouter, {
|
|
456
592
|
refs: new Map(),
|
|
457
593
|
plugins,
|
|
@@ -467,15 +603,11 @@ export function attachRouterServerSsrUtils({
|
|
|
467
603
|
if (err && (err as any).stack) {
|
|
468
604
|
console.error((err as any).stack)
|
|
469
605
|
}
|
|
470
|
-
|
|
606
|
+
finishScriptSerialization()
|
|
471
607
|
},
|
|
472
608
|
scopeId: SCOPE_ID,
|
|
473
609
|
onDone: () => {
|
|
474
|
-
|
|
475
|
-
// Flush all pending scripts synchronously before signaling completion
|
|
476
|
-
// This ensures all scripts are injected before onSerializationFinished is emitted
|
|
477
|
-
scriptBuffer.flush()
|
|
478
|
-
signalSerializationComplete()
|
|
610
|
+
finishScriptSerialization()
|
|
479
611
|
},
|
|
480
612
|
})
|
|
481
613
|
},
|
|
@@ -485,23 +617,65 @@ export function attachRouterServerSsrUtils({
|
|
|
485
617
|
isSerializationFinished() {
|
|
486
618
|
return _serializationFinished
|
|
487
619
|
},
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
renderFinishedListeners.length = 0
|
|
620
|
+
reserveStreamFastPath() {
|
|
621
|
+
if (
|
|
622
|
+
!cleanupStarted &&
|
|
623
|
+
_serializationFinished &&
|
|
624
|
+
!streamFastPathReserved &&
|
|
625
|
+
renderFinishedListeners.length === 0 &&
|
|
626
|
+
!injectedHtmlBuffer &&
|
|
627
|
+
!scriptBuffer.hasPending()
|
|
628
|
+
) {
|
|
629
|
+
streamFastPathReserved = true
|
|
630
|
+
return true
|
|
500
631
|
}
|
|
632
|
+
return false
|
|
633
|
+
},
|
|
634
|
+
onInjectedHtml: (listener) => {
|
|
635
|
+
if (cleanupStarted) return () => {}
|
|
636
|
+
injectedHtmlListeners.push(listener)
|
|
637
|
+
return () => removeListener(injectedHtmlListeners, listener)
|
|
638
|
+
},
|
|
639
|
+
onRenderFinished: (listener) => {
|
|
640
|
+
if (cleanupStarted || streamFastPathReserved) return
|
|
641
|
+
renderFinishedListeners.push(listener)
|
|
642
|
+
},
|
|
643
|
+
onSerializationFinished: (listener) => {
|
|
644
|
+
if (cleanupStarted) return () => {}
|
|
645
|
+
if (_serializationFinished && !cleanupStarted) {
|
|
646
|
+
try {
|
|
647
|
+
listener()
|
|
648
|
+
} catch (err) {
|
|
649
|
+
console.error('Serialization listener error:', err)
|
|
650
|
+
}
|
|
651
|
+
return () => {}
|
|
652
|
+
}
|
|
653
|
+
serializationFinishedListeners.push(listener)
|
|
654
|
+
return () => removeListener(serializationFinishedListeners, listener)
|
|
655
|
+
},
|
|
656
|
+
onCleanup: (listener) => {
|
|
657
|
+
if (cleanupStarted) return
|
|
658
|
+
cleanupListeners.push(listener)
|
|
659
|
+
},
|
|
660
|
+
setRenderFinished: () => {
|
|
661
|
+
if (cleanupStarted) return
|
|
501
662
|
scriptBuffer.liftBarrier()
|
|
663
|
+
const listeners = renderFinishedListeners.slice()
|
|
664
|
+
renderFinishedListeners.length = 0
|
|
665
|
+
for (const l of listeners) {
|
|
666
|
+
try {
|
|
667
|
+
l()
|
|
668
|
+
} catch (err) {
|
|
669
|
+
console.error('Error in render finished listener:', err)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (_serializationFinished) {
|
|
673
|
+
scriptBuffer.flush()
|
|
674
|
+
}
|
|
502
675
|
},
|
|
503
676
|
takeBufferedScripts() {
|
|
504
677
|
const scripts = scriptBuffer.takeAll()
|
|
678
|
+
if (!scripts) return undefined
|
|
505
679
|
const serverBufferedScript: RouterManagedTag = {
|
|
506
680
|
tag: 'script',
|
|
507
681
|
attrs: {
|
|
@@ -525,15 +699,37 @@ export function attachRouterServerSsrUtils({
|
|
|
525
699
|
return buffered
|
|
526
700
|
},
|
|
527
701
|
cleanup() {
|
|
528
|
-
// Guard against multiple cleanup calls
|
|
529
|
-
|
|
702
|
+
// Guard against multiple/reentrant cleanup calls. A listener could call
|
|
703
|
+
// cleanup() again indirectly; snapshot + clear before invoking so each
|
|
704
|
+
// listener runs exactly once and reentry is a no-op.
|
|
705
|
+
if (cleanupStarted) return
|
|
706
|
+
cleanupStarted = true
|
|
707
|
+
const listeners = cleanupListeners.slice()
|
|
708
|
+
cleanupListeners.length = 0
|
|
709
|
+
for (const l of listeners) {
|
|
710
|
+
try {
|
|
711
|
+
l()
|
|
712
|
+
} catch (err) {
|
|
713
|
+
console.error('Error in SSR cleanup listener:', err)
|
|
714
|
+
}
|
|
715
|
+
}
|
|
530
716
|
renderFinishedListeners.length = 0
|
|
717
|
+
injectedHtmlListeners.length = 0
|
|
531
718
|
serializationFinishedListeners.length = 0
|
|
532
719
|
injectedHtmlBuffer = ''
|
|
533
720
|
scriptBuffer.cleanup()
|
|
534
721
|
router.serverSsr = undefined
|
|
535
722
|
},
|
|
536
723
|
}
|
|
724
|
+
|
|
725
|
+
router.serverSsr = serverSsr
|
|
726
|
+
for (const listener of router.serverSsrLifecycle?.onServerSsrAttach ?? []) {
|
|
727
|
+
try {
|
|
728
|
+
listener(serverSsr)
|
|
729
|
+
} catch (err) {
|
|
730
|
+
console.error('SSR attach listener error:', err)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
537
733
|
}
|
|
538
734
|
|
|
539
735
|
/**
|