@tanstack/start-server-core 1.169.2 → 1.169.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.
- package/dist/esm/createStartHandler.js +1 -2
- package/dist/esm/createStartHandler.js.map +1 -1
- package/dist/esm/early-hints.d.ts +3 -3
- package/dist/esm/early-hints.js +16 -21
- package/dist/esm/early-hints.js.map +1 -1
- package/dist/esm/finalManifest.d.ts +4 -4
- package/dist/esm/finalManifest.js.map +1 -1
- package/dist/esm/request-handler.d.ts +2 -1
- package/dist/esm/router-manifest.js +35 -28
- package/dist/esm/router-manifest.js.map +1 -1
- package/dist/esm/transformAssetUrls.d.ts +11 -16
- package/dist/esm/transformAssetUrls.js +80 -53
- package/dist/esm/transformAssetUrls.js.map +1 -1
- package/package.json +4 -4
- package/src/createStartHandler.ts +0 -1
- package/src/early-hints.ts +24 -24
- package/src/finalManifest.ts +14 -12
- package/src/request-handler.ts +2 -1
- package/src/router-manifest.ts +60 -44
- package/src/tanstack-start.d.ts +2 -2
- package/src/transformAssetUrls.ts +160 -91
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import { AssetCrossOrigin, Awaitable,
|
|
1
|
+
import { AssetCrossOrigin, Awaitable, ManifestScript, ScriptFormat, ServerManifest } from '@tanstack/router-core';
|
|
2
2
|
export type { AssetCrossOrigin };
|
|
3
3
|
export type TransformAssetsContext = {
|
|
4
4
|
url: string;
|
|
5
|
-
kind: '
|
|
5
|
+
kind: 'script';
|
|
6
6
|
} | {
|
|
7
7
|
url: string;
|
|
8
8
|
kind: 'stylesheet';
|
|
9
|
-
} | {
|
|
10
|
-
url: string;
|
|
11
|
-
kind: 'clientEntry';
|
|
12
9
|
} | {
|
|
13
10
|
url: string;
|
|
14
11
|
kind: 'css-url';
|
|
15
12
|
stylesheetHref: string;
|
|
16
13
|
};
|
|
17
14
|
export type TransformAssetKind = TransformAssetsContext['kind'];
|
|
18
|
-
type TransformAssetsShorthandCrossOriginKind = Exclude<TransformAssetKind, '
|
|
15
|
+
type TransformAssetsShorthandCrossOriginKind = Exclude<TransformAssetKind, 'css-url'>;
|
|
19
16
|
export type TransformAssetResult = string | {
|
|
20
17
|
href: string;
|
|
21
18
|
crossOrigin?: AssetCrossOrigin;
|
|
@@ -77,7 +74,7 @@ export type TransformAssetsOptions = (TransformAssetsOptionsBase & {
|
|
|
77
74
|
* crossOrigin: 'anonymous'
|
|
78
75
|
*
|
|
79
76
|
* // Different values per kind
|
|
80
|
-
* crossOrigin: {
|
|
77
|
+
* crossOrigin: { script: 'anonymous', stylesheet: 'use-credentials' }
|
|
81
78
|
* ```
|
|
82
79
|
*/
|
|
83
80
|
export type TransformAssetsCrossOriginConfig = AssetCrossOrigin | Partial<Record<TransformAssetsShorthandCrossOriginKind, AssetCrossOrigin>>;
|
|
@@ -96,7 +93,7 @@ export interface TransformAssetsObjectShorthand {
|
|
|
96
93
|
/** URL prefix prepended to every asset URL. */
|
|
97
94
|
prefix: string;
|
|
98
95
|
/**
|
|
99
|
-
* Optional crossOrigin attribute applied to
|
|
96
|
+
* Optional crossOrigin attribute applied to transformed script and stylesheet assets.
|
|
100
97
|
*
|
|
101
98
|
* Accepts a single value or a per-kind record.
|
|
102
99
|
*/
|
|
@@ -114,26 +111,24 @@ export type ResolvedTransformAssetsConfig = {
|
|
|
114
111
|
};
|
|
115
112
|
export declare function resolveTransformAssetsConfig(transform: TransformAssets): ResolvedTransformAssetsConfig;
|
|
116
113
|
export interface StartManifestWithClientEntry {
|
|
117
|
-
manifest:
|
|
114
|
+
manifest: ServerManifest;
|
|
118
115
|
clientEntry: string;
|
|
119
|
-
/** Script content prepended before the client entry import (dev only) */
|
|
120
|
-
injectedHeadScripts?: string;
|
|
121
116
|
}
|
|
122
117
|
/**
|
|
123
118
|
* Builds the client entry `<script>` tag from a (possibly transformed) client
|
|
124
|
-
* entry URL
|
|
119
|
+
* entry URL.
|
|
125
120
|
*/
|
|
126
|
-
export declare function buildClientEntryScriptTag(clientEntry: string,
|
|
121
|
+
export declare function buildClientEntryScriptTag(clientEntry: string, scriptFormat?: ScriptFormat, crossOrigin?: AssetCrossOrigin): ManifestScript;
|
|
127
122
|
export declare function transformManifestAssets(source: StartManifestWithClientEntry, transformFn: TransformAssetsFn, _opts?: {
|
|
128
123
|
clone?: boolean;
|
|
129
124
|
inlineCss?: boolean;
|
|
130
|
-
}): Promise<
|
|
125
|
+
}): Promise<ServerManifest>;
|
|
131
126
|
/**
|
|
132
|
-
* Builds a final
|
|
127
|
+
* Builds a final ServerManifest from a StartManifestWithClientEntry without any
|
|
133
128
|
* URL transforms. Used when no transformAssets option is provided.
|
|
134
129
|
*
|
|
135
130
|
* Returns a new manifest object so the cached base manifest is never mutated.
|
|
136
131
|
*/
|
|
137
132
|
export declare function buildManifestWithClientEntry(source: StartManifestWithClientEntry, opts?: {
|
|
138
133
|
inlineCss?: boolean;
|
|
139
|
-
}):
|
|
134
|
+
}): ServerManifest;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveManifestAssetLink,
|
|
1
|
+
import { getManifestScriptFormat, resolveManifestAssetLink, resolveManifestCssLink } from "@tanstack/router-core";
|
|
2
2
|
//#region src/transformAssetUrls.ts
|
|
3
3
|
function normalizeTransformAssetResult(result) {
|
|
4
4
|
if (typeof result === "string") return { href: result };
|
|
@@ -65,7 +65,7 @@ function resolveTransformAssetsConfig(transform) {
|
|
|
65
65
|
type: "transform",
|
|
66
66
|
transformFn: ({ url, kind }) => {
|
|
67
67
|
const href = `${prefix}${url}`;
|
|
68
|
-
if (kind === "
|
|
68
|
+
if (kind === "css-url") return { href };
|
|
69
69
|
const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind);
|
|
70
70
|
return co ? {
|
|
71
71
|
href,
|
|
@@ -88,82 +88,109 @@ function resolveTransformAssetsConfig(transform) {
|
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
90
|
* Builds the client entry `<script>` tag from a (possibly transformed) client
|
|
91
|
-
* entry URL
|
|
91
|
+
* entry URL.
|
|
92
92
|
*/
|
|
93
|
-
function buildClientEntryScriptTag(clientEntry,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
async: true
|
|
101
|
-
},
|
|
102
|
-
children: script
|
|
103
|
-
};
|
|
93
|
+
function buildClientEntryScriptTag(clientEntry, scriptFormat = "module", crossOrigin) {
|
|
94
|
+
return { attrs: {
|
|
95
|
+
...scriptFormat === "module" ? { type: "module" } : {},
|
|
96
|
+
async: true,
|
|
97
|
+
src: clientEntry,
|
|
98
|
+
...crossOrigin ? { crossOrigin } : {}
|
|
99
|
+
} };
|
|
104
100
|
}
|
|
105
|
-
function
|
|
101
|
+
function assignManifestLink(link, next) {
|
|
106
102
|
if (typeof link === "string") return next.crossOrigin ? next : next.href;
|
|
107
|
-
|
|
103
|
+
const nextLink = {
|
|
104
|
+
...link,
|
|
105
|
+
href: next.href
|
|
106
|
+
};
|
|
107
|
+
if (next.crossOrigin) nextLink.crossOrigin = next.crossOrigin;
|
|
108
|
+
else delete nextLink.crossOrigin;
|
|
109
|
+
return nextLink;
|
|
110
|
+
}
|
|
111
|
+
function appendUniqueManifestAssetLink(target, link) {
|
|
112
|
+
const href = typeof link === "string" ? link : link.href;
|
|
113
|
+
if (target) {
|
|
114
|
+
for (const item of target) if ((typeof item === "string" ? item : item.href) === href) return target;
|
|
115
|
+
}
|
|
116
|
+
return [...target ?? [], link];
|
|
117
|
+
}
|
|
118
|
+
function addClientEntryToManifest(manifest, clientEntry) {
|
|
119
|
+
const rootRoute = manifest.routes.__root__ ?? {};
|
|
120
|
+
const rootScripts = rootRoute.scripts ?? [];
|
|
121
|
+
const scripts = rootScripts.some((script) => script.attrs?.src === clientEntry) ? rootScripts : [...rootScripts, buildClientEntryScriptTag(clientEntry, getManifestScriptFormat(manifest))];
|
|
122
|
+
manifest.routes = {
|
|
123
|
+
...manifest.routes,
|
|
124
|
+
__root__: {
|
|
125
|
+
...rootRoute,
|
|
126
|
+
preloads: appendUniqueManifestAssetLink(rootRoute.preloads, clientEntry),
|
|
127
|
+
scripts
|
|
128
|
+
}
|
|
129
|
+
};
|
|
108
130
|
}
|
|
109
131
|
async function transformManifestAssets(source, transformFn, _opts) {
|
|
110
132
|
const manifest = structuredClone(source.manifest);
|
|
111
|
-
|
|
133
|
+
const inlineCssEnabled = _opts?.inlineCss !== false;
|
|
134
|
+
const scriptTransforms = /* @__PURE__ */ new Map();
|
|
135
|
+
const transformScript = (url) => {
|
|
136
|
+
const cached = scriptTransforms.get(url);
|
|
137
|
+
if (cached) return cached;
|
|
138
|
+
const transformed = Promise.resolve(transformFn({
|
|
139
|
+
url,
|
|
140
|
+
kind: "script"
|
|
141
|
+
})).then(normalizeTransformAssetResult);
|
|
142
|
+
scriptTransforms.set(url, transformed);
|
|
143
|
+
return transformed;
|
|
144
|
+
};
|
|
145
|
+
if (!inlineCssEnabled) delete manifest.inlineCss;
|
|
112
146
|
else if (manifest.inlineCss) manifest.inlineCss = await transformInlineCssStyles(manifest.inlineCss, transformFn);
|
|
147
|
+
addClientEntryToManifest(manifest, source.clientEntry);
|
|
113
148
|
for (const route of Object.values(manifest.routes)) {
|
|
114
|
-
if (route.preloads) route.preloads = await Promise.all(route.preloads.map(async (link) => {
|
|
149
|
+
if (route.preloads?.length) route.preloads = await Promise.all(route.preloads.map(async (link) => {
|
|
150
|
+
const result = await transformScript(resolveManifestAssetLink(link).href);
|
|
151
|
+
return assignManifestLink(link, {
|
|
152
|
+
href: result.href,
|
|
153
|
+
crossOrigin: result.crossOrigin
|
|
154
|
+
});
|
|
155
|
+
}));
|
|
156
|
+
if (route.css?.length && !manifest.inlineCss) route.css = await Promise.all(route.css.map(async (link) => {
|
|
115
157
|
const result = normalizeTransformAssetResult(await transformFn({
|
|
116
|
-
url:
|
|
117
|
-
kind: "
|
|
158
|
+
url: resolveManifestCssLink(link).href,
|
|
159
|
+
kind: "stylesheet"
|
|
118
160
|
}));
|
|
119
|
-
return
|
|
161
|
+
return assignManifestLink(link, {
|
|
120
162
|
href: result.href,
|
|
121
163
|
crossOrigin: result.crossOrigin
|
|
122
164
|
});
|
|
123
165
|
}));
|
|
124
|
-
if (route.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
else delete asset.attrs.crossOrigin;
|
|
135
|
-
}
|
|
166
|
+
if (route.scripts?.length) for (const script of route.scripts) {
|
|
167
|
+
const src = script.attrs?.src;
|
|
168
|
+
if (typeof src !== "string") continue;
|
|
169
|
+
const result = await transformScript(src);
|
|
170
|
+
script.attrs = {
|
|
171
|
+
...script.attrs,
|
|
172
|
+
src: result.href
|
|
173
|
+
};
|
|
174
|
+
if (result.crossOrigin) script.attrs.crossOrigin = result.crossOrigin;
|
|
175
|
+
else delete script.attrs.crossOrigin;
|
|
136
176
|
}
|
|
137
177
|
}
|
|
138
|
-
const transformedClientEntry = normalizeTransformAssetResult(await transformFn({
|
|
139
|
-
url: source.clientEntry,
|
|
140
|
-
kind: "clientEntry"
|
|
141
|
-
}));
|
|
142
|
-
const rootRoute = manifest.routes[rootRouteId] = manifest.routes[rootRouteId] || {};
|
|
143
|
-
rootRoute.assets = rootRoute.assets || [];
|
|
144
|
-
rootRoute.assets.push(buildClientEntryScriptTag(transformedClientEntry.href, source.injectedHeadScripts));
|
|
145
178
|
return manifest;
|
|
146
179
|
}
|
|
147
180
|
/**
|
|
148
|
-
* Builds a final
|
|
181
|
+
* Builds a final ServerManifest from a StartManifestWithClientEntry without any
|
|
149
182
|
* URL transforms. Used when no transformAssets option is provided.
|
|
150
183
|
*
|
|
151
184
|
* Returns a new manifest object so the cached base manifest is never mutated.
|
|
152
185
|
*/
|
|
153
186
|
function buildManifestWithClientEntry(source, opts) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
...source.manifest.routes
|
|
158
|
-
[rootRouteId]: {
|
|
159
|
-
...baseRootRoute,
|
|
160
|
-
assets: [...baseRootRoute?.assets || [], scriptTag]
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
return {
|
|
164
|
-
...opts?.inlineCss === false ? {} : { inlineCss: structuredClone(source.manifest.inlineCss) },
|
|
165
|
-
routes
|
|
187
|
+
const manifest = {
|
|
188
|
+
...source.manifest.scriptFormat ? { scriptFormat: source.manifest.scriptFormat } : {},
|
|
189
|
+
...opts?.inlineCss !== false && source.manifest.inlineCss ? { inlineCss: structuredClone(source.manifest.inlineCss) } : {},
|
|
190
|
+
routes: { ...source.manifest.routes }
|
|
166
191
|
};
|
|
192
|
+
addClientEntryToManifest(manifest, source.clientEntry);
|
|
193
|
+
return manifest;
|
|
167
194
|
}
|
|
168
195
|
//#endregion
|
|
169
196
|
export { buildManifestWithClientEntry, resolveTransformAssetsConfig, transformManifestAssets };
|
|
@@ -1 +1 @@
|
|
|
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 TransformAssetsContext =\n | {\n url: string\n kind: 'modulepreload'\n }\n | {\n url: string\n kind: 'stylesheet'\n }\n | {\n url: string\n kind: 'clientEntry'\n }\n | {\n url: string\n kind: 'css-url'\n stylesheetHref: string\n }\n\nexport type TransformAssetKind = TransformAssetsContext['kind']\n\ntype TransformAssetsShorthandCrossOriginKind = Exclude<\n TransformAssetKind,\n 'clientEntry' | 'css-url'\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 type CreateTransformAssetsContext =\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\nexport type CreateTransformAssetsFn = (\n ctx: CreateTransformAssetsContext,\n) => Awaitable<TransformAssetsFn>\n\ntype TransformAssetsOptionsBase = {\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 TransformAssetsOptions =\n | (TransformAssetsOptionsBase & {\n transform: string | TransformAssetsFn\n createTransform?: never\n })\n | (TransformAssetsOptionsBase & {\n createTransform: CreateTransformAssetsFn\n transform?: never\n })\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\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 escapeCssString(value: string) {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\a ')\n .replace(/\\r/g, '\\\\d ')\n .replace(/\\f/g, '\\\\c ')\n}\n\nasync function transformInlineCssTemplate(options: {\n stylesheetHref: string\n template: { strings: Array<string>; urls: Array<string> }\n transformFn: TransformAssetsFn\n}) {\n const { strings, urls } = options.template\n\n if (strings.length !== urls.length + 1) {\n throw new Error(\n `TanStack Start inlineCss template for ${options.stylesheetHref} is invalid`,\n )\n }\n\n let css = strings[0]!\n\n for (let index = 0; index < urls.length; index++) {\n const transformed = normalizeTransformAssetResult(\n await options.transformFn({\n kind: 'css-url',\n url: urls[index]!,\n stylesheetHref: options.stylesheetHref,\n }),\n )\n\n css += escapeCssString(transformed.href) + strings[index + 1]!\n }\n\n return css\n}\n\nasync function transformInlineCssStyles(\n inlineCss: NonNullable<Manifest['inlineCss']>,\n transformFn: TransformAssetsFn,\n) {\n const transformedStyles: Record<string, string> = {}\n\n const transformedEntries = await Promise.all(\n Object.entries(inlineCss.styles).map(async ([stylesheetHref, css]) => {\n const template = inlineCss.templates?.[stylesheetHref]\n return [\n stylesheetHref,\n template\n ? await transformInlineCssTemplate({\n stylesheetHref,\n template,\n transformFn,\n })\n : css,\n ] as const\n }),\n )\n\n for (const [stylesheetHref, css] of transformedEntries) {\n transformedStyles[stylesheetHref] = css\n }\n\n return {\n styles: transformedStyles,\n ...(inlineCss.templates ? { templates: inlineCss.templates } : {}),\n }\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' || kind === 'css-url') {\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 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 inlineCss?: boolean\n },\n): Promise<Manifest> {\n const manifest = structuredClone(source.manifest)\n const inlineCssEnabled = _opts?.inlineCss !== false\n\n if (!inlineCssEnabled) {\n delete manifest.inlineCss\n } else if (manifest.inlineCss) {\n manifest.inlineCss = await transformInlineCssStyles(\n manifest.inlineCss,\n transformFn,\n )\n }\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 && !manifest.inlineCss) {\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 manifest.routes[rootRouteId] || {})\n rootRoute.assets = rootRoute.assets || []\n rootRoute.assets.push(\n buildClientEntryScriptTag(\n transformedClientEntry.href,\n source.injectedHeadScripts,\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 transformAssets 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 opts?: { inlineCss?: boolean },\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 [rootRouteId]: {\n ...baseRootRoute,\n assets: [...(baseRootRoute?.assets || []), scriptTag],\n },\n }\n\n return {\n ...(opts?.inlineCss === false\n ? {}\n : { inlineCss: structuredClone(source.manifest.inlineCss) }),\n routes,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transformAssetUrls.js","names":[],"sources":["../../src/transformAssetUrls.ts"],"sourcesContent":["import {\n getManifestScriptFormat,\n resolveManifestAssetLink,\n resolveManifestCssLink,\n} from '@tanstack/router-core'\n\nimport type {\n AssetCrossOrigin,\n Awaitable,\n ManifestAssetLink,\n ManifestCssLink,\n ManifestScript,\n ScriptFormat,\n ServerManifest,\n} from '@tanstack/router-core'\n\nexport type { AssetCrossOrigin }\n\nexport type TransformAssetsContext =\n | {\n url: string\n kind: 'script'\n }\n | {\n url: string\n kind: 'stylesheet'\n }\n | {\n url: string\n kind: 'css-url'\n stylesheetHref: string\n }\n\nexport type TransformAssetKind = TransformAssetsContext['kind']\n\ntype TransformAssetsShorthandCrossOriginKind = Exclude<\n TransformAssetKind,\n 'css-url'\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 type CreateTransformAssetsContext =\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\nexport type CreateTransformAssetsFn = (\n ctx: CreateTransformAssetsContext,\n) => Awaitable<TransformAssetsFn>\n\ntype TransformAssetsOptionsBase = {\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 TransformAssetsOptions =\n | (TransformAssetsOptionsBase & {\n transform: string | TransformAssetsFn\n createTransform?: never\n })\n | (TransformAssetsOptionsBase & {\n createTransform: CreateTransformAssetsFn\n transform?: never\n })\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: { script: '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 transformed script and stylesheet 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\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 escapeCssString(value: string) {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\a ')\n .replace(/\\r/g, '\\\\d ')\n .replace(/\\f/g, '\\\\c ')\n}\n\nasync function transformInlineCssTemplate(options: {\n stylesheetHref: string\n template: { strings: Array<string>; urls: Array<string> }\n transformFn: TransformAssetsFn\n}) {\n const { strings, urls } = options.template\n\n if (strings.length !== urls.length + 1) {\n throw new Error(\n `TanStack Start inlineCss template for ${options.stylesheetHref} is invalid`,\n )\n }\n\n let css = strings[0]!\n\n for (let index = 0; index < urls.length; index++) {\n const transformed = normalizeTransformAssetResult(\n await options.transformFn({\n kind: 'css-url',\n url: urls[index]!,\n stylesheetHref: options.stylesheetHref,\n }),\n )\n\n css += escapeCssString(transformed.href) + strings[index + 1]!\n }\n\n return css\n}\n\nasync function transformInlineCssStyles(\n inlineCss: NonNullable<ServerManifest['inlineCss']>,\n transformFn: TransformAssetsFn,\n) {\n const transformedStyles: Record<string, string> = {}\n\n const transformedEntries = await Promise.all(\n Object.entries(inlineCss.styles).map(async ([stylesheetHref, css]) => {\n const template = inlineCss.templates?.[stylesheetHref]\n return [\n stylesheetHref,\n template\n ? await transformInlineCssTemplate({\n stylesheetHref,\n template,\n transformFn,\n })\n : css,\n ] as const\n }),\n )\n\n for (const [stylesheetHref, css] of transformedEntries) {\n transformedStyles[stylesheetHref] = css\n }\n\n return {\n styles: transformedStyles,\n ...(inlineCss.templates ? { templates: inlineCss.templates } : {}),\n }\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 === 'css-url') {\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 interface StartManifestWithClientEntry {\n manifest: ServerManifest\n clientEntry: string\n}\n\n/**\n * Builds the client entry `<script>` tag from a (possibly transformed) client\n * entry URL.\n */\nexport function buildClientEntryScriptTag(\n clientEntry: string,\n scriptFormat: ScriptFormat = 'module',\n crossOrigin?: AssetCrossOrigin,\n): ManifestScript {\n return {\n attrs: {\n ...(scriptFormat === 'module' ? { type: 'module' } : {}),\n async: true,\n src: clientEntry,\n ...(crossOrigin ? { crossOrigin } : {}),\n },\n }\n}\n\ntype AssignableManifestLink = ManifestAssetLink | ManifestCssLink\n\nfunction assignManifestLink(\n link: ManifestAssetLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): ManifestAssetLink\nfunction assignManifestLink(\n link: ManifestCssLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): ManifestCssLink\nfunction assignManifestLink(\n link: AssignableManifestLink,\n next: { href: string; crossOrigin?: AssetCrossOrigin },\n): AssignableManifestLink {\n if (typeof link === 'string') {\n return next.crossOrigin ? next : next.href\n }\n\n const nextLink: Exclude<ManifestCssLink, string> = {\n ...link,\n href: next.href,\n }\n\n if (next.crossOrigin) {\n nextLink.crossOrigin = next.crossOrigin\n } else {\n delete nextLink.crossOrigin\n }\n\n return nextLink\n}\n\nfunction appendUniqueManifestAssetLink(\n target: Array<ManifestAssetLink> | undefined,\n link: ManifestAssetLink,\n) {\n const href = typeof link === 'string' ? link : link.href\n\n if (target) {\n for (const item of target) {\n if ((typeof item === 'string' ? item : item.href) === href) {\n return target\n }\n }\n }\n\n return [...(target ?? []), link]\n}\n\nfunction addClientEntryToManifest(\n manifest: ServerManifest,\n clientEntry: string,\n) {\n const rootRoute = manifest.routes.__root__ ?? {}\n const rootScripts = rootRoute.scripts ?? []\n const scripts = rootScripts.some(\n (script) => script.attrs?.src === clientEntry,\n )\n ? rootScripts\n : [\n ...rootScripts,\n buildClientEntryScriptTag(\n clientEntry,\n getManifestScriptFormat(manifest),\n ),\n ]\n\n manifest.routes = {\n ...manifest.routes,\n __root__: {\n ...rootRoute,\n preloads: appendUniqueManifestAssetLink(rootRoute.preloads, clientEntry),\n scripts,\n },\n }\n}\n\nexport async function transformManifestAssets(\n source: StartManifestWithClientEntry,\n transformFn: TransformAssetsFn,\n _opts?: {\n clone?: boolean\n inlineCss?: boolean\n },\n): Promise<ServerManifest> {\n const manifest = structuredClone(source.manifest)\n const inlineCssEnabled = _opts?.inlineCss !== false\n const scriptTransforms = new Map<\n string,\n Promise<Exclude<TransformAssetResult, string>>\n >()\n const transformScript = (url: string) => {\n const cached = scriptTransforms.get(url)\n if (cached) {\n return cached\n }\n\n const transformed = Promise.resolve(\n transformFn({\n url,\n kind: 'script',\n }),\n ).then(normalizeTransformAssetResult)\n scriptTransforms.set(url, transformed)\n return transformed\n }\n\n if (!inlineCssEnabled) {\n delete manifest.inlineCss\n } else if (manifest.inlineCss) {\n manifest.inlineCss = await transformInlineCssStyles(\n manifest.inlineCss,\n transformFn,\n )\n }\n\n addClientEntryToManifest(manifest, source.clientEntry)\n\n for (const route of Object.values(manifest.routes)) {\n if (route.preloads?.length) {\n route.preloads = await Promise.all(\n route.preloads.map(async (link) => {\n const resolved = resolveManifestAssetLink(link)\n const result = await transformScript(resolved.href)\n\n return assignManifestLink(link, {\n href: result.href,\n crossOrigin: result.crossOrigin,\n })\n }),\n )\n }\n\n if (route.css?.length && !manifest.inlineCss) {\n route.css = await Promise.all(\n route.css.map(async (link) => {\n const resolved = resolveManifestCssLink(link)\n const result = normalizeTransformAssetResult(\n await transformFn({\n url: resolved.href,\n kind: 'stylesheet',\n }),\n )\n\n return assignManifestLink(link, {\n href: result.href,\n crossOrigin: result.crossOrigin,\n })\n }),\n )\n }\n\n if (route.scripts?.length) {\n for (const script of route.scripts) {\n const src = script.attrs?.src\n if (typeof src !== 'string') {\n continue\n }\n\n const result = await transformScript(src)\n\n script.attrs = {\n ...script.attrs,\n src: result.href,\n }\n if (result.crossOrigin) {\n script.attrs.crossOrigin = result.crossOrigin\n } else {\n delete script.attrs.crossOrigin\n }\n }\n }\n }\n\n return manifest\n}\n\n/**\n * Builds a final ServerManifest from a StartManifestWithClientEntry without any\n * URL transforms. Used when no transformAssets 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 opts?: { inlineCss?: boolean },\n): ServerManifest {\n const manifest: ServerManifest = {\n ...(source.manifest.scriptFormat\n ? { scriptFormat: source.manifest.scriptFormat }\n : {}),\n ...(opts?.inlineCss !== false && source.manifest.inlineCss\n ? { inlineCss: structuredClone(source.manifest.inlineCss) }\n : {}),\n routes: {\n ...source.manifest.routes,\n },\n }\n\n addClientEntryToManifest(manifest, source.clientEntry)\n\n return manifest\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.169.
|
|
3
|
+
"version": "1.169.3",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"h3-v2": "npm:h3@2.0.1-rc.20",
|
|
68
68
|
"seroval": "^1.5.4",
|
|
69
69
|
"@tanstack/history": "1.162.0",
|
|
70
|
-
"@tanstack/router-core": "1.171.
|
|
71
|
-
"@tanstack/start-client-core": "1.170.
|
|
72
|
-
"@tanstack/start-storage-context": "1.167.
|
|
70
|
+
"@tanstack/router-core": "1.171.6",
|
|
71
|
+
"@tanstack/start-client-core": "1.170.3",
|
|
72
|
+
"@tanstack/start-storage-context": "1.167.8"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@standard-schema/spec": "^1.0.0",
|
|
@@ -497,7 +497,6 @@ export function createStartHandler<TRegister = Register>(
|
|
|
497
497
|
manifest,
|
|
498
498
|
getRequestAssets: () =>
|
|
499
499
|
getStartContext({ throwIfNotFound: false })?.requestAssets,
|
|
500
|
-
includeUnmatchedRouteAssets: false,
|
|
501
500
|
})
|
|
502
501
|
|
|
503
502
|
routerInstance.update({ additionalContext: { serverContext } })
|
package/src/early-hints.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getScriptPreloadAttrs,
|
|
2
3
|
getStylesheetHref,
|
|
3
|
-
|
|
4
|
+
resolveManifestCssLink,
|
|
4
5
|
} from '@tanstack/router-core'
|
|
5
6
|
import type {
|
|
6
7
|
AnyRoute,
|
|
7
8
|
AnyRouteMatch,
|
|
8
9
|
AssetCrossOrigin,
|
|
9
|
-
Manifest,
|
|
10
10
|
RouterManagedTag,
|
|
11
|
+
ServerManifest,
|
|
11
12
|
} from '@tanstack/router-core'
|
|
12
13
|
|
|
13
14
|
export type EarlyHint = {
|
|
@@ -49,7 +50,7 @@ export type ResponseLinkHeaderOptions = {
|
|
|
49
50
|
|
|
50
51
|
export interface EarlyHintsCollector {
|
|
51
52
|
collectStatic: (opts: {
|
|
52
|
-
manifest:
|
|
53
|
+
manifest: ServerManifest
|
|
53
54
|
matchedRoutes?: ReadonlyArray<AnyRoute>
|
|
54
55
|
}) => void
|
|
55
56
|
collectDynamic: (matches: ReadonlyArray<AnyRouteMatch>) => void
|
|
@@ -174,7 +175,7 @@ function linkAttrsToEarlyHint(
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
export function collectStaticHintsFromManifest(
|
|
177
|
-
manifest:
|
|
178
|
+
manifest: ServerManifest,
|
|
178
179
|
matchedRoutes: ReadonlyArray<AnyRoute>,
|
|
179
180
|
): Array<EarlyHint> {
|
|
180
181
|
const hints: Array<EarlyHint> = []
|
|
@@ -184,33 +185,32 @@ export function collectStaticHintsFromManifest(
|
|
|
184
185
|
if (!routeManifest) continue
|
|
185
186
|
|
|
186
187
|
for (const link of routeManifest.preloads ?? []) {
|
|
187
|
-
const
|
|
188
|
-
const hint: EarlyHint = {
|
|
189
|
-
|
|
188
|
+
const attrs = getScriptPreloadAttrs(manifest, link)
|
|
189
|
+
const hint: EarlyHint = {
|
|
190
|
+
href: attrs.href,
|
|
191
|
+
rel: attrs.rel,
|
|
192
|
+
as: 'script',
|
|
193
|
+
}
|
|
194
|
+
if (attrs.crossOrigin !== undefined) hint.crossOrigin = attrs.crossOrigin
|
|
190
195
|
hints.push(hint)
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
for (const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const stylesheetHref = getStylesheetHref(asset)
|
|
197
|
-
if (stylesheetHref) {
|
|
198
|
-
if (manifest.inlineCss?.styles[stylesheetHref] !== undefined) continue
|
|
199
|
-
|
|
200
|
-
const hint: EarlyHint = {
|
|
201
|
-
href: stylesheetHref,
|
|
202
|
-
rel: 'preload',
|
|
203
|
-
as: 'style',
|
|
204
|
-
}
|
|
205
|
-
addEarlyHintFetchAttrs(hint, asset.attrs)
|
|
206
|
-
hints.push(hint)
|
|
198
|
+
for (const link of routeManifest.css ?? []) {
|
|
199
|
+
const stylesheetHref = getStylesheetHref(link)
|
|
200
|
+
if (manifest.inlineCss?.styles[stylesheetHref] !== undefined) {
|
|
207
201
|
continue
|
|
208
202
|
}
|
|
203
|
+
const resolvedLink = resolveManifestCssLink(link)
|
|
209
204
|
|
|
210
|
-
const hint =
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
const hint: EarlyHint = {
|
|
206
|
+
href: stylesheetHref,
|
|
207
|
+
rel: 'preload',
|
|
208
|
+
as: 'style',
|
|
213
209
|
}
|
|
210
|
+
if (resolvedLink.crossOrigin !== undefined) {
|
|
211
|
+
hint.crossOrigin = resolvedLink.crossOrigin
|
|
212
|
+
}
|
|
213
|
+
hints.push(hint)
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
package/src/finalManifest.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getStaticHandlerInlineCssDefault,
|
|
8
8
|
resolveInlineCssForRequest,
|
|
9
9
|
} from './inlineCss'
|
|
10
|
-
import type {
|
|
10
|
+
import type { ServerManifest } from '@tanstack/router-core'
|
|
11
11
|
import type { HandlerInlineCssOption } from './inlineCss'
|
|
12
12
|
import type {
|
|
13
13
|
CreateTransformAssetsContext,
|
|
@@ -45,7 +45,7 @@ export interface FinalManifestOptions {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
type FinalManifestCacheKey = 'inline-css' | 'linked-css'
|
|
48
|
-
type FinalManifestCache = Map<FinalManifestCacheKey, Promise<
|
|
48
|
+
type FinalManifestCache = Map<FinalManifestCacheKey, Promise<ServerManifest>>
|
|
49
49
|
export type GetBaseManifest = () => Promise<StartManifestWithClientEntry>
|
|
50
50
|
|
|
51
51
|
export interface FinalManifestRequestOptions {
|
|
@@ -66,9 +66,11 @@ interface FinalManifestTransformResolver {
|
|
|
66
66
|
export interface FinalManifestResolver {
|
|
67
67
|
warmup: (opts: {
|
|
68
68
|
getBaseManifest: GetBaseManifest
|
|
69
|
-
}) => Promise<
|
|
70
|
-
resolveCached: (opts: FinalManifestRequestOptions) => Promise<
|
|
71
|
-
resolveUncached: (
|
|
69
|
+
}) => Promise<ServerManifest> | undefined
|
|
70
|
+
resolveCached: (opts: FinalManifestRequestOptions) => Promise<ServerManifest>
|
|
71
|
+
resolveUncached: (
|
|
72
|
+
opts: FinalManifestRequestOptions,
|
|
73
|
+
) => Promise<ServerManifest>
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
export function createCachedBaseManifestLoader(
|
|
@@ -206,8 +208,8 @@ function getFinalManifestCacheKey(inlineCss: boolean): FinalManifestCacheKey {
|
|
|
206
208
|
function cacheFinalManifestPromise(
|
|
207
209
|
cachedFinalManifestPromises: FinalManifestCache,
|
|
208
210
|
cacheKey: FinalManifestCacheKey,
|
|
209
|
-
promise: Promise<
|
|
210
|
-
): Promise<
|
|
211
|
+
promise: Promise<ServerManifest>,
|
|
212
|
+
): Promise<ServerManifest> {
|
|
211
213
|
const cachedFinalManifestPromise = promise.catch((error) => {
|
|
212
214
|
if (
|
|
213
215
|
cachedFinalManifestPromises.get(cacheKey) === cachedFinalManifestPromise
|
|
@@ -224,8 +226,8 @@ function cacheFinalManifestPromise(
|
|
|
224
226
|
function getOrCreateCachedFinalManifestPromise(
|
|
225
227
|
cachedFinalManifestPromises: FinalManifestCache,
|
|
226
228
|
cacheKey: FinalManifestCacheKey,
|
|
227
|
-
computeFinalManifest: () => Promise<
|
|
228
|
-
): Promise<
|
|
229
|
+
computeFinalManifest: () => Promise<ServerManifest>,
|
|
230
|
+
): Promise<ServerManifest> {
|
|
229
231
|
const cachedFinalManifestPromise = cachedFinalManifestPromises.get(cacheKey)
|
|
230
232
|
if (cachedFinalManifestPromise) {
|
|
231
233
|
return cachedFinalManifestPromise
|
|
@@ -242,7 +244,7 @@ async function buildFinalManifest(opts: {
|
|
|
242
244
|
base: StartManifestWithClientEntry
|
|
243
245
|
transformFn: TransformAssetsFn | undefined
|
|
244
246
|
inlineCss: boolean
|
|
245
|
-
}): Promise<
|
|
247
|
+
}): Promise<ServerManifest> {
|
|
246
248
|
return opts.transformFn
|
|
247
249
|
? await transformManifestAssets(opts.base, opts.transformFn, {
|
|
248
250
|
inlineCss: opts.inlineCss,
|
|
@@ -256,7 +258,7 @@ async function resolveFinalManifest(opts: {
|
|
|
256
258
|
cache: boolean
|
|
257
259
|
inlineCss: boolean
|
|
258
260
|
finalManifestCache?: FinalManifestCache
|
|
259
|
-
}): Promise<
|
|
261
|
+
}): Promise<ServerManifest> {
|
|
260
262
|
const computeFinalManifest = async () => {
|
|
261
263
|
return buildFinalManifest({
|
|
262
264
|
base: await opts.getBaseManifest(),
|
|
@@ -284,7 +286,7 @@ function warmupFinalManifest(opts: {
|
|
|
284
286
|
getBaseManifest: () => Promise<StartManifestWithClientEntry>
|
|
285
287
|
getTransformFn: () => Promise<TransformAssetsFn | undefined>
|
|
286
288
|
onError?: () => void
|
|
287
|
-
}): Promise<
|
|
289
|
+
}): Promise<ServerManifest> | undefined {
|
|
288
290
|
if (
|
|
289
291
|
!opts.enabled ||
|
|
290
292
|
opts.handlerDefaultInlineCss === undefined ||
|
package/src/request-handler.ts
CHANGED
|
@@ -9,7 +9,8 @@ type EarlyHintsOptions = {
|
|
|
9
9
|
* Fire-and-forget callback for HTTP 103 Early Hints.
|
|
10
10
|
* Only invoked in production (when TSS_DEV_SERVER !== 'true').
|
|
11
11
|
*
|
|
12
|
-
* The `static` phase contains transformed manifest
|
|
12
|
+
* The `static` phase contains transformed manifest preloads and stylesheets
|
|
13
|
+
* for matched routes.
|
|
13
14
|
* The `dynamic` phase runs after route load, is skipped for redirects, and
|
|
14
15
|
* can contain route `head().links` or empty `hints` and `links` arrays.
|
|
15
16
|
* `hints` and `links` contain only values not emitted in earlier phases.
|