@tanstack/vue-router
Version:
Modern and scalable routing for Vue applications
108 lines (107 loc) • 3.94 kB
JavaScript
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