@tanstack/react-router
Version:
Modern and scalable routing for React applications
1 lines • 16 kB
Source Map (JSON)
{"version":3,"file":"headContentUtils.cjs","names":[],"sources":["../../src/headContentUtils.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useStore } from '@tanstack/react-store'\nimport {\n deepEqual,\n escapeHtml,\n getAssetCrossOrigin,\n resolveManifestAssetLink,\n} from '@tanstack/router-core'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport { useRouter } from './useRouter'\nimport type {\n AssetCrossOriginConfig,\n RouterManagedTag,\n} from '@tanstack/router-core'\n\nfunction buildTagsFromMatches(\n router: ReturnType<typeof useRouter>,\n nonce: string | undefined,\n matches: Array<any>,\n assetCrossOrigin?: AssetCrossOriginConfig,\n): Array<RouterManagedTag> {\n const routeMeta = matches.map((match) => match.meta!).filter(Boolean)\n\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n for (let i = routeMeta.length - 1; i >= 0; i--) {\n const metas = routeMeta[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: nonce,\n },\n })\n }\n resultMeta.reverse()\n\n const constructedLinks = matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n const assetLinks = matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: {\n ...asset.attrs,\n crossOrigin:\n getAssetCrossOrigin(assetCrossOrigin, 'stylesheet') ??\n asset.attrs?.crossOrigin,\n suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n const preloadLinks: Array<RouterManagedTag> = []\n matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n const preloadLink = resolveManifestAssetLink(preload)\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preloadLink.href,\n crossOrigin:\n getAssetCrossOrigin(assetCrossOrigin, 'modulepreload') ??\n preloadLink.crossOrigin,\n nonce,\n },\n })\n }),\n )\n\n const styles = (\n matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...attrs }) => ({\n tag: 'style',\n attrs: {\n ...attrs,\n nonce,\n },\n children,\n }))\n\n const headScripts = (\n matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n }))\n\n return uniqBy(\n [\n ...resultMeta,\n ...preloadLinks,\n ...constructedLinks,\n ...assetLinks,\n ...styles,\n ...headScripts,\n ] as Array<RouterManagedTag>,\n (d) => JSON.stringify(d),\n )\n}\n\n/**\n * Build the list of head/link/meta/script tags to render for active matches.\n * Used internally by `HeadContent`.\n */\nexport const useTags = (assetCrossOrigin?: AssetCrossOriginConfig) => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n\n if (isServer ?? router.isServer) {\n return buildTagsFromMatches(\n router,\n nonce,\n router.stores.activeMatchesSnapshot.state,\n assetCrossOrigin,\n )\n }\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const routeMeta = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n return matches.map((match) => match.meta!).filter(Boolean)\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const meta: Array<RouterManagedTag> = React.useMemo(() => {\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n for (let i = routeMeta.length - 1; i >= 0; i--) {\n const metas = routeMeta[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n // Handle JSON-LD structured data\n // Content is HTML-escaped to prevent XSS when injected via dangerouslySetInnerHTML\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: nonce,\n },\n })\n }\n resultMeta.reverse()\n\n return resultMeta\n }, [routeMeta, nonce])\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const links = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n const constructed = matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n\n // These are the assets extracted from the ViteManifest\n // using the `startManifestPlugin`\n const assets = matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: {\n ...asset.attrs,\n crossOrigin:\n getAssetCrossOrigin(assetCrossOrigin, 'stylesheet') ??\n asset.attrs?.crossOrigin,\n suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const preloadLinks = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n const preloadLink = resolveManifestAssetLink(preload)\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preloadLink.href,\n crossOrigin:\n getAssetCrossOrigin(assetCrossOrigin, 'modulepreload') ??\n preloadLink.crossOrigin,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const styles = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) =>\n (\n matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...attrs }) => ({\n tag: 'style',\n attrs: {\n ...attrs,\n nonce,\n },\n children,\n })),\n deepEqual,\n )\n\n // eslint-disable-next-line react-hooks/rules-of-hooks -- condition is static\n const headScripts: Array<RouterManagedTag> = useStore(\n router.stores.activeMatchesSnapshot,\n (matches) =>\n (\n matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n })),\n deepEqual,\n )\n\n return uniqBy(\n [\n ...meta,\n ...preloadLinks,\n ...links,\n ...styles,\n ...headScripts,\n ] as Array<RouterManagedTag>,\n (d) => {\n return JSON.stringify(d)\n },\n )\n}\n\nexport function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {\n const seen = new Set<string>()\n return arr.filter((item) => {\n const key = fn(item)\n if (seen.has(key)) {\n return false\n }\n seen.add(key)\n return true\n })\n}\n"],"mappings":"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"}