@tanstack/start-server-core 1.167.30 → 1.168.1
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/esm/createStartHandler.d.ts +2 -133
- package/dist/esm/createStartHandler.js +64 -155
- package/dist/esm/createStartHandler.js.map +1 -1
- package/dist/esm/early-hints.d.ts +12 -0
- package/dist/esm/early-hints.js +87 -1
- package/dist/esm/early-hints.js.map +1 -1
- package/dist/esm/finalManifest.d.ts +42 -0
- package/dist/esm/finalManifest.js +126 -0
- package/dist/esm/finalManifest.js.map +1 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/inlineCss.d.ts +10 -0
- package/dist/esm/inlineCss.js +14 -0
- package/dist/esm/inlineCss.js.map +1 -0
- package/dist/esm/request-handler.d.ts +11 -1
- package/dist/esm/transformAssetUrls.d.ts +25 -48
- package/dist/esm/transformAssetUrls.js +41 -34
- package/dist/esm/transformAssetUrls.js.map +1 -1
- package/package.json +5 -5
- package/skills/start-server-core/SKILL.md +1 -1
- package/src/createStartHandler.ts +92 -460
- package/src/early-hints.ts +159 -0
- package/src/finalManifest.ts +319 -0
- package/src/global.d.ts +1 -0
- package/src/index.tsx +1 -5
- package/src/inlineCss.ts +31 -0
- package/src/request-handler.ts +12 -0
- package/src/transformAssetUrls.ts +118 -121
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createMemoryHistory } from '@tanstack/history'
|
|
2
2
|
import {
|
|
3
|
+
createCsrfMiddleware,
|
|
3
4
|
createNullProtoObject,
|
|
5
|
+
csrfSymbol,
|
|
4
6
|
flattenMiddlewares,
|
|
5
7
|
mergeHeaders,
|
|
6
8
|
safeObjectMerge,
|
|
@@ -22,19 +24,11 @@ import {
|
|
|
22
24
|
import { requestHandler } from './request-response'
|
|
23
25
|
import { getStartManifest } from './router-manifest'
|
|
24
26
|
import { handleServerAction } from './server-functions-handler'
|
|
27
|
+
import { createEarlyHintsCollector } from './early-hints'
|
|
25
28
|
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
transformManifestAssets,
|
|
30
|
-
} from './transformAssetUrls'
|
|
31
|
-
import {
|
|
32
|
-
collectDynamicHintsFromMatches,
|
|
33
|
-
collectStaticHintsFromManifest,
|
|
34
|
-
createEarlyHintsEvent,
|
|
35
|
-
createResponseLinkHeaderEntries,
|
|
36
|
-
getResponseLinkHeaderEntries,
|
|
37
|
-
} from './early-hints'
|
|
29
|
+
createCachedBaseManifestLoader,
|
|
30
|
+
createFinalManifestResolver,
|
|
31
|
+
} from './finalManifest'
|
|
38
32
|
|
|
39
33
|
import { HEADERS } from './constants'
|
|
40
34
|
import { ServerFunctionSerializationAdapter } from './serializer/ServerFunctionSerializationAdapter'
|
|
@@ -48,29 +42,14 @@ import type {
|
|
|
48
42
|
StartEntry,
|
|
49
43
|
} from '@tanstack/start-client-core'
|
|
50
44
|
import type { RequestHandler } from './request-handler'
|
|
51
|
-
import type {
|
|
52
|
-
EarlyHint,
|
|
53
|
-
EarlyHintsEvent,
|
|
54
|
-
EarlyHintsPhase,
|
|
55
|
-
OnEarlyHints,
|
|
56
|
-
ResponseLinkHeaderEntry,
|
|
57
|
-
ResponseLinkHeaderFilter,
|
|
58
|
-
ResponseLinkHeaderOptions,
|
|
59
|
-
} from './early-hints'
|
|
60
45
|
import type {
|
|
61
46
|
AnyRoute,
|
|
62
47
|
AnyRouter,
|
|
63
48
|
AnySerializationAdapter,
|
|
64
|
-
Manifest,
|
|
65
49
|
Register,
|
|
66
50
|
} from '@tanstack/router-core'
|
|
67
51
|
import type { HandlerCallback } from '@tanstack/router-core/ssr/server'
|
|
68
|
-
import type {
|
|
69
|
-
StartManifestWithClientEntry,
|
|
70
|
-
TransformAssetUrls,
|
|
71
|
-
TransformAssets,
|
|
72
|
-
TransformAssetsFn,
|
|
73
|
-
} from './transformAssetUrls'
|
|
52
|
+
import type { FinalManifestOptions } from './finalManifest'
|
|
74
53
|
|
|
75
54
|
type TODO = any
|
|
76
55
|
|
|
@@ -78,139 +57,8 @@ type AnyMiddlewareServerFn =
|
|
|
78
57
|
| AnyRequestMiddleware['options']['server']
|
|
79
58
|
| AnyFunctionMiddleware['options']['server']
|
|
80
59
|
|
|
81
|
-
export interface CreateStartHandlerOptions {
|
|
60
|
+
export interface CreateStartHandlerOptions extends FinalManifestOptions {
|
|
82
61
|
handler: HandlerCallback<AnyRouter>
|
|
83
|
-
/**
|
|
84
|
-
* Transform asset URLs and attributes at runtime, e.g. to prepend a CDN prefix.
|
|
85
|
-
*
|
|
86
|
-
* **String** — a URL prefix prepended to every asset URL (cached by default):
|
|
87
|
-
* ```ts
|
|
88
|
-
* createStartHandler({
|
|
89
|
-
* handler: defaultStreamHandler,
|
|
90
|
-
* transformAssets: 'https://cdn.example.com',
|
|
91
|
-
* })
|
|
92
|
-
* ```
|
|
93
|
-
*
|
|
94
|
-
* **Object shorthand** — a URL prefix with optional `crossOrigin`:
|
|
95
|
-
* ```ts
|
|
96
|
-
* createStartHandler({
|
|
97
|
-
* handler: defaultStreamHandler,
|
|
98
|
-
* transformAssets: {
|
|
99
|
-
* prefix: 'https://cdn.example.com',
|
|
100
|
-
* crossOrigin: 'anonymous',
|
|
101
|
-
* },
|
|
102
|
-
* })
|
|
103
|
-
* ```
|
|
104
|
-
*
|
|
105
|
-
* `crossOrigin` accepts a single value or a per-kind record:
|
|
106
|
-
* ```ts
|
|
107
|
-
* transformAssets: {
|
|
108
|
-
* prefix: 'https://cdn.example.com',
|
|
109
|
-
* crossOrigin: {
|
|
110
|
-
* modulepreload: 'anonymous',
|
|
111
|
-
* stylesheet: 'use-credentials',
|
|
112
|
-
* },
|
|
113
|
-
* }
|
|
114
|
-
* ```
|
|
115
|
-
*
|
|
116
|
-
* **Callback** — receives `{ kind, url }` and returns either a string URL or
|
|
117
|
-
* `{ href, crossOrigin? }` (cached by default — runs once on first request):
|
|
118
|
-
* ```ts
|
|
119
|
-
* createStartHandler({
|
|
120
|
-
* handler: defaultStreamHandler,
|
|
121
|
-
* transformAssets: ({ kind, url }) => {
|
|
122
|
-
* const href = `https://cdn.example.com${url}`
|
|
123
|
-
*
|
|
124
|
-
* if (kind === 'modulepreload') {
|
|
125
|
-
* return { href, crossOrigin: 'anonymous' }
|
|
126
|
-
* }
|
|
127
|
-
*
|
|
128
|
-
* return { href }
|
|
129
|
-
* },
|
|
130
|
-
* })
|
|
131
|
-
* ```
|
|
132
|
-
*
|
|
133
|
-
* **Object** — for explicit cache control:
|
|
134
|
-
* ```ts
|
|
135
|
-
* createStartHandler({
|
|
136
|
-
* handler: defaultStreamHandler,
|
|
137
|
-
* transformAssets: {
|
|
138
|
-
* transform: ({ url }) => {
|
|
139
|
-
* const region = getRequest().headers.get('x-region') || 'us'
|
|
140
|
-
* return { href: `https://cdn-${region}.example.com${url}` }
|
|
141
|
-
* },
|
|
142
|
-
* cache: false,
|
|
143
|
-
* },
|
|
144
|
-
* })
|
|
145
|
-
* ```
|
|
146
|
-
*
|
|
147
|
-
* `kind` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.
|
|
148
|
-
* `crossOrigin` applies to manifest-managed `<link>` assets.
|
|
149
|
-
*
|
|
150
|
-
* By default, the transformed manifest is cached after the first request
|
|
151
|
-
* (`cache: true`). Set `cache: false` for per-request transforms.
|
|
152
|
-
*
|
|
153
|
-
* If you're using a cached transform, you can optionally set `warmup: true`
|
|
154
|
-
* (object form only) to compute the transformed manifest in the background at
|
|
155
|
-
* server startup.
|
|
156
|
-
*
|
|
157
|
-
* Note: This only transforms URLs managed by TanStack Start's manifest
|
|
158
|
-
* (JS preloads, CSS links, and the client entry script). For asset imports
|
|
159
|
-
* used directly in components (e.g. `import logo from './logo.svg'`),
|
|
160
|
-
* configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.
|
|
161
|
-
*/
|
|
162
|
-
transformAssets?: TransformAssets
|
|
163
|
-
/**
|
|
164
|
-
* @deprecated Use `transformAssets` instead.
|
|
165
|
-
*
|
|
166
|
-
* **String** — a URL prefix prepended to every asset URL (cached by default):
|
|
167
|
-
* ```ts
|
|
168
|
-
* createStartHandler({
|
|
169
|
-
* handler: defaultStreamHandler,
|
|
170
|
-
* transformAssetUrls: 'https://cdn.example.com',
|
|
171
|
-
* })
|
|
172
|
-
* ```
|
|
173
|
-
*
|
|
174
|
-
* **Callback** — receives `{ url, type }` and returns a new URL
|
|
175
|
-
* (cached by default — runs once on first request):
|
|
176
|
-
* ```ts
|
|
177
|
-
* createStartHandler({
|
|
178
|
-
* handler: defaultStreamHandler,
|
|
179
|
-
* transformAssetUrls: ({ url, type }) => {
|
|
180
|
-
* return `https://cdn.example.com${url}`
|
|
181
|
-
* },
|
|
182
|
-
* })
|
|
183
|
-
* ```
|
|
184
|
-
*
|
|
185
|
-
* **Object** — for explicit cache control:
|
|
186
|
-
* ```ts
|
|
187
|
-
* createStartHandler({
|
|
188
|
-
* handler: defaultStreamHandler,
|
|
189
|
-
* transformAssetUrls: {
|
|
190
|
-
* transform: ({ url }) => {
|
|
191
|
-
* const region = getRequest().headers.get('x-region') || 'us'
|
|
192
|
-
* return `https://cdn-${region}.example.com${url}`
|
|
193
|
-
* },
|
|
194
|
-
* cache: false, // transform per-request
|
|
195
|
-
* },
|
|
196
|
-
* })
|
|
197
|
-
* ```
|
|
198
|
-
*
|
|
199
|
-
* `type` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.
|
|
200
|
-
*
|
|
201
|
-
* By default, the transformed manifest is cached after the first request
|
|
202
|
-
* (`cache: true`). Set `cache: false` for per-request transforms.
|
|
203
|
-
*
|
|
204
|
-
* If you're using a cached transform, you can optionally set `warmup: true`
|
|
205
|
-
* (object form only) to compute the transformed manifest in the background at
|
|
206
|
-
* server startup.
|
|
207
|
-
*
|
|
208
|
-
* Note: This only transforms URLs managed by TanStack Start's manifest
|
|
209
|
-
* (JS preloads, CSS links, and the client entry script). For asset imports
|
|
210
|
-
* used directly in components (e.g. `import logo from './logo.svg'`),
|
|
211
|
-
* configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.
|
|
212
|
-
*/
|
|
213
|
-
transformAssetUrls?: TransformAssetUrls
|
|
214
62
|
}
|
|
215
63
|
|
|
216
64
|
function getStartResponseHeaders(opts: { router: AnyRouter }) {
|
|
@@ -225,106 +73,6 @@ function getStartResponseHeaders(opts: { router: AnyRouter }) {
|
|
|
225
73
|
return headers
|
|
226
74
|
}
|
|
227
75
|
|
|
228
|
-
function notifyEarlyHints(
|
|
229
|
-
phase: EarlyHintsPhase,
|
|
230
|
-
event: EarlyHintsEvent,
|
|
231
|
-
onEarlyHints: OnEarlyHints,
|
|
232
|
-
) {
|
|
233
|
-
try {
|
|
234
|
-
const result = onEarlyHints(event)
|
|
235
|
-
if (result) {
|
|
236
|
-
void Promise.resolve(result).catch((err) => {
|
|
237
|
-
console.error(`Error sending ${phase} early hints:`, err)
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
} catch (err) {
|
|
241
|
-
console.error(`Error sending ${phase} early hints:`, err)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function getResponseLinkHeaderFilter(
|
|
246
|
-
responseLinkHeader: boolean | ResponseLinkHeaderOptions | undefined,
|
|
247
|
-
): ResponseLinkHeaderFilter | undefined {
|
|
248
|
-
if (typeof responseLinkHeader !== 'object') {
|
|
249
|
-
return undefined
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return responseLinkHeader.filter
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function appendResponseLinkHeaders(opts: {
|
|
256
|
-
responseHeaders: Headers
|
|
257
|
-
entries: ReadonlyArray<ResponseLinkHeaderEntry>
|
|
258
|
-
filter?: ResponseLinkHeaderFilter
|
|
259
|
-
}) {
|
|
260
|
-
if (!opts.filter) {
|
|
261
|
-
for (const entry of opts.entries) {
|
|
262
|
-
opts.responseHeaders.append('Link', entry.link)
|
|
263
|
-
}
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const links = getResponseLinkHeaderEntries(opts)
|
|
268
|
-
|
|
269
|
-
for (const link of links) {
|
|
270
|
-
opts.responseHeaders.append('Link', link)
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function collectResponseLinkHeaderEntries(opts: {
|
|
275
|
-
phase: EarlyHintsPhase
|
|
276
|
-
event: EarlyHintsEvent
|
|
277
|
-
entries: Array<ResponseLinkHeaderEntry>
|
|
278
|
-
}) {
|
|
279
|
-
for (let index = 0; index < opts.event.hints.length; index++) {
|
|
280
|
-
opts.entries.push({
|
|
281
|
-
phase: opts.phase,
|
|
282
|
-
hint: opts.event.hints[index]!,
|
|
283
|
-
link: opts.event.links[index]!,
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function handleCollectedEarlyHints(opts: {
|
|
289
|
-
phase: EarlyHintsPhase
|
|
290
|
-
hints: ReadonlyArray<EarlyHint>
|
|
291
|
-
sentLinks: Set<string>
|
|
292
|
-
sentHints?: Array<EarlyHint>
|
|
293
|
-
onEarlyHints?: OnEarlyHints
|
|
294
|
-
responseLinkHeaderEntries?: Array<ResponseLinkHeaderEntry>
|
|
295
|
-
}) {
|
|
296
|
-
const event = opts.onEarlyHints
|
|
297
|
-
? createEarlyHintsEvent({
|
|
298
|
-
phase: opts.phase,
|
|
299
|
-
hints: opts.hints,
|
|
300
|
-
sentLinks: opts.sentLinks,
|
|
301
|
-
sentHints: opts.sentHints!,
|
|
302
|
-
})
|
|
303
|
-
: undefined
|
|
304
|
-
|
|
305
|
-
if (event) {
|
|
306
|
-
notifyEarlyHints(opts.phase, event, opts.onEarlyHints!)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (!opts.responseLinkHeaderEntries) return
|
|
310
|
-
|
|
311
|
-
if (event) {
|
|
312
|
-
collectResponseLinkHeaderEntries({
|
|
313
|
-
phase: opts.phase,
|
|
314
|
-
event,
|
|
315
|
-
entries: opts.responseLinkHeaderEntries,
|
|
316
|
-
})
|
|
317
|
-
return
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
createResponseLinkHeaderEntries({
|
|
321
|
-
phase: opts.phase,
|
|
322
|
-
hints: opts.hints,
|
|
323
|
-
sentLinks: opts.sentLinks,
|
|
324
|
-
entries: opts.responseLinkHeaderEntries,
|
|
325
|
-
})
|
|
326
|
-
}
|
|
327
|
-
|
|
328
76
|
interface PluginAdaptersEntry {
|
|
329
77
|
hasPluginAdapters: boolean
|
|
330
78
|
pluginSerializationAdapters: Array<AnySerializationAdapter>
|
|
@@ -339,13 +87,21 @@ interface Entries {
|
|
|
339
87
|
// Cached entries - promises stored immediately to prevent concurrent imports
|
|
340
88
|
// that can cause race conditions during module initialization
|
|
341
89
|
let entriesPromise: Promise<Entries> | undefined
|
|
342
|
-
let
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
90
|
+
let hasWarnedMissingCsrfMiddleware = false
|
|
91
|
+
const defaultCsrfMiddleware = createCsrfMiddleware({
|
|
92
|
+
filter: (ctx) => ctx.handlerType === 'serverFn',
|
|
93
|
+
})
|
|
94
|
+
const getCachedBaseManifest = createCachedBaseManifestLoader(() =>
|
|
95
|
+
getStartManifest(),
|
|
96
|
+
)
|
|
97
|
+
const getProdBaseManifest: typeof getStartManifest = () =>
|
|
98
|
+
getCachedBaseManifest()
|
|
99
|
+
const getBaseManifest =
|
|
100
|
+
process.env.TSS_DEV_SERVER === 'true' ? getStartManifest : getProdBaseManifest
|
|
101
|
+
const createEarlyHintsForRequest: typeof createEarlyHintsCollector =
|
|
102
|
+
process.env.TSS_DEV_SERVER === 'true'
|
|
103
|
+
? () => undefined
|
|
104
|
+
: createEarlyHintsCollector
|
|
349
105
|
|
|
350
106
|
async function loadEntries(): Promise<Entries> {
|
|
351
107
|
const [routerEntry, startEntry, pluginAdapters] = await Promise.all([
|
|
@@ -370,59 +126,37 @@ function getEntries() {
|
|
|
370
126
|
return entriesPromise
|
|
371
127
|
}
|
|
372
128
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
function getBaseManifest(
|
|
378
|
-
matchedRoutes?: ReadonlyArray<AnyRoute>,
|
|
379
|
-
): Promise<StartManifestWithClientEntry> {
|
|
380
|
-
// In dev mode, always get fresh manifest (no caching) to include route-specific dev styles
|
|
381
|
-
if (process.env.TSS_DEV_SERVER === 'true') {
|
|
382
|
-
return getStartManifest(matchedRoutes)
|
|
383
|
-
}
|
|
384
|
-
// In prod, cache the base manifest
|
|
385
|
-
if (!baseManifestPromise) {
|
|
386
|
-
baseManifestPromise = getStartManifest()
|
|
387
|
-
}
|
|
388
|
-
return baseManifestPromise
|
|
129
|
+
function hasCsrfMiddleware(
|
|
130
|
+
middlewares: Array<AnyRequestMiddleware | AnyFunctionMiddleware>,
|
|
131
|
+
): boolean {
|
|
132
|
+
return middlewares.some((middleware) => csrfSymbol in middleware)
|
|
389
133
|
}
|
|
390
134
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
* - No transform: builds client entry script tag and returns (cached in prod).
|
|
395
|
-
* - Cached transform: transforms all URLs + builds script tag, caches result.
|
|
396
|
-
* - Per-request transform: deep-clones base manifest, transforms per-request.
|
|
397
|
-
*/
|
|
398
|
-
async function resolveManifest(
|
|
399
|
-
matchedRoutes: ReadonlyArray<AnyRoute> | undefined,
|
|
400
|
-
transformFn: TransformAssetsFn | undefined,
|
|
401
|
-
cache: boolean,
|
|
402
|
-
): Promise<Manifest> {
|
|
403
|
-
const base = await getBaseManifest(matchedRoutes)
|
|
404
|
-
|
|
405
|
-
const computeFinalManifest = async () => {
|
|
406
|
-
return transformFn
|
|
407
|
-
? await transformManifestAssets(base, transformFn, { clone: !cache })
|
|
408
|
-
: buildManifestWithClientEntry(base)
|
|
409
|
-
}
|
|
135
|
+
function warnMissingCsrfMiddlewareOnce() {
|
|
136
|
+
if (hasWarnedMissingCsrfMiddleware) return
|
|
137
|
+
hasWarnedMissingCsrfMiddleware = true
|
|
410
138
|
|
|
411
|
-
|
|
412
|
-
if (process.env.TSS_DEV_SERVER === 'true') {
|
|
413
|
-
return computeFinalManifest()
|
|
414
|
-
}
|
|
139
|
+
console.warn(`TanStack Start server functions are not protected by the CSRF middleware.
|
|
415
140
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
141
|
+
Server functions are same-origin RPC endpoints and should be protected from cross-site requests.
|
|
142
|
+
|
|
143
|
+
Add the CSRF middleware in src/start.ts:
|
|
144
|
+
|
|
145
|
+
const csrfMiddleware = createCsrfMiddleware({
|
|
146
|
+
filter: (ctx) => ctx.handlerType === 'serverFn',
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export const startInstance = createStart(() => ({
|
|
150
|
+
requestMiddleware: [csrfMiddleware],
|
|
151
|
+
}))
|
|
423
152
|
|
|
424
|
-
|
|
425
|
-
|
|
153
|
+
If you intentionally handle CSRF another way, disable this warning:
|
|
154
|
+
|
|
155
|
+
tanstackStart({
|
|
156
|
+
serverFns: {
|
|
157
|
+
disableCsrfMiddlewareWarning: true,
|
|
158
|
+
},
|
|
159
|
+
})`)
|
|
426
160
|
}
|
|
427
161
|
|
|
428
162
|
// Pre-computed constants
|
|
@@ -569,96 +303,22 @@ function handlerToMiddleware(
|
|
|
569
303
|
export function createStartHandler<TRegister = Register>(
|
|
570
304
|
cbOrOptions: HandlerCallback<AnyRouter> | CreateStartHandlerOptions,
|
|
571
305
|
): RequestHandler<TRegister> {
|
|
572
|
-
|
|
306
|
+
const handlerOptions: FinalManifestOptions =
|
|
307
|
+
typeof cbOrOptions === 'function' ? {} : cbOrOptions
|
|
573
308
|
const cb: HandlerCallback<AnyRouter> =
|
|
574
309
|
typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
adaptTransformAssetUrlsConfigToTransformAssets(
|
|
588
|
-
transformAssetUrlsOption,
|
|
589
|
-
),
|
|
590
|
-
)
|
|
591
|
-
: undefined
|
|
592
|
-
|
|
593
|
-
const warmupTransformManifest =
|
|
594
|
-
(!!transformAssetsOption &&
|
|
595
|
-
typeof transformAssetsOption === 'object' &&
|
|
596
|
-
'warmup' in transformAssetsOption &&
|
|
597
|
-
transformAssetsOption.warmup === true) ||
|
|
598
|
-
(!!transformAssetUrlsOption &&
|
|
599
|
-
typeof transformAssetUrlsOption === 'object' &&
|
|
600
|
-
transformAssetUrlsOption.warmup === true)
|
|
601
|
-
|
|
602
|
-
// Pre-resolve the transform function and cache flag
|
|
603
|
-
const resolvedTransformConfig = transformOption
|
|
604
|
-
const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true
|
|
605
|
-
const shouldCacheCreateTransform =
|
|
606
|
-
cache && process.env.TSS_DEV_SERVER !== 'true'
|
|
607
|
-
|
|
608
|
-
// Memoize a single createTransform() result when caching is enabled outside
|
|
609
|
-
// of the dev server.
|
|
610
|
-
let cachedCreateTransformPromise: Promise<TransformAssetsFn> | undefined
|
|
611
|
-
|
|
612
|
-
const getTransformFn = async (
|
|
613
|
-
opts: { warmup: true } | { warmup: false; request: Request },
|
|
614
|
-
): Promise<TransformAssetsFn | undefined> => {
|
|
615
|
-
if (!resolvedTransformConfig) return undefined
|
|
616
|
-
|
|
617
|
-
if (resolvedTransformConfig.type === 'createTransform') {
|
|
618
|
-
if (shouldCacheCreateTransform) {
|
|
619
|
-
if (!cachedCreateTransformPromise) {
|
|
620
|
-
cachedCreateTransformPromise = Promise.resolve(
|
|
621
|
-
resolvedTransformConfig.createTransform(opts),
|
|
622
|
-
).catch((error) => {
|
|
623
|
-
cachedCreateTransformPromise = undefined
|
|
624
|
-
throw error
|
|
625
|
-
})
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return cachedCreateTransformPromise
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
return resolvedTransformConfig.createTransform(opts)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return resolvedTransformConfig.transformFn
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// Background warmup for cached transforms (production only)
|
|
638
|
-
if (
|
|
639
|
-
warmupTransformManifest &&
|
|
640
|
-
cache &&
|
|
641
|
-
process.env.TSS_DEV_SERVER !== 'true' &&
|
|
642
|
-
!cachedFinalManifestPromise
|
|
643
|
-
) {
|
|
644
|
-
// NOTE: Do not call resolveManifest() here.
|
|
645
|
-
// resolveManifest() reads from cachedFinalManifestPromise, and since we set
|
|
646
|
-
// cachedFinalManifestPromise to this warmup promise, that would create a
|
|
647
|
-
// self-referential promise and hang forever.
|
|
648
|
-
const warmupPromise = (async () => {
|
|
649
|
-
const base = await getBaseManifest(undefined)
|
|
650
|
-
const transformFn = await getTransformFn({ warmup: true })
|
|
651
|
-
return transformFn
|
|
652
|
-
? await transformManifestAssets(base, transformFn, { clone: false })
|
|
653
|
-
: buildManifestWithClientEntry(base)
|
|
654
|
-
})()
|
|
655
|
-
cachedFinalManifestPromise = warmupPromise
|
|
656
|
-
warmupPromise.catch(() => {
|
|
657
|
-
// If warmup fails, allow the next request to retry.
|
|
658
|
-
if (cachedFinalManifestPromise === warmupPromise) {
|
|
659
|
-
cachedFinalManifestPromise = undefined
|
|
660
|
-
}
|
|
661
|
-
cachedCreateTransformPromise = undefined
|
|
310
|
+
const finalManifestResolver = createFinalManifestResolver({
|
|
311
|
+
...handlerOptions,
|
|
312
|
+
cacheCreateTransform: process.env.TSS_DEV_SERVER !== 'true',
|
|
313
|
+
})
|
|
314
|
+
const resolveManifestForRequest =
|
|
315
|
+
process.env.TSS_DEV_SERVER === 'true'
|
|
316
|
+
? finalManifestResolver.resolveUncached
|
|
317
|
+
: finalManifestResolver.resolveCached
|
|
318
|
+
|
|
319
|
+
if (process.env.TSS_DEV_SERVER !== 'true') {
|
|
320
|
+
finalManifestResolver.warmup({
|
|
321
|
+
getBaseManifest: () => getBaseManifest(undefined),
|
|
662
322
|
})
|
|
663
323
|
}
|
|
664
324
|
|
|
@@ -682,6 +342,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
682
342
|
}
|
|
683
343
|
|
|
684
344
|
const entries = await getEntries()
|
|
345
|
+
const hasStartInstance = !!entries.startEntry.startInstance
|
|
685
346
|
const startOptions: AnyStartInstanceOptions =
|
|
686
347
|
(await entries.startEntry.startInstance?.getOptions()) ||
|
|
687
348
|
({} as AnyStartInstanceOptions)
|
|
@@ -697,12 +358,15 @@ export function createStartHandler<TRegister = Register>(
|
|
|
697
358
|
|
|
698
359
|
const requestStartOptions = {
|
|
699
360
|
...startOptions,
|
|
361
|
+
requestMiddleware: hasStartInstance
|
|
362
|
+
? startOptions.requestMiddleware
|
|
363
|
+
: [defaultCsrfMiddleware],
|
|
700
364
|
serializationAdapters,
|
|
701
365
|
}
|
|
702
366
|
|
|
703
367
|
// Flatten request middlewares once
|
|
704
|
-
const flattenedRequestMiddlewares =
|
|
705
|
-
? flattenMiddlewares(
|
|
368
|
+
const flattenedRequestMiddlewares = requestStartOptions.requestMiddleware
|
|
369
|
+
? flattenMiddlewares(requestStartOptions.requestMiddleware)
|
|
706
370
|
: []
|
|
707
371
|
|
|
708
372
|
// Create set for deduplication
|
|
@@ -745,6 +409,14 @@ export function createStartHandler<TRegister = Register>(
|
|
|
745
409
|
|
|
746
410
|
// Check for server function requests first (early exit)
|
|
747
411
|
if (SERVER_FN_BASE && url.pathname.startsWith(SERVER_FN_BASE)) {
|
|
412
|
+
if (
|
|
413
|
+
process.env.NODE_ENV !== 'production' &&
|
|
414
|
+
process.env.TSS_DISABLE_CSRF_MIDDLEWARE_WARNING !== 'true' &&
|
|
415
|
+
!hasCsrfMiddleware(flattenedRequestMiddlewares)
|
|
416
|
+
) {
|
|
417
|
+
warnMissingCsrfMiddlewareOnce()
|
|
418
|
+
}
|
|
419
|
+
|
|
748
420
|
const serverFnId = url.pathname
|
|
749
421
|
.slice(SERVER_FN_BASE.length)
|
|
750
422
|
.split('/')[0]
|
|
@@ -778,6 +450,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
778
450
|
const ctx = await executeMiddleware([...middlewares, serverFnHandler], {
|
|
779
451
|
request,
|
|
780
452
|
pathname: url.pathname,
|
|
453
|
+
handlerType: 'serverFn',
|
|
781
454
|
context: createNullProtoObject(requestOpts?.context),
|
|
782
455
|
})
|
|
783
456
|
|
|
@@ -804,44 +477,18 @@ export function createStartHandler<TRegister = Register>(
|
|
|
804
477
|
)
|
|
805
478
|
}
|
|
806
479
|
|
|
807
|
-
const manifest = await
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
)
|
|
480
|
+
const manifest = await resolveManifestForRequest({
|
|
481
|
+
request,
|
|
482
|
+
requestInlineCss: requestOpts?.inlineCss,
|
|
483
|
+
getBaseManifest: () => getBaseManifest(matchedRoutes),
|
|
484
|
+
})
|
|
812
485
|
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
(!!onEarlyHints || !!responseLinkHeader)
|
|
818
|
-
const sentEarlyHintLinks = shouldCollectEarlyHints
|
|
819
|
-
? new Set<string>()
|
|
820
|
-
: undefined
|
|
821
|
-
const sentEarlyHints = onEarlyHints ? new Array<EarlyHint>() : undefined
|
|
822
|
-
const responseLinkHeaderEntries =
|
|
823
|
-
shouldCollectEarlyHints && responseLinkHeader
|
|
824
|
-
? new Array<ResponseLinkHeaderEntry>()
|
|
825
|
-
: undefined
|
|
826
|
-
const responseLinkHeaderFilter = shouldCollectEarlyHints
|
|
827
|
-
? getResponseLinkHeaderFilter(responseLinkHeader)
|
|
828
|
-
: undefined
|
|
486
|
+
const earlyHints = createEarlyHintsForRequest({
|
|
487
|
+
onEarlyHints: requestOpts?.onEarlyHints,
|
|
488
|
+
responseLinkHeader: requestOpts?.responseLinkHeader,
|
|
489
|
+
})
|
|
829
490
|
|
|
830
|
-
|
|
831
|
-
shouldCollectEarlyHints &&
|
|
832
|
-
sentEarlyHintLinks &&
|
|
833
|
-
matchedRoutes?.length
|
|
834
|
-
) {
|
|
835
|
-
const hints = collectStaticHintsFromManifest(manifest, matchedRoutes)
|
|
836
|
-
handleCollectedEarlyHints({
|
|
837
|
-
phase: 'static',
|
|
838
|
-
hints,
|
|
839
|
-
sentLinks: sentEarlyHintLinks,
|
|
840
|
-
sentHints: sentEarlyHints,
|
|
841
|
-
onEarlyHints,
|
|
842
|
-
responseLinkHeaderEntries,
|
|
843
|
-
})
|
|
844
|
-
}
|
|
491
|
+
earlyHints?.collectStatic({ manifest, matchedRoutes })
|
|
845
492
|
|
|
846
493
|
const routerInstance = await getRouter()
|
|
847
494
|
|
|
@@ -860,18 +507,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
860
507
|
return routerInstance.state.redirect
|
|
861
508
|
}
|
|
862
509
|
|
|
863
|
-
|
|
864
|
-
const loadedMatches = routerInstance.stores.matches.get()
|
|
865
|
-
const hints = collectDynamicHintsFromMatches(loadedMatches)
|
|
866
|
-
handleCollectedEarlyHints({
|
|
867
|
-
phase: 'dynamic',
|
|
868
|
-
hints,
|
|
869
|
-
sentLinks: sentEarlyHintLinks,
|
|
870
|
-
sentHints: sentEarlyHints,
|
|
871
|
-
onEarlyHints,
|
|
872
|
-
responseLinkHeaderEntries,
|
|
873
|
-
})
|
|
874
|
-
}
|
|
510
|
+
earlyHints?.collectDynamic(routerInstance.stores.matches.get())
|
|
875
511
|
|
|
876
512
|
// Pass request-scoped assets to dehydrate for manifest injection
|
|
877
513
|
const ctx = getStartContext({ throwIfNotFound: false })
|
|
@@ -882,13 +518,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
882
518
|
const responseHeaders = getStartResponseHeaders({
|
|
883
519
|
router: routerInstance,
|
|
884
520
|
})
|
|
885
|
-
|
|
886
|
-
appendResponseLinkHeaders({
|
|
887
|
-
responseHeaders,
|
|
888
|
-
entries: responseLinkHeaderEntries,
|
|
889
|
-
filter: responseLinkHeaderFilter,
|
|
890
|
-
})
|
|
891
|
-
}
|
|
521
|
+
earlyHints?.appendResponseHeaders(responseHeaders)
|
|
892
522
|
cbWillCleanup = true
|
|
893
523
|
|
|
894
524
|
return cb({
|
|
@@ -937,6 +567,7 @@ export function createStartHandler<TRegister = Register>(
|
|
|
937
567
|
{
|
|
938
568
|
request,
|
|
939
569
|
pathname: url.pathname,
|
|
570
|
+
handlerType: 'router',
|
|
940
571
|
context: createNullProtoObject(requestOpts?.context),
|
|
941
572
|
},
|
|
942
573
|
)
|
|
@@ -1107,6 +738,7 @@ async function handleServerRoutes({
|
|
|
1107
738
|
context,
|
|
1108
739
|
params: routeParams,
|
|
1109
740
|
pathname,
|
|
741
|
+
handlerType: 'router',
|
|
1110
742
|
})
|
|
1111
743
|
|
|
1112
744
|
// RFC 9110 §9.3.2: HEAD must carry the same header fields as GET but no body.
|