@tanstack/start-server-core 1.167.2 → 1.167.3

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,15 +1,30 @@
1
- import { rootRouteId } from "@tanstack/router-core";
1
+ import { resolveManifestAssetLink, rootRouteId } from "@tanstack/router-core";
2
2
  //#region src/transformAssetUrls.ts
3
- /**
4
- * Resolves a TransformAssetUrls value (string prefix, callback, or options
5
- * object) into a concrete transform function and cache flag.
6
- */
7
- function resolveTransformConfig(transform) {
3
+ var hasWarnedAboutDeprecatedTransformAssetUrls = false;
4
+ function warnDeprecatedTransformAssetUrls() {
5
+ if ((process.env.NODE_ENV === "development" || process.env.TSS_DEV_SERVER === "true") && !hasWarnedAboutDeprecatedTransformAssetUrls) {
6
+ hasWarnedAboutDeprecatedTransformAssetUrls = true;
7
+ console.warn("[TanStack Start] `transformAssetUrls` is deprecated. Use `transformAssets` instead.");
8
+ }
9
+ }
10
+ function normalizeTransformAssetResult(result) {
11
+ if (typeof result === "string") return { href: result };
12
+ return result;
13
+ }
14
+ function resolveTransformAssetsCrossOrigin(config, kind) {
15
+ if (!config) return void 0;
16
+ if (typeof config === "string") return config;
17
+ return config[kind];
18
+ }
19
+ function isObjectShorthand(transform) {
20
+ return "prefix" in transform;
21
+ }
22
+ function resolveTransformAssetsConfig(transform) {
8
23
  if (typeof transform === "string") {
9
24
  const prefix = transform;
10
25
  return {
11
26
  type: "transform",
12
- transformFn: ({ url }) => `${prefix}${url}`,
27
+ transformFn: ({ url }) => ({ href: `${prefix}${url}` }),
13
28
  cache: true
14
29
  };
15
30
  }
@@ -18,6 +33,22 @@ function resolveTransformConfig(transform) {
18
33
  transformFn: transform,
19
34
  cache: true
20
35
  };
36
+ if (isObjectShorthand(transform)) {
37
+ const { prefix, crossOrigin } = transform;
38
+ return {
39
+ type: "transform",
40
+ transformFn: ({ url, kind }) => {
41
+ const href = `${prefix}${url}`;
42
+ if (kind === "clientEntry") return { href };
43
+ const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind);
44
+ return co ? {
45
+ href,
46
+ crossOrigin: co
47
+ } : { href };
48
+ },
49
+ cache: true
50
+ };
51
+ }
21
52
  if ("createTransform" in transform && transform.createTransform) return {
22
53
  type: "createTransform",
23
54
  createTransform: transform.createTransform,
@@ -25,10 +56,31 @@ function resolveTransformConfig(transform) {
25
56
  };
26
57
  return {
27
58
  type: "transform",
28
- transformFn: typeof transform.transform === "string" ? (({ url }) => `${transform.transform}${url}`) : transform.transform,
59
+ transformFn: typeof transform.transform === "string" ? (({ url }) => ({ href: `${transform.transform}${url}` })) : transform.transform,
29
60
  cache: transform.cache !== false
30
61
  };
31
62
  }
63
+ function adaptTransformAssetUrlsToTransformAssets(transformFn) {
64
+ return async ({ url, kind }) => ({ href: await transformFn({
65
+ url,
66
+ type: kind
67
+ }) });
68
+ }
69
+ function adaptTransformAssetUrlsConfigToTransformAssets(transform) {
70
+ warnDeprecatedTransformAssetUrls();
71
+ if (typeof transform === "string") return transform;
72
+ if (typeof transform === "function") return adaptTransformAssetUrlsToTransformAssets(transform);
73
+ if ("createTransform" in transform && transform.createTransform) return {
74
+ createTransform: async (ctx) => adaptTransformAssetUrlsToTransformAssets(await transform.createTransform(ctx)),
75
+ cache: transform.cache,
76
+ warmup: transform.warmup
77
+ };
78
+ return {
79
+ transform: typeof transform.transform === "string" ? transform.transform : adaptTransformAssetUrlsToTransformAssets(transform.transform),
80
+ cache: transform.cache,
81
+ warmup: transform.warmup
82
+ };
83
+ }
32
84
  /**
33
85
  * Builds the client entry `<script>` tag from a (possibly transformed) client
34
86
  * entry URL and optional injected head scripts.
@@ -45,39 +97,47 @@ function buildClientEntryScriptTag(clientEntry, injectedHeadScripts) {
45
97
  children: script
46
98
  };
47
99
  }
48
- /**
49
- * Applies a URL transform to every asset URL in the manifest and returns a
50
- * new manifest with a client entry script tag appended to the root route's
51
- * assets.
52
- *
53
- * The source manifest is deep-cloned so the cached original is never mutated.
54
- */
55
- function transformManifestUrls(source, transformFn, opts) {
56
- return (async () => {
57
- const manifest = opts?.clone ? structuredClone(source.manifest) : source.manifest;
58
- for (const route of Object.values(manifest.routes)) {
59
- if (route.preloads) route.preloads = await Promise.all(route.preloads.map((url) => Promise.resolve(transformFn({
60
- url,
61
- type: "modulepreload"
62
- }))));
63
- if (route.assets) {
64
- for (const asset of route.assets) if (asset.tag === "link" && asset.attrs?.href) asset.attrs.href = await Promise.resolve(transformFn({
100
+ function assignManifestAssetLink(link, next) {
101
+ if (typeof link === "string") return next.crossOrigin ? next : next.href;
102
+ return next.crossOrigin ? next : { href: next.href };
103
+ }
104
+ async function transformManifestAssets(source, transformFn, _opts) {
105
+ const manifest = structuredClone(source.manifest);
106
+ for (const route of Object.values(manifest.routes)) {
107
+ if (route.preloads) route.preloads = await Promise.all(route.preloads.map(async (link) => {
108
+ const result = normalizeTransformAssetResult(await transformFn({
109
+ url: resolveManifestAssetLink(link).href,
110
+ kind: "modulepreload"
111
+ }));
112
+ return assignManifestAssetLink(link, {
113
+ href: result.href,
114
+ crossOrigin: result.crossOrigin
115
+ });
116
+ }));
117
+ if (route.assets) {
118
+ for (const asset of route.assets) if (asset.tag === "link" && asset.attrs?.href) {
119
+ const rel = asset.attrs.rel;
120
+ if (!(typeof rel === "string" ? rel.split(/\s+/) : []).includes("stylesheet")) continue;
121
+ const result = normalizeTransformAssetResult(await transformFn({
65
122
  url: asset.attrs.href,
66
- type: "stylesheet"
123
+ kind: "stylesheet"
67
124
  }));
125
+ asset.attrs.href = result.href;
126
+ if (result.crossOrigin) asset.attrs.crossOrigin = result.crossOrigin;
127
+ else delete asset.attrs.crossOrigin;
68
128
  }
69
129
  }
70
- const transformedClientEntry = await Promise.resolve(transformFn({
71
- url: source.clientEntry,
72
- type: "clientEntry"
73
- }));
74
- const rootRoute = manifest.routes[rootRouteId];
75
- if (rootRoute) {
76
- rootRoute.assets = rootRoute.assets || [];
77
- rootRoute.assets.push(buildClientEntryScriptTag(transformedClientEntry, source.injectedHeadScripts));
78
- }
79
- return manifest;
80
- })();
130
+ }
131
+ const transformedClientEntry = normalizeTransformAssetResult(await transformFn({
132
+ url: source.clientEntry,
133
+ kind: "clientEntry"
134
+ }));
135
+ const rootRoute = manifest.routes[rootRouteId];
136
+ if (rootRoute) {
137
+ rootRoute.assets = rootRoute.assets || [];
138
+ rootRoute.assets.push(buildClientEntryScriptTag(transformedClientEntry.href, source.injectedHeadScripts));
139
+ }
140
+ return manifest;
81
141
  }
82
142
  /**
83
143
  * Builds a final Manifest from a StartManifestWithClientEntry without any
@@ -97,6 +157,6 @@ function buildManifestWithClientEntry(source) {
97
157
  } };
98
158
  }
99
159
  //#endregion
100
- export { buildManifestWithClientEntry, resolveTransformConfig, transformManifestUrls };
160
+ export { adaptTransformAssetUrlsConfigToTransformAssets, buildManifestWithClientEntry, resolveTransformAssetsConfig, transformManifestAssets };
101
161
 
102
162
  //# sourceMappingURL=transformAssetUrls.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transformAssetUrls.js","names":[],"sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import { rootRouteId } from '@tanstack/router-core'\n\nimport type {\n Awaitable,\n Manifest,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type AssetUrlType = 'modulepreload' | 'stylesheet' | 'clientEntry'\n\nexport interface TransformAssetUrlsContext {\n url: string\n type: AssetUrlType\n}\n\nexport type TransformAssetUrlsFn = (\n context: TransformAssetUrlsContext,\n) => Awaitable<string>\n\nexport type CreateTransformAssetUrlsContext =\n | {\n /** True when the server is computing the cached manifest during startup warmup. */\n warmup: true\n }\n | {\n /**\n * The current Request.\n *\n * Only available during request handling (i.e. when `warmup: false`).\n */\n request: Request\n /** False when transforming URLs as part of request handling. */\n warmup: false\n }\n\n/**\n * Async factory that runs once per manifest computation and returns the\n * per-asset transform.\n */\nexport type CreateTransformAssetUrlsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetUrlsFn>\n\ntype TransformAssetUrlsOptionsBase = {\n /**\n * Whether to cache the transformed manifest after the first request.\n *\n * When `true` (default), the transform runs once on the first request and\n * the resulting manifest is reused for all subsequent requests in production.\n *\n * Set to `false` for per-request transforms (e.g. geo-routing to different\n * CDNs based on request headers).\n *\n * @default true\n */\n cache?: boolean\n\n /**\n * When `true`, warms up the cached transformed manifest in the background when\n * the server starts (production only).\n *\n * This can reduce latency for the first request when `cache` is `true`.\n * Has no effect when `cache: false` (per-request transforms) or in dev mode.\n *\n * @default false\n */\n warmup?: boolean\n}\n\nexport type TransformAssetUrlsOptions =\n | (TransformAssetUrlsOptionsBase & {\n /**\n * The transform to apply to asset URLs. Can be a string prefix or a callback.\n *\n * **String** — prepended to every asset URL.\n * **Callback** — receives `{ url, type }` and returns a new URL.\n */\n transform: string | TransformAssetUrlsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n /**\n * Create a per-asset transform function.\n *\n * This factory runs once per manifest computation (per request when\n * `cache: false`, or once per server when `cache: true`). It can do async\n * setup work (fetch config, read from a KV, etc.) and return a fast\n * per-asset transformer.\n */\n createTransform: CreateTransformAssetUrlsFn\n transform?: never\n })\n\nexport type TransformAssetUrls =\n | string\n | TransformAssetUrlsFn\n | TransformAssetUrlsOptions\n\nexport type ResolvedTransformAssetUrlsConfig =\n | {\n type: 'transform'\n transformFn: TransformAssetUrlsFn\n cache: boolean\n }\n | {\n type: 'createTransform'\n createTransform: CreateTransformAssetUrlsFn\n cache: boolean\n }\n\n/**\n * Resolves a TransformAssetUrls value (string prefix, callback, or options\n * object) into a concrete transform function and cache flag.\n */\nexport function resolveTransformConfig(\n transform: TransformAssetUrls,\n): ResolvedTransformAssetUrlsConfig {\n // String shorthand\n if (typeof transform === 'string') {\n const prefix = transform\n return {\n type: 'transform',\n transformFn: ({ url }) => `${prefix}${url}`,\n cache: true,\n }\n }\n\n // Callback shorthand\n if (typeof transform === 'function') {\n return {\n type: 'transform',\n transformFn: transform,\n cache: true,\n }\n }\n\n // Options object\n if ('createTransform' in transform && transform.createTransform) {\n return {\n type: 'createTransform',\n createTransform: transform.createTransform,\n cache: transform.cache !== false,\n }\n }\n\n const transformFn =\n typeof transform.transform === 'string'\n ? ((({ url }: TransformAssetUrlsContext) =>\n `${transform.transform}${url}`) as TransformAssetUrlsFn)\n : transform.transform\n\n return {\n type: 'transform',\n transformFn,\n cache: transform.cache !== false,\n }\n}\n\nexport interface StartManifestWithClientEntry {\n manifest: Manifest\n clientEntry: string\n /** Script content prepended before the client entry import (dev only) */\n injectedHeadScripts?: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL and optional injected head scripts.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n injectedHeadScripts?: string,\n): RouterManagedTag {\n const clientEntryLiteral = JSON.stringify(clientEntry)\n let script = `import(${clientEntryLiteral})`\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts};${script}`\n }\n return {\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n }\n}\n\n/**\n * Applies a URL transform to every asset URL in the manifest and returns a\n * new manifest with a client entry script tag appended to the root route's\n * assets.\n *\n * The source manifest is deep-cloned so the cached original is never mutated.\n */\nexport function transformManifestUrls(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetUrlsFn,\n opts?: {\n /** When true, clone the source manifest before mutating it. */\n clone?: boolean\n },\n): Promise<Manifest> {\n return (async () => {\n const manifest = opts?.clone\n ? structuredClone(source.manifest)\n : source.manifest\n\n for (const route of Object.values(manifest.routes)) {\n // Transform preload URLs (modulepreload)\n if (route.preloads) {\n route.preloads = await Promise.all(\n route.preloads.map((url) =>\n Promise.resolve(transformFn({ url, type: 'modulepreload' })),\n ),\n )\n }\n\n // Transform asset tag URLs\n if (route.assets) {\n for (const asset of route.assets) {\n if (asset.tag === 'link' && asset.attrs?.href) {\n asset.attrs.href = await Promise.resolve(\n transformFn({\n url: asset.attrs.href,\n type: 'stylesheet',\n }),\n )\n }\n }\n }\n }\n\n // Transform and append the client entry script tag\n const transformedClientEntry = await Promise.resolve(\n transformFn({\n url: source.clientEntry,\n type: 'clientEntry',\n }),\n )\n\n const rootRoute = manifest.routes[rootRouteId]\n if (rootRoute) {\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry,\n source.injectedHeadScripts,\n ),\n )\n }\n\n return manifest\n })()\n}\n\n/**\n * Builds a final Manifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssetUrls option is provided.\n *\n * Returns a new manifest object so the cached base manifest is never mutated.\n */\nexport function buildManifestWithClientEntry(\n source: StartManifestWithClientEntry,\n): Manifest {\n const scriptTag = buildClientEntryScriptTag(\n source.clientEntry,\n source.injectedHeadScripts,\n )\n\n const baseRootRoute = source.manifest.routes[rootRouteId]\n const routes = {\n ...source.manifest.routes,\n ...(baseRootRoute\n ? {\n [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute.assets || []), scriptTag],\n },\n }\n : {}),\n }\n\n return { routes }\n}\n"],"mappings":";;;;;;AAkHA,SAAgB,uBACd,WACkC;AAElC,KAAI,OAAO,cAAc,UAAU;EACjC,MAAM,SAAS;AACf,SAAO;GACL,MAAM;GACN,cAAc,EAAE,UAAU,GAAG,SAAS;GACtC,OAAO;GACR;;AAIH,KAAI,OAAO,cAAc,WACvB,QAAO;EACL,MAAM;EACN,aAAa;EACb,OAAO;EACR;AAIH,KAAI,qBAAqB,aAAa,UAAU,gBAC9C,QAAO;EACL,MAAM;EACN,iBAAiB,UAAU;EAC3B,OAAO,UAAU,UAAU;EAC5B;AASH,QAAO;EACL,MAAM;EACN,aAPA,OAAO,UAAU,cAAc,aACxB,EAAE,UACH,GAAG,UAAU,YAAY,SAC3B,UAAU;EAKd,OAAO,UAAU,UAAU;EAC5B;;;;;;AAcH,SAAgB,0BACd,aACA,qBACkB;CAElB,IAAI,SAAS,UADc,KAAK,UAAU,YAAY,CACZ;AAC1C,KAAI,oBACF,UAAS,GAAG,oBAAoB,GAAG;AAErC,QAAO;EACL,KAAK;EACL,OAAO;GACL,MAAM;GACN,OAAO;GACR;EACD,UAAU;EACX;;;;;;;;;AAUH,SAAgB,sBACd,QACA,aACA,MAImB;AACnB,SAAQ,YAAY;EAClB,MAAM,WAAW,MAAM,QACnB,gBAAgB,OAAO,SAAS,GAChC,OAAO;AAEX,OAAK,MAAM,SAAS,OAAO,OAAO,SAAS,OAAO,EAAE;AAElD,OAAI,MAAM,SACR,OAAM,WAAW,MAAM,QAAQ,IAC7B,MAAM,SAAS,KAAK,QAClB,QAAQ,QAAQ,YAAY;IAAE;IAAK,MAAM;IAAiB,CAAC,CAAC,CAC7D,CACF;AAIH,OAAI,MAAM;SACH,MAAM,SAAS,MAAM,OACxB,KAAI,MAAM,QAAQ,UAAU,MAAM,OAAO,KACvC,OAAM,MAAM,OAAO,MAAM,QAAQ,QAC/B,YAAY;KACV,KAAK,MAAM,MAAM;KACjB,MAAM;KACP,CAAC,CACH;;;EAOT,MAAM,yBAAyB,MAAM,QAAQ,QAC3C,YAAY;GACV,KAAK,OAAO;GACZ,MAAM;GACP,CAAC,CACH;EAED,MAAM,YAAY,SAAS,OAAO;AAClC,MAAI,WAAW;AACb,aAAU,SAAS,UAAU,UAAU,EAAE;AACzC,aAAU,OAAO,KACf,0BACE,wBACA,OAAO,oBACR,CACF;;AAGH,SAAO;KACL;;;;;;;;AASN,SAAgB,6BACd,QACU;CACV,MAAM,YAAY,0BAChB,OAAO,aACP,OAAO,oBACR;CAED,MAAM,gBAAgB,OAAO,SAAS,OAAO;AAa7C,QAAO,EAAE,QAZM;EACb,GAAG,OAAO,SAAS;EACnB,GAAI,gBACA,GACG,cAAc;GACb,GAAG;GACH,QAAQ,CAAC,GAAI,cAAc,UAAU,EAAE,EAAG,UAAU;GACrD,EACF,GACD,EAAE;EACP,EAEgB"}
1
+ {"version":3,"file":"transformAssetUrls.js","names":[],"sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import { resolveManifestAssetLink, rootRouteId } from '@tanstack/router-core'\n\nimport type {\n AssetCrossOrigin,\n Awaitable,\n Manifest,\n ManifestAssetLink,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nexport type { AssetCrossOrigin }\n\nexport type TransformAssetKind = 'modulepreload' | 'stylesheet' | 'clientEntry'\n\ntype TransformAssetsShorthandCrossOriginKind = Exclude<\n TransformAssetKind,\n 'clientEntry'\n>\n\nexport type AssetUrlType = TransformAssetKind\n\nexport interface TransformAssetsContext {\n url: string\n kind: TransformAssetKind\n}\n\nexport type TransformAssetResult =\n | string\n | {\n href: string\n crossOrigin?: AssetCrossOrigin\n }\n\nexport type TransformAssetsFn = (\n context: TransformAssetsContext,\n) => Awaitable<TransformAssetResult>\n\nexport interface TransformAssetUrlsContext {\n url: string\n type: AssetUrlType\n}\n\nexport type TransformAssetUrlsFn = (\n context: TransformAssetUrlsContext,\n) => Awaitable<string>\n\nexport type CreateTransformAssetUrlsContext =\n | {\n /** True when the server is computing the cached manifest during startup warmup. */\n warmup: true\n }\n | {\n /**\n * The current Request.\n *\n * Only available during request handling (i.e. when `warmup: false`).\n */\n request: Request\n /** False when transforming URLs as part of request handling. */\n warmup: false\n }\n\n/**\n * Async factory that runs once per manifest computation and returns the\n * per-asset transform.\n */\nexport type CreateTransformAssetUrlsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetUrlsFn>\n\nexport type CreateTransformAssetsFn = (\n ctx: CreateTransformAssetUrlsContext,\n) => Awaitable<TransformAssetsFn>\n\ntype TransformAssetUrlsOptionsBase = {\n /**\n * Whether to cache the transformed manifest after the first request.\n *\n * When `true` (default), the transform runs once on the first request and\n * the resulting manifest is reused for all subsequent requests in production.\n *\n * Set to `false` for per-request transforms (e.g. geo-routing to different\n * CDNs based on request headers).\n *\n * @default true\n */\n cache?: boolean\n\n /**\n * When `true`, warms up the cached transformed manifest in the background when\n * the server starts (production only).\n *\n * This can reduce latency for the first request when `cache` is `true`.\n * Has no effect when `cache: false` (per-request transforms) or in dev mode.\n *\n * @default false\n */\n warmup?: boolean\n}\n\nexport type TransformAssetUrlsOptions =\n | (TransformAssetUrlsOptionsBase & {\n /**\n * The transform to apply to asset URLs. Can be a string prefix or a callback.\n *\n * **String** — prepended to every asset URL.\n * **Callback** — receives `{ url, type }` and returns a new URL.\n */\n transform: string | TransformAssetUrlsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n /**\n * Create a per-asset transform function.\n *\n * This factory runs once per manifest computation (per request when\n * `cache: false`, or once per server when `cache: true`). It can do async\n * setup work (fetch config, read from a KV, etc.) and return a fast\n * per-asset transformer.\n */\n createTransform: CreateTransformAssetUrlsFn\n transform?: never\n })\n\nexport type TransformAssetsOptions =\n | (TransformAssetUrlsOptionsBase & {\n transform: string | TransformAssetsFn\n createTransform?: never\n })\n | (TransformAssetUrlsOptionsBase & {\n createTransform: CreateTransformAssetsFn\n transform?: never\n })\n\nexport type TransformAssetUrls =\n | string\n | TransformAssetUrlsFn\n | TransformAssetUrlsOptions\n\n/**\n * Per-kind crossOrigin configuration for the object shorthand.\n *\n * Accepts either a single value applied to all asset kinds, or a per-kind\n * record (matching `HeadContent`'s `assetCrossOrigin` shape):\n *\n * ```ts\n * // All assets get the same value\n * crossOrigin: 'anonymous'\n *\n * // Different values per kind\n * crossOrigin: { modulepreload: 'anonymous', stylesheet: 'use-credentials' }\n * ```\n */\nexport type TransformAssetsCrossOriginConfig =\n | AssetCrossOrigin\n | Partial<Record<TransformAssetsShorthandCrossOriginKind, AssetCrossOrigin>>\n\n/**\n * Object shorthand for `transformAssets`. Combines a URL prefix with optional\n * per-asset `crossOrigin` without needing a callback:\n *\n * ```ts\n * transformAssets: {\n * prefix: 'https://cdn.example.com',\n * crossOrigin: 'anonymous',\n * }\n * ```\n */\nexport interface TransformAssetsObjectShorthand {\n /** URL prefix prepended to every asset URL. */\n prefix: string\n /**\n * Optional crossOrigin attribute applied to manifest-managed `<link>` assets.\n *\n * Accepts a single value or a per-kind record.\n */\n crossOrigin?: TransformAssetsCrossOriginConfig\n}\n\nexport type TransformAssets =\n | string\n | TransformAssetsFn\n | TransformAssetsObjectShorthand\n | TransformAssetsOptions\n\nexport type ResolvedTransformAssetsConfig =\n | {\n type: 'transform'\n transformFn: TransformAssetsFn\n cache: boolean\n }\n | {\n type: 'createTransform'\n createTransform: CreateTransformAssetsFn\n cache: boolean\n }\n\nlet hasWarnedAboutDeprecatedTransformAssetUrls = false\n\nexport function warnDeprecatedTransformAssetUrls() {\n if (\n (process.env.NODE_ENV === 'development' ||\n process.env.TSS_DEV_SERVER === 'true') &&\n !hasWarnedAboutDeprecatedTransformAssetUrls\n ) {\n hasWarnedAboutDeprecatedTransformAssetUrls = true\n console.warn(\n '[TanStack Start] `transformAssetUrls` is deprecated. Use `transformAssets` instead.',\n )\n }\n}\n\nfunction normalizeTransformAssetResult(\n result: TransformAssetResult,\n): Exclude<TransformAssetResult, string> {\n if (typeof result === 'string') {\n return { href: result }\n }\n\n return result\n}\n\nfunction resolveTransformAssetsCrossOrigin(\n config: TransformAssetsCrossOriginConfig | undefined,\n kind: TransformAssetsShorthandCrossOriginKind,\n): AssetCrossOrigin | undefined {\n if (!config) return undefined\n if (typeof config === 'string') return config\n\n return config[kind]\n}\n\nfunction isObjectShorthand(\n transform: TransformAssetsObjectShorthand | TransformAssetsOptions,\n): transform is TransformAssetsObjectShorthand {\n return 'prefix' in transform\n}\n\nexport function resolveTransformAssetsConfig(\n transform: TransformAssets,\n): ResolvedTransformAssetsConfig {\n if (typeof transform === 'string') {\n const prefix = transform\n return {\n type: 'transform',\n transformFn: ({ url }) => ({ href: `${prefix}${url}` }),\n cache: true,\n }\n }\n\n if (typeof transform === 'function') {\n return {\n type: 'transform',\n transformFn: transform,\n cache: true,\n }\n }\n\n // Object shorthand: { prefix, crossOrigin? }\n if (isObjectShorthand(transform)) {\n const { prefix, crossOrigin } = transform\n\n return {\n type: 'transform',\n transformFn: ({ url, kind }) => {\n const href = `${prefix}${url}`\n\n if (kind === 'clientEntry') {\n return { href }\n }\n\n const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind)\n return co ? { href, crossOrigin: co } : { href }\n },\n cache: true,\n }\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n type: 'createTransform',\n createTransform: transform.createTransform,\n cache: transform.cache !== false,\n }\n }\n\n const transformFn =\n typeof transform.transform === 'string'\n ? ((({ url }: TransformAssetsContext) => ({\n href: `${transform.transform}${url}`,\n })) as TransformAssetsFn)\n : transform.transform\n\n return {\n type: 'transform',\n transformFn,\n cache: transform.cache !== false,\n }\n}\n\nexport function adaptTransformAssetUrlsToTransformAssets(\n transformFn: TransformAssetUrlsFn,\n): TransformAssetsFn {\n return async ({ url, kind }) => ({\n href: await transformFn({ url, type: kind }),\n })\n}\n\nexport function adaptTransformAssetUrlsConfigToTransformAssets(\n transform: TransformAssetUrls,\n): TransformAssets {\n warnDeprecatedTransformAssetUrls()\n\n if (typeof transform === 'string') {\n return transform\n }\n\n if (typeof transform === 'function') {\n return adaptTransformAssetUrlsToTransformAssets(transform)\n }\n\n if ('createTransform' in transform && transform.createTransform) {\n return {\n createTransform: async (ctx: CreateTransformAssetUrlsContext) =>\n adaptTransformAssetUrlsToTransformAssets(\n await transform.createTransform(ctx),\n ),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n }\n\n return {\n transform:\n typeof transform.transform === 'string'\n ? transform.transform\n : adaptTransformAssetUrlsToTransformAssets(transform.transform),\n cache: transform.cache,\n warmup: transform.warmup,\n }\n}\n\nexport interface StartManifestWithClientEntry {\n manifest: Manifest\n clientEntry: string\n /** Script content prepended before the client entry import (dev only) */\n injectedHeadScripts?: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL and optional injected head scripts.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n injectedHeadScripts?: string,\n): RouterManagedTag {\n const clientEntryLiteral = JSON.stringify(clientEntry)\n let script = `import(${clientEntryLiteral})`\n if (injectedHeadScripts) {\n script = `${injectedHeadScripts};${script}`\n }\n return {\n tag: 'script',\n attrs: {\n type: 'module',\n async: true,\n },\n children: script,\n }\n}\n\nfunction assignManifestAssetLink(\n link: ManifestAssetLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): ManifestAssetLink {\n if (typeof link === 'string') {\n return next.crossOrigin ? next : next.href\n }\n\n return next.crossOrigin ? next : { href: next.href }\n}\n\nexport async function transformManifestAssets(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetsFn,\n _opts?: {\n clone?: boolean\n },\n): Promise<Manifest> {\n const manifest = structuredClone(source.manifest)\n\n for (const route of Object.values(manifest.routes)) {\n if (route.preloads) {\n route.preloads = await Promise.all(\n route.preloads.map(async (link) => {\n const resolved = resolveManifestAssetLink(link)\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: resolved.href,\n kind: 'modulepreload',\n }),\n )\n\n return assignManifestAssetLink(link, {\n href: result.href,\n crossOrigin: result.crossOrigin,\n })\n }),\n )\n }\n\n if (route.assets) {\n for (const asset of route.assets) {\n if (asset.tag === 'link' && asset.attrs?.href) {\n const rel = asset.attrs.rel\n const relTokens = typeof rel === 'string' ? rel.split(/\\s+/) : []\n\n if (!relTokens.includes('stylesheet')) {\n continue\n }\n\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: asset.attrs.href,\n kind: 'stylesheet',\n }),\n )\n\n asset.attrs.href = result.href\n if (result.crossOrigin) {\n asset.attrs.crossOrigin = result.crossOrigin\n } else {\n delete asset.attrs.crossOrigin\n }\n }\n }\n }\n }\n\n const transformedClientEntry = normalizeTransformAssetResult(\n await transformFn({\n url: source.clientEntry,\n kind: 'clientEntry',\n }),\n )\n\n const rootRoute = manifest.routes[rootRouteId]\n if (rootRoute) {\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry.href,\n source.injectedHeadScripts,\n ),\n )\n }\n\n return manifest\n}\n\n/**\n * Builds a final Manifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssetUrls option is provided.\n *\n * Returns a new manifest object so the cached base manifest is never mutated.\n */\nexport function buildManifestWithClientEntry(\n source: StartManifestWithClientEntry,\n): Manifest {\n const scriptTag = buildClientEntryScriptTag(\n source.clientEntry,\n source.injectedHeadScripts,\n )\n\n const baseRootRoute = source.manifest.routes[rootRouteId]\n const routes = {\n ...source.manifest.routes,\n ...(baseRootRoute\n ? {\n [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute.assets || []), scriptTag],\n },\n }\n : {}),\n }\n\n return { routes }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-server-core",
3
- "version": "1.167.2",
3
+ "version": "1.167.3",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -66,9 +66,9 @@
66
66
  "h3-v2": "npm:h3@2.0.1-rc.16",
67
67
  "seroval": "^1.4.2",
68
68
  "@tanstack/history": "1.161.6",
69
- "@tanstack/router-core": "1.168.2",
70
- "@tanstack/start-client-core": "1.167.2",
71
- "@tanstack/start-storage-context": "1.166.16"
69
+ "@tanstack/router-core": "1.168.3",
70
+ "@tanstack/start-client-core": "1.167.3",
71
+ "@tanstack/start-storage-context": "1.166.17"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@standard-schema/spec": "^1.0.0",
@@ -20,9 +20,10 @@ import { requestHandler } from './request-response'
20
20
  import { getStartManifest } from './router-manifest'
21
21
  import { handleServerAction } from './server-functions-handler'
22
22
  import {
23
+ adaptTransformAssetUrlsConfigToTransformAssets,
23
24
  buildManifestWithClientEntry,
24
- resolveTransformConfig,
25
- transformManifestUrls,
25
+ resolveTransformAssetsConfig,
26
+ transformManifestAssets,
26
27
  } from './transformAssetUrls'
27
28
 
28
29
  import { HEADERS } from './constants'
@@ -47,7 +48,8 @@ import type { HandlerCallback } from '@tanstack/router-core/ssr/server'
47
48
  import type {
48
49
  StartManifestWithClientEntry,
49
50
  TransformAssetUrls,
50
- TransformAssetUrlsFn,
51
+ TransformAssets,
52
+ TransformAssetsFn,
51
53
  } from './transformAssetUrls'
52
54
 
53
55
  type TODO = any
@@ -59,7 +61,87 @@ type AnyMiddlewareServerFn =
59
61
  export interface CreateStartHandlerOptions {
60
62
  handler: HandlerCallback<AnyRouter>
61
63
  /**
62
- * Transform asset URLs at runtime, e.g. to prepend a CDN prefix.
64
+ * Transform asset URLs and attributes at runtime, e.g. to prepend a CDN prefix.
65
+ *
66
+ * **String** — a URL prefix prepended to every asset URL (cached by default):
67
+ * ```ts
68
+ * createStartHandler({
69
+ * handler: defaultStreamHandler,
70
+ * transformAssets: 'https://cdn.example.com',
71
+ * })
72
+ * ```
73
+ *
74
+ * **Object shorthand** — a URL prefix with optional `crossOrigin`:
75
+ * ```ts
76
+ * createStartHandler({
77
+ * handler: defaultStreamHandler,
78
+ * transformAssets: {
79
+ * prefix: 'https://cdn.example.com',
80
+ * crossOrigin: 'anonymous',
81
+ * },
82
+ * })
83
+ * ```
84
+ *
85
+ * `crossOrigin` accepts a single value or a per-kind record:
86
+ * ```ts
87
+ * transformAssets: {
88
+ * prefix: 'https://cdn.example.com',
89
+ * crossOrigin: {
90
+ * modulepreload: 'anonymous',
91
+ * stylesheet: 'use-credentials',
92
+ * },
93
+ * }
94
+ * ```
95
+ *
96
+ * **Callback** — receives `{ kind, url }` and returns either a string URL or
97
+ * `{ href, crossOrigin? }` (cached by default — runs once on first request):
98
+ * ```ts
99
+ * createStartHandler({
100
+ * handler: defaultStreamHandler,
101
+ * transformAssets: ({ kind, url }) => {
102
+ * const href = `https://cdn.example.com${url}`
103
+ *
104
+ * if (kind === 'modulepreload') {
105
+ * return { href, crossOrigin: 'anonymous' }
106
+ * }
107
+ *
108
+ * return { href }
109
+ * },
110
+ * })
111
+ * ```
112
+ *
113
+ * **Object** — for explicit cache control:
114
+ * ```ts
115
+ * createStartHandler({
116
+ * handler: defaultStreamHandler,
117
+ * transformAssets: {
118
+ * transform: ({ url }) => {
119
+ * const region = getRequest().headers.get('x-region') || 'us'
120
+ * return { href: `https://cdn-${region}.example.com${url}` }
121
+ * },
122
+ * cache: false,
123
+ * },
124
+ * })
125
+ * ```
126
+ *
127
+ * `kind` is one of `'modulepreload' | 'stylesheet' | 'clientEntry'`.
128
+ * `crossOrigin` applies to manifest-managed `<link>` assets.
129
+ *
130
+ * By default, the transformed manifest is cached after the first request
131
+ * (`cache: true`). Set `cache: false` for per-request transforms.
132
+ *
133
+ * If you're using a cached transform, you can optionally set `warmup: true`
134
+ * (object form only) to compute the transformed manifest in the background at
135
+ * server startup.
136
+ *
137
+ * Note: This only transforms URLs managed by TanStack Start's manifest
138
+ * (JS preloads, CSS links, and the client entry script). For asset imports
139
+ * used directly in components (e.g. `import logo from './logo.svg'`),
140
+ * configure Vite's `experimental.renderBuiltUrl` in your vite.config.ts.
141
+ */
142
+ transformAssets?: TransformAssets
143
+ /**
144
+ * @deprecated Use `transformAssets` instead.
63
145
  *
64
146
  * **String** — a URL prefix prepended to every asset URL (cached by default):
65
147
  * ```ts
@@ -181,14 +263,14 @@ function getBaseManifest(
181
263
  */
182
264
  async function resolveManifest(
183
265
  matchedRoutes: ReadonlyArray<AnyRoute> | undefined,
184
- transformFn: TransformAssetUrlsFn | undefined,
266
+ transformFn: TransformAssetsFn | undefined,
185
267
  cache: boolean,
186
268
  ): Promise<Manifest> {
187
269
  const base = await getBaseManifest(matchedRoutes)
188
270
 
189
271
  const computeFinalManifest = async () => {
190
272
  return transformFn
191
- ? await transformManifestUrls(base, transformFn, { clone: !cache })
273
+ ? await transformManifestAssets(base, transformFn, { clone: !cache })
192
274
  : buildManifestWithClientEntry(base)
193
275
  }
194
276
 
@@ -332,7 +414,7 @@ function handlerToMiddleware(
332
414
  * ```ts
333
415
  * export default createStartHandler({
334
416
  * handler: defaultStreamHandler,
335
- * transformAssetUrls: 'https://cdn.example.com',
417
+ * transformAssets: 'https://cdn.example.com',
336
418
  * })
337
419
  * ```
338
420
  *
@@ -340,10 +422,10 @@ function handlerToMiddleware(
340
422
  * ```ts
341
423
  * export default createStartHandler({
342
424
  * handler: defaultStreamHandler,
343
- * transformAssetUrls: {
425
+ * transformAssets: {
344
426
  * transform: ({ url }) => {
345
427
  * const cdnBase = getRequest().headers.get('x-cdn-base') || ''
346
- * return `${cdnBase}${url}`
428
+ * return { href: `${cdnBase}${url}` }
347
429
  * },
348
430
  * cache: false,
349
431
  * },
@@ -356,40 +438,65 @@ export function createStartHandler<TRegister = Register>(
356
438
  // Normalize the overloaded argument
357
439
  const cb: HandlerCallback<AnyRouter> =
358
440
  typeof cbOrOptions === 'function' ? cbOrOptions : cbOrOptions.handler
441
+ const transformAssetsOption: TransformAssets | undefined =
442
+ typeof cbOrOptions === 'function' ? undefined : cbOrOptions.transformAssets
359
443
  const transformAssetUrlsOption: TransformAssetUrls | undefined =
360
444
  typeof cbOrOptions === 'function'
361
445
  ? undefined
362
446
  : cbOrOptions.transformAssetUrls
363
447
 
448
+ const transformOption =
449
+ transformAssetsOption !== undefined
450
+ ? resolveTransformAssetsConfig(transformAssetsOption)
451
+ : transformAssetUrlsOption !== undefined
452
+ ? resolveTransformAssetsConfig(
453
+ adaptTransformAssetUrlsConfigToTransformAssets(
454
+ transformAssetUrlsOption,
455
+ ),
456
+ )
457
+ : undefined
458
+
364
459
  const warmupTransformManifest =
365
- !!transformAssetUrlsOption &&
366
- typeof transformAssetUrlsOption === 'object' &&
367
- transformAssetUrlsOption.warmup === true
460
+ (!!transformAssetsOption &&
461
+ typeof transformAssetsOption === 'object' &&
462
+ 'warmup' in transformAssetsOption &&
463
+ transformAssetsOption.warmup === true) ||
464
+ (!!transformAssetUrlsOption &&
465
+ typeof transformAssetUrlsOption === 'object' &&
466
+ transformAssetUrlsOption.warmup === true)
368
467
 
369
468
  // Pre-resolve the transform function and cache flag
370
- const resolvedTransformConfig = transformAssetUrlsOption
371
- ? resolveTransformConfig(transformAssetUrlsOption)
372
- : undefined
469
+ const resolvedTransformConfig = transformOption
373
470
  const cache = resolvedTransformConfig ? resolvedTransformConfig.cache : true
471
+ const shouldCacheCreateTransform =
472
+ cache && process.env.TSS_DEV_SERVER !== 'true'
374
473
 
375
- // Memoize a single createTransform() result when caching is enabled.
376
- let cachedCreateTransformPromise: Promise<TransformAssetUrlsFn> | undefined
474
+ // Memoize a single createTransform() result when caching is enabled outside
475
+ // of the dev server.
476
+ let cachedCreateTransformPromise: Promise<TransformAssetsFn> | undefined
377
477
 
378
478
  const getTransformFn = async (
379
479
  opts: { warmup: true } | { warmup: false; request: Request },
380
- ): Promise<TransformAssetUrlsFn | undefined> => {
480
+ ): Promise<TransformAssetsFn | undefined> => {
381
481
  if (!resolvedTransformConfig) return undefined
482
+
382
483
  if (resolvedTransformConfig.type === 'createTransform') {
383
- if (cache) {
484
+ if (shouldCacheCreateTransform) {
384
485
  if (!cachedCreateTransformPromise) {
385
486
  cachedCreateTransformPromise = Promise.resolve(
386
487
  resolvedTransformConfig.createTransform(opts),
387
- )
488
+ ).catch((error) => {
489
+ cachedCreateTransformPromise = undefined
490
+ throw error
491
+ })
388
492
  }
493
+
389
494
  return cachedCreateTransformPromise
390
495
  }
496
+
391
497
  return resolvedTransformConfig.createTransform(opts)
392
498
  }
499
+
393
500
  return resolvedTransformConfig.transformFn
394
501
  }
395
502
 
@@ -408,7 +515,7 @@ export function createStartHandler<TRegister = Register>(
408
515
  const base = await getBaseManifest(undefined)
409
516
  const transformFn = await getTransformFn({ warmup: true })
410
517
  return transformFn
411
- ? await transformManifestUrls(base, transformFn, { clone: false })
518
+ ? await transformManifestAssets(base, transformFn, { clone: false })
412
519
  : buildManifestWithClientEntry(base)
413
520
  })()
414
521
  cachedFinalManifestPromise = warmupPromise
package/src/index.tsx CHANGED
@@ -2,11 +2,19 @@ export { createStartHandler } from './createStartHandler'
2
2
  export type { CreateStartHandlerOptions } from './createStartHandler'
3
3
 
4
4
  export type {
5
+ TransformAssets,
6
+ TransformAssetsFn,
7
+ TransformAssetsContext,
8
+ TransformAssetsOptions,
9
+ TransformAssetsObjectShorthand,
10
+ TransformAssetsCrossOriginConfig,
11
+ TransformAssetResult,
5
12
  TransformAssetUrls,
6
13
  TransformAssetUrlsFn,
7
14
  TransformAssetUrlsContext,
8
15
  TransformAssetUrlsOptions,
9
16
  AssetUrlType,
17
+ TransformAssetKind,
10
18
  } from './transformAssetUrls'
11
19
 
12
20
  export {
@@ -1,5 +1,9 @@
1
1
  import { buildDevStylesUrl, rootRouteId } from '@tanstack/router-core'
2
- import type { AnyRoute, RouterManagedTag } from '@tanstack/router-core'
2
+ import type {
3
+ AnyRoute,
4
+ ManifestAssetLink,
5
+ RouterManagedTag,
6
+ } from '@tanstack/router-core'
3
7
  import type { StartManifestWithClientEntry } from './transformAssetUrls'
4
8
 
5
9
  // Pre-computed constant for dev styles URL basepath.
@@ -61,7 +65,7 @@ export async function getStartManifest(
61
65
  routes: Object.fromEntries(
62
66
  Object.entries(startManifest.routes).flatMap(([k, v]) => {
63
67
  const result = {} as {
64
- preloads?: Array<string>
68
+ preloads?: Array<ManifestAssetLink>
65
69
  assets?: Array<RouterManagedTag>
66
70
  }
67
71
  let hasData = false
@@ -82,7 +86,7 @@ export async function getStartManifest(
82
86
  }
83
87
 
84
88
  return {
85
- manifest,
89
+ manifest: manifest as StartManifestWithClientEntry['manifest'],
86
90
  clientEntry: startManifest.clientEntry,
87
91
  injectedHeadScripts,
88
92
  }