UNPKG

@tanstack/vue-router

Version:

Modern and scalable routing for Vue applications

108 lines (107 loc) 3.94 kB
import { useRouter } from "./useRouter.js"; import { Asset } from "./Asset.js"; import { useTags } from "./headContentUtils.js"; import * as Vue from "vue"; import { isServer } from "@tanstack/router-core/isServer"; //#region src/HeadContent.tsx function attrsMatch(attrs, element) { let expectedAttrCount = 0; for (const name in attrs) { const value = attrs[name]; if (value === void 0 || value === false || value === null) continue; expectedAttrCount++; const attrName = name.toLowerCase(); if (value === true) { if (!element.hasAttribute(attrName)) return false; continue; } if (element.getAttribute(attrName) !== String(value)) return false; } return expectedAttrCount === element.attributes.length; } function reconcileHydratedHead(tags, preservedHeadTagElements) { if (typeof document === "undefined") return; const matchedHeadElements = /* @__PURE__ */ new Set(); const hydratedLinks = document.head.querySelectorAll("link"); for (const tag of tags) { if (tag.tag !== "link") continue; const attrs = tag.attrs; const rel = typeof attrs?.rel === "string" ? attrs.rel.toLowerCase() : void 0; if (rel !== "stylesheet" && rel !== "preload" && rel !== "modulepreload") continue; for (const element of hydratedLinks) if (!matchedHeadElements.has(element) && attrsMatch(attrs, element)) { matchedHeadElements.add(element); const key = JSON.stringify(tag); (preservedHeadTagElements[key] ||= []).push(element); break; } } } function cleanupInactivePreservedHeadElements(preservedHeadTagElements, activeElements) { for (const key in preservedHeadTagElements) { const elements = preservedHeadTagElements[key]; let nextElements; for (const element of elements) if (activeElements.has(element)) (nextElements ||= []).push(element); else if (!shouldRemoveInactivePreservedHeadElement(element)) (nextElements ||= []).push(element); else element.remove(); if (nextElements) preservedHeadTagElements[key] = nextElements; else delete preservedHeadTagElements[key]; } } function shouldRemoveInactivePreservedHeadElement(element) { const rel = element.getAttribute("rel")?.toLowerCase(); return rel === "preload" || rel === "modulepreload"; } /** * @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route. * It should be rendered in the `<head>` of your document. */ var HeadContent = Vue.defineComponent({ name: "HeadContent", props: { assetCrossOrigin: { type: [String, Object], default: void 0 } }, setup(props) { const router = useRouter(); const tags = useTags(props.assetCrossOrigin); if (isServer ?? router.isServer) return () => { return tags().map((tag) => { const key = JSON.stringify(tag); return Vue.h(Asset, { ...tag, key: `tsr-meta-${key}` }); }); }; const preservedHeadTagElements = {}; let activePreservedHeadElements = /* @__PURE__ */ new Set(); if (router.ssr) reconcileHydratedHead(tags(), preservedHeadTagElements); Vue.onUpdated(() => { cleanupInactivePreservedHeadElements(preservedHeadTagElements, activePreservedHeadElements); }); Vue.onUnmounted(() => { cleanupInactivePreservedHeadElements(preservedHeadTagElements, /* @__PURE__ */ new Set()); }); return () => { const renderedPreservedHeadTagKeys = {}; activePreservedHeadElements = /* @__PURE__ */ new Set(); return tags().map((tag) => { const key = JSON.stringify(tag); const renderedCount = renderedPreservedHeadTagKeys[key] || 0; const preservedElement = preservedHeadTagElements[key]?.[renderedCount]; if (preservedElement?.isConnected) { renderedPreservedHeadTagKeys[key] = renderedCount + 1; activePreservedHeadElements.add(preservedElement); return null; } return Vue.h(Asset, { ...tag, key: `tsr-meta-${key}` }); }); }; } }); //#endregion export { HeadContent }; //# sourceMappingURL=HeadContent.js.map