@tanstack/start-server-core
Version:
Modern and scalable routing for React applications
163 lines (162 loc) • 5.65 kB
JavaScript
import { resolveManifestAssetLink, resolveManifestCssLink } from "@tanstack/router-core";
//#region src/transformAssetUrls.ts
function normalizeTransformAssetResult(result) {
if (typeof result === "string") return { href: result };
return result;
}
function escapeCssString(value) {
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\a ").replace(/\r/g, "\\d ").replace(/\f/g, "\\c ");
}
async function transformInlineCssTemplate(options) {
const { strings, urls } = options.template;
if (strings.length !== urls.length + 1) throw new Error(`TanStack Start inlineCss template for ${options.stylesheetHref} is invalid`);
let css = strings[0];
for (let index = 0; index < urls.length; index++) {
const transformed = normalizeTransformAssetResult(await options.transformFn({
kind: "css-url",
url: urls[index],
stylesheetHref: options.stylesheetHref
}));
css += escapeCssString(transformed.href) + strings[index + 1];
}
return css;
}
async function transformInlineCssStyles(inlineCss, transformFn) {
const transformedStyles = {};
const transformedEntries = await Promise.all(Object.entries(inlineCss.styles).map(async ([stylesheetHref, css]) => {
const template = inlineCss.templates?.[stylesheetHref];
return [stylesheetHref, template ? await transformInlineCssTemplate({
stylesheetHref,
template,
transformFn
}) : css];
}));
for (const [stylesheetHref, css] of transformedEntries) transformedStyles[stylesheetHref] = css;
return {
styles: transformedStyles,
...inlineCss.templates ? { templates: inlineCss.templates } : {}
};
}
function resolveTransformAssetsCrossOrigin(config, kind) {
if (!config) return void 0;
if (typeof config === "string") return config;
return config[kind];
}
function isObjectShorthand(transform) {
return "prefix" in transform;
}
function resolveTransformAssetsConfig(transform) {
if (typeof transform === "string") {
const prefix = transform;
return {
type: "transform",
transformFn: ({ url }) => ({ href: `${prefix}${url}` }),
cache: true
};
}
if (typeof transform === "function") return {
type: "transform",
transformFn: transform,
cache: true
};
if (isObjectShorthand(transform)) {
const { prefix, crossOrigin } = transform;
return {
type: "transform",
transformFn: ({ url, kind }) => {
const href = `${prefix}${url}`;
if (kind === "css-url") return { href };
const co = resolveTransformAssetsCrossOrigin(crossOrigin, kind);
return co ? {
href,
crossOrigin: co
} : { href };
},
cache: true
};
}
if ("createTransform" in transform && transform.createTransform) return {
type: "createTransform",
createTransform: transform.createTransform,
cache: transform.cache !== false
};
return {
type: "transform",
transformFn: typeof transform.transform === "string" ? (({ url }) => ({ href: `${transform.transform}${url}` })) : transform.transform,
cache: transform.cache !== false
};
}
function assignManifestLink(link, next) {
if (typeof link === "string") return next.crossOrigin ? next : next.href;
const nextLink = {
...link,
href: next.href
};
if (next.crossOrigin) nextLink.crossOrigin = next.crossOrigin;
else delete nextLink.crossOrigin;
return nextLink;
}
async function transformManifestAssets(source, transformFn, _opts) {
const manifest = structuredClone(source);
const inlineCssEnabled = _opts?.inlineCss !== false;
const scriptTransforms = /* @__PURE__ */ new Map();
const transformScript = (url) => {
const cached = scriptTransforms.get(url);
if (cached) return cached;
const transformed = Promise.resolve(transformFn({
url,
kind: "script"
})).then(normalizeTransformAssetResult);
scriptTransforms.set(url, transformed);
return transformed;
};
if (!inlineCssEnabled) delete manifest.inlineCss;
else if (manifest.inlineCss) manifest.inlineCss = await transformInlineCssStyles(manifest.inlineCss, transformFn);
for (const route of Object.values(manifest.routes)) {
if (route.preloads?.length) route.preloads = await Promise.all(route.preloads.map(async (link) => {
const result = await transformScript(resolveManifestAssetLink(link).href);
return assignManifestLink(link, {
href: result.href,
crossOrigin: result.crossOrigin
});
}));
if (route.css?.length && !manifest.inlineCss) route.css = await Promise.all(route.css.map(async (link) => {
const result = normalizeTransformAssetResult(await transformFn({
url: resolveManifestCssLink(link).href,
kind: "stylesheet"
}));
return assignManifestLink(link, {
href: result.href,
crossOrigin: result.crossOrigin
});
}));
if (route.scripts?.length) for (const script of route.scripts) {
const src = script.attrs?.src;
if (typeof src !== "string") continue;
const result = await transformScript(src);
script.attrs = {
...script.attrs,
src: result.href
};
if (result.crossOrigin) script.attrs.crossOrigin = result.crossOrigin;
else delete script.attrs.crossOrigin;
}
}
return manifest;
}
/**
* Builds a final ServerManifest without URL transforms. Used when no
* transformAssets option is provided.
*
* Returns a new manifest object so the cached base manifest is never mutated.
*/
function buildManifest(source, opts) {
return {
...source.scriptFormat ? { scriptFormat: source.scriptFormat } : {},
...opts?.inlineCss !== false && source.inlineCss ? { inlineCss: structuredClone(source.inlineCss) } : {},
routes: { ...source.routes }
};
}
//#endregion
export { buildManifest, resolveTransformAssetsConfig, transformManifestAssets };
//# sourceMappingURL=transformAssetUrls.js.map