@tanstack/start-server-core 1.167.2 → 1.167.4

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.
@@ -1,12 +1,39 @@
1
- import { rootRouteId } from '@tanstack/router-core'
1
+ import { resolveManifestAssetLink, rootRouteId } from '@tanstack/router-core'
2
2
 
3
3
  import type {
4
+ AssetCrossOrigin,
4
5
  Awaitable,
5
6
  Manifest,
7
+ ManifestAssetLink,
6
8
  RouterManagedTag,
7
9
  } from '@tanstack/router-core'
8
10
 
9
- export type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry'
11
+ export type { AssetCrossOrigin }
12
+
13
+ export type TransformAssetKind = 'modulepreload' | 'stylesheet' | 'clientEntry'
14
+
15
+ type TransformAssetsShorthandCrossOriginKind = Exclude<
16
+ TransformAssetKind,
17
+ 'clientEntry'
18
+ >
19
+
20
+ export type AssetUrlType = TransformAssetKind
21
+
22
+ export interface TransformAssetsContext {
23
+ url: string
24
+ kind: TransformAssetKind
25
+ }
26
+
27
+ export type TransformAssetResult =
28
+ | string
29
+ | {
30
+ href: string
31
+ crossOrigin?: AssetCrossOrigin
32
+ }
33
+
34
+ export type TransformAssetsFn = (
35
+ context: TransformAssetsContext,
36
+ ) => Awaitable<TransformAssetResult>
10
37
 
