@tanstack/start-server-core 1.158.4 → 1.159.0
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 +86 -1
- package/dist/esm/createStartHandler.js +64 -7
- package/dist/esm/createStartHandler.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/router-manifest.d.ts +7 -10
- package/dist/esm/router-manifest.js +9 -13
- package/dist/esm/router-manifest.js.map +1 -1
- package/dist/esm/transformAssetUrls.d.ts +115 -0
- package/dist/esm/transformAssetUrls.js +113 -0
- package/dist/esm/transformAssetUrls.js.map +1 -0
- package/package.json +1 -1
- package/src/createStartHandler.ts +224 -8
- package/src/index.tsx +9 -0
- package/src/router-manifest.ts +17 -17
- package/src/transformAssetUrls.ts +285 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { rootRouteId } from '@tanstack/router-core'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
Awaitable,
|
|
5
|
+
Manifest,
|
|
6
|
+
RouterManagedTag,
|
|
7
|
+
} from '@tanstack/router-core'
|
|
8
|
+
|
|
9
|
+
export type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry'
|
|
10
|
+
|
|
11
|
+
export interface TransformAssetUrlsContext {
|
|
12
|
+
url: string
|
|
13
|
+
type: AssetUrlType
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TransformAssetUrlsFn = (
|
|
17
|
+
context: TransformAssetUrlsContext,
|
|
18
|
+
) => Awaitable<string>
|
|
19
|
+
|
|
20
|
+
export type CreateTransformAssetUrlsContext =
|
|
21
|
+
| {
|
|
22
|
+
/** True when the server is computing the cached manifest during startup warmup. */
|
|
23
|
+
warmup: true
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
/**
|
|
27
|
+
* The current Request.
|
|
28
|
+
*
|
|
29
|
+
* Only available during request handling (i.e. when `warmup: false`).
|
|
30
|
+
*/
|
|
31
|
+
request: Request
|
|
32
|
+
/** False when transforming URLs as part of request handling. */
|
|
33
|
+
warmup: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Async factory that runs once per manifest computation and returns the
|
|
38
|
+
* per-asset transform.
|
|
39
|
+
*/
|
|
40
|
+
export type CreateTransformAssetUrlsFn = (
|
|
41
|
+
ctx: CreateTransformAssetUrlsContext,
|
|
42
|
+
) => Awaitable<TransformAssetUrlsFn>
|
|
43
|
+
|
|
44
|
+
type TransformAssetUrlsOptionsBase = {
|
|
45
|
+
/**
|
|
46
|
+
* Whether to cache the transformed manifest after the first request.
|
|
47
|
+
*
|
|
48
|
+
* When `true` (default), the transform runs once on the first request and
|
|
49
|
+
* the resulting manifest is reused for all subsequent requests in production.
|
|
50
|
+
*
|
|
51
|
+
* Set to `false` for per-request transforms (e.g. geo-routing to different
|
|
52
|
+
* CDNs based on request headers).
|
|
53
|
+
*
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
cache?: boolean
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* When `true`, warms up the cached transformed manifest in the background when
|
|
60
|
+
* the server starts (production only).
|
|
61
|
+
*
|
|
62
|
+
* This can reduce latency for the first request when `cache` is `true`.
|
|
63
|
+
* Has no effect when `cache: false` (per-request transforms) or in dev mode.
|
|
64
|
+
*
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
warmup?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type TransformAssetUrlsOptions =
|
|
71
|
+
| (TransformAssetUrlsOptionsBase & {
|
|
72
|
+
/**
|
|
73
|
+
* The transform to apply to asset URLs. Can be a string prefix or a callback.
|
|
74
|
+
*
|
|
75
|
+
* **String** — prepended to every asset URL.
|
|
76
|
+
* **Callback** — receives `{ url, type }` and returns a new URL.
|
|
77
|
+
*/
|
|
78
|
+
transform: string | TransformAssetUrlsFn
|
|
79
|
+
createTransform?: never
|
|
80
|
+
})
|
|
81
|
+
| (TransformAssetUrlsOptionsBase & {
|
|
82
|
+
/**
|
|
83
|
+
* Create a per-asset transform function.
|
|
84
|
+
*
|
|
85
|
+
* This factory runs once per manifest computation (per request when
|
|
86
|
+
* `cache: false`, or once per server when `cache: true`). It can do async
|
|
87
|
+
* setup work (fetch config, read from a KV, etc.) and return a fast
|
|
88
|
+
* per-asset transformer.
|
|
89
|
+
*/
|
|
90
|
+
createTransform: CreateTransformAssetUrlsFn
|
|
91
|
+
transform?: never
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
export type TransformAssetUrls =
|
|
95
|
+
| string
|
|
96
|
+
| TransformAssetUrlsFn
|
|
97
|
+
| TransformAssetUrlsOptions
|
|
98
|
+
|
|
99
|
+
export type ResolvedTransformAssetUrlsConfig =
|
|
100
|
+
| {
|
|
101
|
+
type: 'transform'
|
|
102
|
+
transformFn: TransformAssetUrlsFn
|
|
103
|
+
cache: boolean
|
|
104
|
+
}
|
|
105
|
+
| {
|
|
106
|
+
type: 'createTransform'
|
|
107
|
+
createTransform: CreateTransformAssetUrlsFn
|
|
108
|
+
cache: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolves a TransformAssetUrls value (string prefix, callback, or options
|
|
113
|
+
* object) into a concrete transform function and cache flag.
|
|
114
|
+
*/
|
|
115
|
+
export function resolveTransformConfig(
|
|
116
|
+
transform: TransformAssetUrls,
|
|
117
|
+
): ResolvedTransformAssetUrlsConfig {
|
|
118
|
+
// String shorthand
|
|
119
|
+
if (typeof transform === 'string') {
|
|
120
|
+
const prefix = transform
|
|
121
|
+
return {
|
|
122
|
+
type: 'transform',
|
|
123
|
+
transformFn: ({ url }) => `${prefix}${url}`,
|
|
124
|
+
cache: true,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Callback shorthand
|
|
129
|
+
if (typeof transform === 'function') {
|
|
130
|
+
return {
|
|
131
|
+
type: 'transform',
|
|
132
|
+
transformFn: transform,
|
|
133
|
+
cache: true,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Options object
|
|
138
|
+
if ('createTransform' in transform && transform.createTransform) {
|
|
139
|
+
return {
|
|
140
|
+
type: 'createTransform',
|
|
141
|
+
createTransform: transform.createTransform,
|
|
142
|
+
cache: transform.cache !== false,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const transformFn =
|
|
147
|
+
typeof transform.transform === 'string'
|
|
148
|
+
? ((({ url }: TransformAssetUrlsContext) =>
|
|
149
|
+
`${transform.transform}${url}`) as TransformAssetUrlsFn)
|
|
150
|
+
: transform.transform
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
type: 'transform',
|
|
154
|
+
transformFn,
|
|
155
|
+
cache: transform.cache !== false,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface StartManifestWithClientEntry {
|
|
160
|
+
manifest: Manifest
|
|
161
|
+
clientEntry: string
|
|
162
|
+
/** Script content prepended before the client entry import (dev only) */
|
|
163
|
+
injectedHeadScripts?: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Builds the client entry `<script>` tag from a (possibly transformed) client
|
|
168
|
+
* entry URL and optional injected head scripts.
|
|
169
|
+
*/
|
|
170
|
+
export function buildClientEntryScriptTag(
|
|
171
|
+
clientEntry: string,
|
|
172
|
+
injectedHeadScripts?: string,
|
|
173
|
+
): RouterManagedTag {
|
|
174
|
+
const clientEntryLiteral = JSON.stringify(clientEntry)
|
|
175
|
+
let script = `import(${clientEntryLiteral})`
|
|
176
|
+
if (injectedHeadScripts) {
|
|
177
|
+
script = `${injectedHeadScripts};${script}`
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
tag: 'script',
|
|
181
|
+
attrs: {
|
|
182
|
+
type: 'module',
|
|
183
|
+
async: true,
|
|
184
|
+
},
|
|
185
|
+
children: script,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Applies a URL transform to every asset URL in the manifest and returns a
|
|
191
|
+
* new manifest with a client entry script tag appended to the root route's
|
|
192
|
+
* assets.
|
|
193
|
+
*
|
|
194
|
+
* The source manifest is deep-cloned so the cached original is never mutated.
|
|
195
|
+
*/
|
|
196
|
+
export function transformManifestUrls(
|
|
197
|
+
source: StartManifestWithClientEntry,
|
|
198
|
+
transformFn: TransformAssetUrlsFn,
|
|
199
|
+
opts?: {
|
|
200
|
+
/** When true, clone the source manifest before mutating it. */
|
|
201
|
+
clone?: boolean
|
|
202
|
+
},
|
|
203
|
+
): Promise<Manifest> {
|
|
204
|
+
return (async () => {
|
|
205
|
+
const manifest = opts?.clone
|
|
206
|
+
? structuredClone(source.manifest)
|
|
207
|
+
: source.manifest
|
|
208
|
+
|
|
209
|
+
for (const route of Object.values(manifest.routes)) {
|
|
210
|
+
// Transform preload URLs (modulepreload)
|
|
211
|
+
if (route.preloads) {
|
|
212
|
+
route.preloads = await Promise.all(
|
|
213
|
+
route.preloads.map((url) =>
|
|
214
|
+
Promise.resolve(transformFn({ url, type: 'modulepreload' })),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Transform asset tag URLs
|
|
220
|
+
if (route.assets) {
|
|
221
|
+
for (const asset of route.assets) {
|
|
222
|
+
if (asset.tag === 'link' && asset.attrs?.href) {
|
|
223
|
+
asset.attrs.href = await Promise.resolve(
|
|
224
|
+
transformFn({
|
|
225
|
+
url: asset.attrs.href,
|
|
226
|
+
type: 'stylesheet',
|
|
227
|
+
}),
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Transform and append the client entry script tag
|
|
235
|
+
const transformedClientEntry = await Promise.resolve(
|
|
236
|
+
transformFn({
|
|
237
|
+
url: source.clientEntry,
|
|
238
|
+
type: 'clientEntry',
|
|
239
|
+
}),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const rootRoute = manifest.routes[rootRouteId]
|
|
243
|
+
if (rootRoute) {
|
|
244
|
+
rootRoute.assets = rootRoute.assets || []
|
|
245
|
+
rootRoute.assets.push(
|
|
246
|
+
buildClientEntryScriptTag(
|
|
247
|
+
transformedClientEntry,
|
|
248
|
+
source.injectedHeadScripts,
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return manifest
|
|
254
|
+
})()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Builds a final Manifest from a StartManifestWithClientEntry without any
|
|
259
|
+
* URL transforms. Used when no transformAssetUrls option is provided.
|
|
260
|
+
*
|
|
261
|
+
* Returns a new manifest object so the cached base manifest is never mutated.
|
|
262
|
+
*/
|
|
263
|
+
export function buildManifestWithClientEntry(
|
|
264
|
+
source: StartManifestWithClientEntry,
|
|
265
|
+
): Manifest {
|
|
266
|
+
const scriptTag = buildClientEntryScriptTag(
|
|
267
|
+
source.clientEntry,
|
|
268
|
+
source.injectedHeadScripts,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
const baseRootRoute = source.manifest.routes[rootRouteId]
|
|
272
|
+
const routes = {
|
|
273
|
+
...source.manifest.routes,
|
|
274
|
+
...(baseRootRoute
|
|
275
|
+
? {
|
|
276
|
+
[rootRouteId]: {
|
|
277
|
+
...baseRootRoute,
|
|
278
|
+
assets: [...(baseRootRoute.assets || []), scriptTag],
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
: {}),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { routes }
|
|
285
|
+
}
|