@tanstack/react-router 1.146.2 → 1.147.0
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/cjs/HeadContent.cjs +24 -1
- package/dist/cjs/HeadContent.cjs.map +1 -1
- package/dist/cjs/HeadContent.d.cts +1 -1
- package/dist/esm/HeadContent.d.ts +1 -1
- package/dist/esm/HeadContent.js +24 -1
- package/dist/esm/HeadContent.js.map +1 -1
- package/package.json +1 -1
- package/src/HeadContent.tsx +41 -3
package/dist/cjs/HeadContent.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
3
4
|
const React = require("react");
|
|
4
5
|
const routerCore = require("@tanstack/router-core");
|
|
5
6
|
const Asset = require("./Asset.cjs");
|
|
@@ -168,11 +169,33 @@ const useTags = () => {
|
|
|
168
169
|
}
|
|
169
170
|
);
|
|
170
171
|
};
|
|
172
|
+
function DevStylesLink() {
|
|
173
|
+
useRouter.useRouter();
|
|
174
|
+
const routeIds = useRouterState.useRouterState({
|
|
175
|
+
select: (state) => state.matches.map((match) => match.routeId)
|
|
176
|
+
});
|
|
177
|
+
React__namespace.useEffect(() => {
|
|
178
|
+
document.querySelectorAll("[data-tanstack-start-dev-styles]").forEach((el) => el.remove());
|
|
179
|
+
}, []);
|
|
180
|
+
const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
|
|
181
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
182
|
+
"link",
|
|
183
|
+
{
|
|
184
|
+
rel: "stylesheet",
|
|
185
|
+
href,
|
|
186
|
+
"data-tanstack-start-dev-styles": true,
|
|
187
|
+
suppressHydrationWarning: true
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
171
191
|
function HeadContent() {
|
|
172
192
|
const tags = useTags();
|
|
173
193
|
const router = useRouter.useRouter();
|
|
174
194
|
const nonce = router.options.ssr?.nonce;
|
|
175
|
-
return
|
|
195
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
196
|
+
process.env.NODE_ENV !== "production" && /* @__PURE__ */ jsxRuntime.jsx(DevStylesLink, {}),
|
|
197
|
+
tags.map((tag) => /* @__PURE__ */ React.createElement(Asset.Asset, { ...tag, key: `tsr-meta-${JSON.stringify(tag)}`, nonce }))
|
|
198
|
+
] });
|
|
176
199
|
}
|
|
177
200
|
function uniqBy(arr, fn) {
|
|
178
201
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeadContent.cjs","sources":["../../src/HeadContent.tsx"],"sourcesContent":["import * as React from 'react'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { Asset } from './Asset'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\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 = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\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 const links = useRouterState({\n select: (state) => {\n const constructed = state.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 = state.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 suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n structuralSharing: true as any,\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.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 preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n structuralSharing: true as any,\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.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 children,\n nonce,\n })),\n structuralSharing: true as any,\n })\n\n const headScripts: Array<RouterManagedTag> = useRouterState({\n select: (state) =>\n (\n state.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 structuralSharing: true as any,\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\n/**\n * Render route-managed head tags (title, meta, links, styles, head scripts).\n * Place inside the document head of your app shell.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management\n */\nexport function HeadContent() {\n const tags = useTags()\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n return tags.map((tag) => (\n <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} nonce={nonce} />\n ))\n}\n\nfunction 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"],"names":["useRouter","useRouterState","React","escapeHtml","preloadLinks","createElement","Asset"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HeadContent.cjs","sources":["../../src/HeadContent.tsx"],"sourcesContent":["import * as React from 'react'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { Asset } from './Asset'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\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 = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\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 const links = useRouterState({\n select: (state) => {\n const constructed = state.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 = state.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 suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n structuralSharing: true as any,\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.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 preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n structuralSharing: true as any,\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.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 children,\n nonce,\n })),\n structuralSharing: true as any,\n })\n\n const headScripts: Array<RouterManagedTag> = useRouterState({\n select: (state) =>\n (\n state.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 structuralSharing: true as any,\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\n/**\n * Renders a stylesheet link for dev mode CSS collection.\n * On the server, renders the full link with route-scoped CSS URL.\n * On the client, renders the same link to avoid hydration mismatch,\n * then removes it after hydration since Vite's HMR handles CSS updates.\n */\nfunction DevStylesLink() {\n const router = useRouter()\n\n const routeIds = useRouterState({\n select: (state) => state.matches.map((match) => match.routeId),\n })\n\n React.useEffect(() => {\n // After hydration, remove the SSR-rendered dev styles link\n document\n .querySelectorAll('[data-tanstack-start-dev-styles]')\n .forEach((el) => el.remove())\n }, [])\n\n // Build the same href on both server and client for hydration match\n const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n\n return (\n <link\n rel=\"stylesheet\"\n href={href}\n data-tanstack-start-dev-styles\n suppressHydrationWarning\n />\n )\n}\n\n/**\n * Render route-managed head tags (title, meta, links, styles, head scripts).\n * Place inside the document head of your app shell.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management\n */\nexport function HeadContent() {\n const tags = useTags()\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n return (\n <>\n {process.env.NODE_ENV !== 'production' && <DevStylesLink />}\n {tags.map((tag) => (\n <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} nonce={nonce} />\n ))}\n </>\n )\n}\n\nfunction 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"],"names":["useRouter","useRouterState","React","escapeHtml","preloadLinks","jsx","jsxs","Fragment","createElement","Asset"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAWO,MAAM,UAAU,MAAM;AAC3B,QAAM,SAASA,UAAAA,UAAA;AACf,QAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,QAAM,YAAYC,eAAAA,eAAe;AAAA,IAC/B,QAAQ,CAAC,UAAU;AACjB,aAAO,MAAM,QAAQ,IAAI,CAAC,UAAU,MAAM,IAAK,EAAE,OAAO,OAAO;AAAA,IACjE;AAAA,EAAA,CACD;AAED,QAAM,OAAgCC,iBAAM,QAAQ,MAAM;AACxD,UAAM,aAAsC,CAAA;AAC5C,UAAM,kBAAwC,CAAA;AAC9C,QAAI;AACJ,aAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,YAAM,QAAQ,UAAU,CAAC;AACzB,eAAS,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;AAC1C,cAAM,IAAI,MAAM,CAAC;AACjB,YAAI,CAAC,EAAG;AAER,YAAI,EAAE,OAAO;AACX,cAAI,CAAC,OAAO;AACV,oBAAQ;AAAA,cACN,KAAK;AAAA,cACL,UAAU,EAAE;AAAA,YAAA;AAAA,UAEhB;AAAA,QACF,WAAW,oBAAoB,GAAG;AAGhC,cAAI;AACF,kBAAM,OAAO,KAAK,UAAU,EAAE,gBAAgB,CAAC;AAC/C,uBAAW,KAAK;AAAA,cACd,KAAK;AAAA,cACL,OAAO;AAAA,gBACL,MAAM;AAAA,cAAA;AAAA,cAER,UAAUC,WAAAA,WAAW,IAAI;AAAA,YAAA,CAC1B;AAAA,UACH,QAAQ;AAAA,UAER;AAAA,QACF,OAAO;AACL,gBAAM,YAAY,EAAE,QAAQ,EAAE;AAC9B,cAAI,WAAW;AACb,gBAAI,gBAAgB,SAAS,GAAG;AAC9B;AAAA,YACF,OAAO;AACL,8BAAgB,SAAS,IAAI;AAAA,YAC/B;AAAA,UACF;AAEA,qBAAW,KAAK;AAAA,YACd,KAAK;AAAA,YACL,OAAO;AAAA,cACL,GAAG;AAAA,cACH;AAAA,YAAA;AAAA,UACF,CACD;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO;AACT,iBAAW,KAAK,KAAK;AAAA,IACvB;AAEA,QAAI,OAAO;AACT,iBAAW,KAAK;AAAA,QACd,KAAK;AAAA,QACL,OAAO;AAAA,UACL,UAAU;AAAA,UACV,SAAS;AAAA,QAAA;AAAA,MACX,CACD;AAAA,IACH;AACA,eAAW,QAAA;AAEX,WAAO;AAAA,EACT,GAAG,CAAC,WAAW,KAAK,CAAC;AAErB,QAAM,QAAQF,eAAAA,eAAe;AAAA,IAC3B,QAAQ,CAAC,UAAU;AACjB,YAAM,cAAc,MAAM,QACvB,IAAI,CAAC,UAAU,MAAM,KAAM,EAC3B,OAAO,OAAO,EACd,KAAK,CAAC,EACN,IAAI,CAAC,UAAU;AAAA,QACd,KAAK;AAAA,QACL,OAAO;AAAA,UACL,GAAG;AAAA,UACH;AAAA,QAAA;AAAA,MACF,EACA;AAEJ,YAAM,WAAW,OAAO,KAAK;AAI7B,YAAM,SAAS,MAAM,QAClB,IAAI,CAAC,UAAU,UAAU,OAAO,MAAM,OAAO,GAAG,UAAU,CAAA,CAAE,EAC5D,OAAO,OAAO,EACd,KAAK,CAAC,EACN,OAAO,CAAC,UAAU,MAAM,QAAQ,MAAM,EACtC;AAAA,QACC,CAAC,WACE;AAAA,UACC,KAAK;AAAA,UACL,OAAO;AAAA,YACL,GAAG,MAAM;AAAA,YACT,0BAA0B;AAAA,YAC1B;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAGN,aAAO,CAAC,GAAG,aAAa,GAAG,MAAM;AAAA,IACnC;AAAA,IACA,mBAAmB;AAAA,EAAA,CACpB;AAED,QAAM,eAAeA,eAAAA,eAAe;AAAA,IAClC,QAAQ,CAAC,UAAU;AACjB,YAAMG,gBAAwC,CAAA;AAE9C,YAAM,QACH,IAAI,CAAC,UAAU,OAAO,gBAAgB,MAAM,OAAO,CAAE,EACrD;AAAA,QAAQ,CAAC,UACR,OAAO,KAAK,UAAU,OAAO,MAAM,EAAE,GAAG,UACpC,OAAO,OAAO,EACf,QAAQ,CAAC,YAAY;AACpBA,wBAAa,KAAK;AAAA,YAChB,KAAK;AAAA,YACL,OAAO;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN;AAAA,YAAA;AAAA,UACF,CACD;AAAA,QACH,CAAC;AAAA,MAAA;AAGP,aAAOA;AAAAA,IACT;AAAA,IACA,mBAAmB;AAAA,EAAA,CACpB;AAED,QAAM,SAASH,eAAAA,eAAe;AAAA,IAC5B,QAAQ,CAAC,UAEL,MAAM,QACH,IAAI,CAAC,UAAU,MAAM,MAAO,EAC5B,KAAK,CAAC,EACN,OAAO,OAAO,EACjB,IAAI,CAAC,EAAE,UAAU,GAAG,aAAa;AAAA,MACjC,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IAAA,EACA;AAAA,IACJ,mBAAmB;AAAA,EAAA,CACpB;AAED,QAAM,cAAuCA,eAAAA,eAAe;AAAA,IAC1D,QAAQ,CAAC,UAEL,MAAM,QACH,IAAI,CAAC,UAAU,MAAM,WAAY,EACjC,KAAK,CAAC,EACN,OAAO,OAAO,EACjB,IAAI,CAAC,EAAE,UAAU,GAAG,cAAc;AAAA,MAClC,KAAK;AAAA,MACL,OAAO;AAAA,QACL,GAAG;AAAA,QACH;AAAA,MAAA;AAAA,MAEF;AAAA,IAAA,EACA;AAAA,IACJ,mBAAmB;AAAA,EAAA,CACpB;AAED,SAAO;AAAA,IACL;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,IAAA;AAAA,IAEL,CAAC,MAAM;AACL,aAAO,KAAK,UAAU,CAAC;AAAA,IACzB;AAAA,EAAA;AAEJ;AAQA,SAAS,gBAAgB;AACRD,YAAAA,UAAA;AAEf,QAAM,WAAWC,eAAAA,eAAe;AAAA,IAC9B,QAAQ,CAAC,UAAU,MAAM,QAAQ,IAAI,CAAC,UAAU,MAAM,OAAO;AAAA,EAAA,CAC9D;AAEDC,mBAAM,UAAU,MAAM;AAEpB,aACG,iBAAiB,kCAAkC,EACnD,QAAQ,CAAC,OAAO,GAAG,QAAQ;AAAA,EAChC,GAAG,CAAA,CAAE;AAGL,QAAM,OAAO,sCAAsC,mBAAmB,SAAS,KAAK,GAAG,CAAC,CAAC;AAEzF,SACEG,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAI;AAAA,MACJ;AAAA,MACA,kCAA8B;AAAA,MAC9B,0BAAwB;AAAA,IAAA;AAAA,EAAA;AAG9B;AAOO,SAAS,cAAc;AAC5B,QAAM,OAAO,QAAA;AACb,QAAM,SAASL,UAAAA,UAAA;AACf,QAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,SACEM,2BAAAA,KAAAC,qBAAA,EACG,UAAA;AAAA,IAAA,QAAQ,IAAI,aAAa,gBAAgBF,2BAAAA,IAAC,eAAA,EAAc;AAAA,IACxD,KAAK,IAAI,CAAC,QACTG,sBAAAA,cAACC,MAAAA,SAAO,GAAG,KAAK,KAAK,YAAY,KAAK,UAAU,GAAG,CAAC,IAAI,OAAc,CACvE;AAAA,EAAA,GACH;AAEJ;AAEA,SAAS,OAAU,KAAe,IAAyB;AACzD,QAAM,2BAAW,IAAA;AACjB,SAAO,IAAI,OAAO,CAAC,SAAS;AAC1B,UAAM,MAAM,GAAG,IAAI;AACnB,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB,aAAO;AAAA,IACT;AACA,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC;AACH;;;"}
|
|
@@ -9,4 +9,4 @@ export declare const useTags: () => RouterManagedTag[];
|
|
|
9
9
|
* Place inside the document head of your app shell.
|
|
10
10
|
* @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management
|
|
11
11
|
*/
|
|
12
|
-
export declare function HeadContent(): import("react/jsx-runtime").JSX.Element
|
|
12
|
+
export declare function HeadContent(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,4 +9,4 @@ export declare const useTags: () => RouterManagedTag[];
|
|
|
9
9
|
* Place inside the document head of your app shell.
|
|
10
10
|
* @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management
|
|
11
11
|
*/
|
|
12
|
-
export declare function HeadContent(): import("react/jsx-runtime").JSX.Element
|
|
12
|
+
export declare function HeadContent(): import("react/jsx-runtime").JSX.Element;
|
package/dist/esm/HeadContent.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
1
2
|
import * as React from "react";
|
|
2
3
|
import { createElement } from "react";
|
|
3
4
|
import { escapeHtml } from "@tanstack/router-core";
|
|
@@ -150,11 +151,33 @@ const useTags = () => {
|
|
|
150
151
|
}
|
|
151
152
|
);
|
|
152
153
|
};
|
|
154
|
+
function DevStylesLink() {
|
|
155
|
+
useRouter();
|
|
156
|
+
const routeIds = useRouterState({
|
|
157
|
+
select: (state) => state.matches.map((match) => match.routeId)
|
|
158
|
+
});
|
|
159
|
+
React.useEffect(() => {
|
|
160
|
+
document.querySelectorAll("[data-tanstack-start-dev-styles]").forEach((el) => el.remove());
|
|
161
|
+
}, []);
|
|
162
|
+
const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
|
|
163
|
+
return /* @__PURE__ */ jsx(
|
|
164
|
+
"link",
|
|
165
|
+
{
|
|
166
|
+
rel: "stylesheet",
|
|
167
|
+
href,
|
|
168
|
+
"data-tanstack-start-dev-styles": true,
|
|
169
|
+
suppressHydrationWarning: true
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
153
173
|
function HeadContent() {
|
|
154
174
|
const tags = useTags();
|
|
155
175
|
const router = useRouter();
|
|
156
176
|
const nonce = router.options.ssr?.nonce;
|
|
157
|
-
return
|
|
177
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
178
|
+
process.env.NODE_ENV !== "production" && /* @__PURE__ */ jsx(DevStylesLink, {}),
|
|
179
|
+
tags.map((tag) => /* @__PURE__ */ createElement(Asset, { ...tag, key: `tsr-meta-${JSON.stringify(tag)}`, nonce }))
|
|
180
|
+
] });
|
|
158
181
|
}
|
|
159
182
|
function uniqBy(arr, fn) {
|
|
160
183
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeadContent.js","sources":["../../src/HeadContent.tsx"],"sourcesContent":["import * as React from 'react'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { Asset } from './Asset'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\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 = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\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 const links = useRouterState({\n select: (state) => {\n const constructed = state.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 = state.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 suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n structuralSharing: true as any,\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.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 preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n structuralSharing: true as any,\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.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 children,\n nonce,\n })),\n structuralSharing: true as any,\n })\n\n const headScripts: Array<RouterManagedTag> = useRouterState({\n select: (state) =>\n (\n state.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 structuralSharing: true as any,\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\n/**\n * Render route-managed head tags (title, meta, links, styles, head scripts).\n * Place inside the document head of your app shell.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management\n */\nexport function HeadContent() {\n const tags = useTags()\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n return tags.map((tag) => (\n <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} nonce={nonce} />\n ))\n}\n\nfunction 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"],"names":["preloadLinks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HeadContent.js","sources":["../../src/HeadContent.tsx"],"sourcesContent":["import * as React from 'react'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { Asset } from './Asset'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\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 = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\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 const links = useRouterState({\n select: (state) => {\n const constructed = state.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 = state.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 suppressHydrationWarning: true,\n nonce,\n },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n structuralSharing: true as any,\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.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 preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n structuralSharing: true as any,\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.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 children,\n nonce,\n })),\n structuralSharing: true as any,\n })\n\n const headScripts: Array<RouterManagedTag> = useRouterState({\n select: (state) =>\n (\n state.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 structuralSharing: true as any,\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\n/**\n * Renders a stylesheet link for dev mode CSS collection.\n * On the server, renders the full link with route-scoped CSS URL.\n * On the client, renders the same link to avoid hydration mismatch,\n * then removes it after hydration since Vite's HMR handles CSS updates.\n */\nfunction DevStylesLink() {\n const router = useRouter()\n\n const routeIds = useRouterState({\n select: (state) => state.matches.map((match) => match.routeId),\n })\n\n React.useEffect(() => {\n // After hydration, remove the SSR-rendered dev styles link\n document\n .querySelectorAll('[data-tanstack-start-dev-styles]')\n .forEach((el) => el.remove())\n }, [])\n\n // Build the same href on both server and client for hydration match\n const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`\n\n return (\n <link\n rel=\"stylesheet\"\n href={href}\n data-tanstack-start-dev-styles\n suppressHydrationWarning\n />\n )\n}\n\n/**\n * Render route-managed head tags (title, meta, links, styles, head scripts).\n * Place inside the document head of your app shell.\n * @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management\n */\nexport function HeadContent() {\n const tags = useTags()\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n return (\n <>\n {process.env.NODE_ENV !== 'production' && <DevStylesLink />}\n {tags.map((tag) => (\n <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} nonce={nonce} />\n ))}\n </>\n )\n}\n\nfunction 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"],"names":["preloadLinks"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/HeadContent.tsx
CHANGED
|
@@ -203,6 +203,39 @@ export const useTags = () => {
|
|
|
203
203
|
)
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Renders a stylesheet link for dev mode CSS collection.
|
|
208
|
+
* On the server, renders the full link with route-scoped CSS URL.
|
|
209
|
+
* On the client, renders the same link to avoid hydration mismatch,
|
|
210
|
+
* then removes it after hydration since Vite's HMR handles CSS updates.
|
|
211
|
+
*/
|
|
212
|
+
function DevStylesLink() {
|
|
213
|
+
const router = useRouter()
|
|
214
|
+
|
|
215
|
+
const routeIds = useRouterState({
|
|
216
|
+
select: (state) => state.matches.map((match) => match.routeId),
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
React.useEffect(() => {
|
|
220
|
+
// After hydration, remove the SSR-rendered dev styles link
|
|
221
|
+
document
|
|
222
|
+
.querySelectorAll('[data-tanstack-start-dev-styles]')
|
|
223
|
+
.forEach((el) => el.remove())
|
|
224
|
+
}, [])
|
|
225
|
+
|
|
226
|
+
// Build the same href on both server and client for hydration match
|
|
227
|
+
const href = `/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(','))}`
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<link
|
|
231
|
+
rel="stylesheet"
|
|
232
|
+
href={href}
|
|
233
|
+
data-tanstack-start-dev-styles
|
|
234
|
+
suppressHydrationWarning
|
|
235
|
+
/>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
206
239
|
/**
|
|
207
240
|
* Render route-managed head tags (title, meta, links, styles, head scripts).
|
|
208
241
|
* Place inside the document head of your app shell.
|
|
@@ -212,9 +245,14 @@ export function HeadContent() {
|
|
|
212
245
|
const tags = useTags()
|
|
213
246
|
const router = useRouter()
|
|
214
247
|
const nonce = router.options.ssr?.nonce
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
return (
|
|
249
|
+
<>
|
|
250
|
+
{process.env.NODE_ENV !== 'production' && <DevStylesLink />}
|
|
251
|
+
{tags.map((tag) => (
|
|
252
|
+
<Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} nonce={nonce} />
|
|
253
|
+
))}
|
|
254
|
+
</>
|
|
255
|
+
)
|
|
218
256
|
}
|
|
219
257
|
|
|
220
258
|
function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
|