11
38
  export interface TransformAssetUrlsContext {
12
39
  url: string
@@ -41,6 +68,10 @@ export type CreateTransformAssetUrlsFn = (
41
68
  ctx: CreateTransformAssetUrlsContext,
42
69
  ) => Awaitable<TransformAssetUrlsFn>
43
70
 
71
+ export type CreateTransformAssetsFn = (
72
+ ctx: CreateTransformAssetUrlsContext,
73
+ ) => Awaitable<TransformAssetsFn>
74
+
44
75
  type TransformAssetUrlsOptionsBase = {
45
76
  /**
46
77
  * Whether to cache the transformed manifest after the first request.
@@ -91,41 +122,132 @@ export type TransformAssetUrlsOptions =
91
122
  transform?: never
92
123
  })
93
124
 
125
+ export type TransformAssetsOptions =
126
+ | (TransformAssetUrlsOptionsBase & {
127
+ transform: string | TransformAssetsFn
128
+ createTransform?: never
129
+ })
130
+ | (TransformAssetUrlsOptionsBase & {
131
+ createTransform: CreateTransformAssetsFn
132
+ transform?: never
133
+ })
134
+
94
135
  export type TransformAssetUrls =
95
136
  | string
96
137
  | TransformAssetUrlsFn
97
138
  | TransformAssetUrlsOptions
98
139
 
99
- export type ResolvedTransformAssetUrlsConfig =
140
+ /**
141
+ * Per-kind crossOrigin configuration for the object shorthand.
142
+ *
143
+ * Accepts either a single value applied to all asset kinds, or a per-kind
144
+ * record (matching `HeadContent`'s `assetCrossOrigin` shape):
145
+ *
146
+ * ```ts
147
+ * // All assets get the same value
148
+ * crossOrigin: 'anonymous'
149
+ *
150
+ * // Different values per kind
151
+ * crossOrigin: { modulepreload: 'anonymous', stylesheet: 'use-credentials' }
152
+ * ```
153
+ */
154
+ export type TransformAssetsCrossOriginConfig =
155
+ | AssetCrossOrigin
156
+ | Partial<Record<TransformAssetsShorthandCrossOriginKind, AssetCrossOrigin>>
157
+
158
+ /**
159
+ * Object shorthand for `transformAssets`. Combines a URL prefix with optional
160
+ * per-asset `crossOrigin` without needing a callback:
161
+ *
162
+ * ```ts
163
+ * transformAssets: {
164
+ * prefix: 'https://cdn.example.com',
165
+ * crossOrigin: 'anonymous',
166
+ * }
167
+ * ```
168
+ */
169
+ export interface TransformAssetsObjectShorthand {
170
+ /** URL prefix prepended to every asset URL. */
171
+ prefix: string
172
+ /**
173
+ * Optional crossOrigin attribute applied to manifest-managed `<link>` assets.
174
+ *
175
+ * Accepts a single value or a per-kind record.
176
+ */
177
+ crossOrigin?: TransformAssetsCrossOriginConfig
178
+ }
179
+
180
+ export type TransformAssets =
181
+ | string
182
+ | TransformAssetsFn
183
+ | TransformAssetsObjectShorthand
184
+ | TransformAssetsOptions
185
+
186
+ export type ResolvedTransformAssetsConfig =
100
187
  | {
101
188
  type: 'transform'
102
- transformFn: TransformAssetUrlsFn
189
+ transformFn: TransformAssetsFn
103
190
  cache: boolean
104
191
  }
105
192
  | {
106
193
  type: 'createTransform'
107
- createTransform: CreateTransformAssetUrlsFn
194
+ createTransform: CreateTransformAssetsFn
108
195
  cache: boolean
109
196
  }
110
197
 
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
198
+ let hasWarnedAboutDeprecatedTransformAssetUrls = false
199
+
200
+ export function warnDeprecatedTransformAssetUrls() {
201
+ if (
202
+ (process.env.NODE_ENV === 'development' ||
203
+ process.env.TSS_DEV_SERVER === 'true') &&
204
+ !hasWarnedAboutDeprecatedTransformAssetUrls
205
+ ) {
206
+ hasWarnedAboutDeprecatedTransformAssetUrls = true
207
+ console.warn(
208
+ '[TanStack Start] `transformAssetUrls` is deprecated. Use `transformAssets` instead.',
209
+ )
210
+ }
211
+ }
212
+
213
+ function normalizeTransformAssetResult(
214
+ result: TransformAssetResult,
215
+ ): Exclude<TransformAssetResult, string> {
216
+ if (typeof result === 'string') {
217
+ return { href: result }
218
+ }
219
+
220
+ return result
221
+ }
222
+
223
+ function resolveTransformAssetsCrossOrigin(
224
+ config: TransformAssetsCrossOriginConfig | undefined,
225
+ kind: TransformAssetsShorthandCrossOriginKind,
226
+ ): AssetCrossOrigin | undefined {
227
+ if (!config) return undefined
228
+ if (typeof config === 'string') return config
229
+
230
+ return config[kind]
231
+ }
232
+
233
+ function isObjectShorthand(
234
+ transform: TransformAssetsObjectShorthand | TransformAssetsOptions,
235
+ ): transform is TransformAssetsObjectShorthand {
236
+ return 'prefix' in transform
237
+ }
238
+
239
+ export function resolveTransformAssetsConfig(
240
+ transform: TransformAssets,
241
+ ): ResolvedTransformAssetsConfig {
119
242
  if (typeof transform === 'string') {
120
243
  const prefix = transform
121
244
  return {
122
245
  type: 'transform',
123
- transformFn: ({ url }) => `${prefix}${url}`,
246
+ transformFn: ({ url }) => ({ href: `${prefix}${url}` }),
124
247
  cache: true,
125
248
  }
126
249
  }
127
250
 
128
- // Callback shorthand
129
251
  if (typeof transform === 'function') {
130
252
  return {
131
253
  type: 'transform',
@@ -134,7 +256,26 @@ export function resolveTransformConfig(
134
256
  }
135
257
  }
136
258
 
137
- // Options object
259
+ // Object shorthand: { prefix, crossOrigin? }
260
+ if (isObjectShorthand(transform)) {
261
+ const { prefix, crossOrigin } = transform
262
+
263
+ return {
264
+ type: 'transform',
265
+ transformFn: ({ url, kind }) => {
266
+ const href = `${prefix}${url}`
267
+
268
+ if (kind === 'clientEntry') {
269
+ return { href }
270
+ }
271
+
272
+ const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind)
273
+ return co ? { href, crossOrigin: co } : { href }
274
+ },
275
+ cache: true,
276
+ }
277
+ }
278
+
138
279
  if ('createTransform' in transform && transform.createTransform) {
139
280
  return {
140
281
  type: 'createTransform',
@@ -145,8 +286,9 @@ export function resolveTransformConfig(
145
286
 
146
287
  const transformFn =
147
288
  typeof transform.transform === 'string'
148
- ? ((({ url }: TransformAssetUrlsContext) =>
149
- `${transform.transform}${url}`) as TransformAssetUrlsFn)
289
+ ? ((({ url }: TransformAssetsContext) => ({
290
+ href: `${transform.transform}${url}`,
291
+ })) as TransformAssetsFn)
150
292
  : transform.transform
151
293
 
152
294
  return {
@@ -156,6 +298,48 @@ export function resolveTransformConfig(
156
298
  }
157
299
  }
158
300
 
301
+ export function adaptTransformAssetUrlsToTransformAssets(
302
+ transformFn: TransformAssetUrlsFn,
303
+ ): TransformAssetsFn {
304
+ return async ({ url, kind }) => ({
305
+ href: await transformFn({ url, type: kind }),
306
+ })
307
+ }
308
+
309
+ export function adaptTransformAssetUrlsConfigToTransformAssets(
310
+ transform: TransformAssetUrls,
311
+ ): TransformAssets {
312
+ warnDeprecatedTransformAssetUrls()
313
+
314
+ if (typeof transform === 'string') {
315
+ return transform
316
+ }
317
+
318
+ if (typeof transform === 'function') {
319
+ return adaptTransformAssetUrlsToTransformAssets(transform)
320
+ }
321
+
322
+ if ('createTransform' in transform && transform.createTransform) {
323
+ return {
324
+ createTransform: async (ctx: CreateTransformAssetUrlsContext) =>
325
+ adaptTransformAssetUrlsToTransformAssets(
326
+ await transform.createTransform(ctx),
327
+ ),
328
+ cache: transform.cache,
329
+ warmup: transform.warmup,
330
+ }
331
+ }
332
+
333
+ return {
334
+ transform:
335
+ typeof transform.transform === 'string'
336
+ ? transform.transform
337
+ : adaptTransformAssetUrlsToTransformAssets(transform.transform),
338
+ cache: transform.cache,
339
+ warmup: transform.warmup,
340
+ }
341
+ }
342
+
159
343
  export interface StartManifestWithClientEntry {
160
344
  manifest: Manifest
161
345
  clientEntry: string
@@ -186,72 +370,93 @@ export function buildClientEntryScriptTag(
186
370
  }
187
371
  }
188
372
 
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(
373
+ function assignManifestAssetLink(
374
+ link: ManifestAssetLink,
375
+ next: { href: string; crossOrigin?: AssetCrossOrigin },
376
+ ): ManifestAssetLink {
377
+ if (typeof link === 'string') {
378
+ return next.crossOrigin ? next : next.href
379
+ }
380
+
381
+ return next.crossOrigin ? next : { href: next.href }
382
+ }
383
+
384
+ export async function transformManifestAssets(
197
385
  source: StartManifestWithClientEntry,
198
- transformFn: TransformAssetUrlsFn,
199
- opts?: {
200
- /** When true, clone the source manifest before mutating it. */
386
+ transformFn: TransformAssetsFn,
387
+ _opts?: {
201
388
  clone?: boolean
202
389
  },
203
390
  ): 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
- }
391
+ const manifest = structuredClone(source.manifest)
218
392
 
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
- )
393
+ for (const route of Object.values(manifest.routes)) {
394
+ if (route.preloads) {
395
+ route.preloads = await Promise.all(
396
+ route.preloads.map(async (link) => {
397
+ const resolved = resolveManifestAssetLink(link)
398
+ const result = normalizeTransformAssetResult(
399
+ await transformFn({
400
+ url: resolved.href,
401
+ kind: 'modulepreload',
402
+ }),
403
+ )
404
+
405
+ return assignManifestAssetLink(link, {
406
+ href: result.href,
407
+ crossOrigin: result.crossOrigin,
408
+ })
409
+ }),
410
+ )
411
+ }
412
+
413
+ if (route.assets) {
414
+ for (const asset of route.assets) {
415
+ if (asset.tag === 'link' && asset.attrs?.href) {
416
+ const rel = asset.attrs.rel
417
+ const relTokens = typeof rel === 'string' ? rel.split(/\s+/) : []
418
+
419
+ if (!relTokens.includes('stylesheet')) {
420
+ continue
421
+ }
422
+
423
+ const result = normalizeTransformAssetResult(
424
+ await transformFn({
425
+ url: asset.attrs.href,
426
+ kind: 'stylesheet',
427
+ }),
428
+ )
429
+
430
+ asset.attrs.href = result.href
431
+ if (result.crossOrigin) {
432
+ asset.attrs.crossOrigin = result.crossOrigin
433
+ } else {
434
+ delete asset.attrs.crossOrigin
229
435
  }
230
436
  }
231
437
  }
232
438
  }
439
+ }
233
440
 
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
- )
441
+ const transformedClientEntry = normalizeTransformAssetResult(
442
+ await transformFn({
443
+ url: source.clientEntry,
444
+ kind: 'clientEntry',
445
+ }),
446
+ )
241
447
 
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
- }
448
+ const rootRoute = manifest.routes[rootRouteId]
449
+ if (rootRoute) {
450
+ rootRoute.assets = rootRoute.assets || []
451
+ rootRoute.assets.push(
452
+ buildClientEntryScriptTag(
453
+ transformedClientEntry.href,
454
+ source.injectedHeadScripts,
455
+ ),
456
+ )
457
+ }
252
458
 
253
- return manifest
254
- })()
459
+ return manifest
255
460
  }
256
461
 
257
462
  /**