PC-mj/.output/server/node_modules/unhead/dist/index.mjs

749 lines
23 KiB
JavaScript
Raw Normal View History

2025-02-20 11:30:25 +00:00
import { DomPlugin } from '@unhead/dom';
import { defineHeadPlugin, tagDedupeKey, hashTag, tagWeight, HasElementTags, NetworkEvents, hashCode, SortModifiers, processTemplateParams, resolveTitleTemplate, IsBrowser, normaliseEntryTags, composableNames, whitelistSafeInput, ScriptNetworkEvents, unpackMeta } from '@unhead/shared';
export { composableNames } from '@unhead/shared';
import { createHooks } from 'hookable';
const UsesMergeStrategy = /* @__PURE__ */ new Set(["templateParams", "htmlAttrs", "bodyAttrs"]);
const DedupePlugin = defineHeadPlugin({
hooks: {
"tag:normalise": ({ tag }) => {
if (tag.props.hid) {
tag.key = tag.props.hid;
delete tag.props.hid;
}
if (tag.props.vmid) {
tag.key = tag.props.vmid;
delete tag.props.vmid;
}
if (tag.props.key) {
tag.key = tag.props.key;
delete tag.props.key;
}
const generatedKey = tagDedupeKey(tag);
if (generatedKey && !generatedKey.startsWith("meta:og:") && !generatedKey.startsWith("meta:twitter:")) {
delete tag.key;
}
const dedupe = generatedKey || (tag.key ? `${tag.tag}:${tag.key}` : false);
if (dedupe)
tag._d = dedupe;
},
"tags:resolve": (ctx) => {
const deduping = /* @__PURE__ */ Object.create(null);
for (const tag of ctx.tags) {
const dedupeKey = (tag.key ? `${tag.tag}:${tag.key}` : tag._d) || hashTag(tag);
const dupedTag = deduping[dedupeKey];
if (dupedTag) {
let strategy = tag?.tagDuplicateStrategy;
if (!strategy && UsesMergeStrategy.has(tag.tag))
strategy = "merge";
if (strategy === "merge") {
const oldProps = dupedTag.props;
if (oldProps.style && tag.props.style) {
if (oldProps.style[oldProps.style.length - 1] !== ";") {
oldProps.style += ";";
}
tag.props.style = `${oldProps.style} ${tag.props.style}`;
}
if (oldProps.class && tag.props.class) {
tag.props.class = `${oldProps.class} ${tag.props.class}`;
} else if (oldProps.class) {
tag.props.class = oldProps.class;
}
deduping[dedupeKey].props = {
...oldProps,
...tag.props
};
continue;
} else if (tag._e === dupedTag._e) {
dupedTag._duped = dupedTag._duped || [];
tag._d = `${dupedTag._d}:${dupedTag._duped.length + 1}`;
dupedTag._duped.push(tag);
continue;
} else if (tagWeight(tag) > tagWeight(dupedTag)) {
continue;
}
}
const hasProps = tag.innerHTML || tag.textContent || Object.keys(tag.props).length !== 0;
if (!hasProps && HasElementTags.has(tag.tag)) {
delete deduping[dedupeKey];
continue;
}
deduping[dedupeKey] = tag;
}
const newTags = [];
for (const key in deduping) {
const tag = deduping[key];
const dupes = tag._duped;
newTags.push(tag);
if (dupes) {
delete tag._duped;
newTags.push(...dupes);
}
}
ctx.tags = newTags;
ctx.tags = ctx.tags.filter((t) => !(t.tag === "meta" && (t.props.name || t.props.property) && !t.props.content));
}
}
});
const ValidEventTags = /* @__PURE__ */ new Set(["script", "link", "bodyAttrs"]);
const EventHandlersPlugin = defineHeadPlugin((head) => ({
hooks: {
"tags:resolve": (ctx) => {
for (const tag of ctx.tags) {
if (!ValidEventTags.has(tag.tag)) {
continue;
}
const props = tag.props;
for (const key in props) {
if (key[0] !== "o" || key[1] !== "n") {
continue;
}
if (!Object.prototype.hasOwnProperty.call(props, key)) {
continue;
}
const value = props[key];
if (typeof value !== "function") {
continue;
}
if (head.ssr && NetworkEvents.has(key)) {
props[key] = `this.dataset.${key}fired = true`;
} else {
delete props[key];
}
tag._eventHandlers = tag._eventHandlers || {};
tag._eventHandlers[key] = value;
}
if (head.ssr && tag._eventHandlers && (tag.props.src || tag.props.href)) {
tag.key = tag.key || hashCode(tag.props.src || tag.props.href);
}
}
},
"dom:renderTag": ({ $el, tag }) => {
const dataset = $el?.dataset;
if (!dataset) {
return;
}
for (const k in dataset) {
if (!k.endsWith("fired")) {
continue;
}
const ek = k.slice(0, -5);
if (!NetworkEvents.has(ek)) {
continue;
}
tag._eventHandlers?.[ek]?.call($el, new Event(ek.substring(2)));
}
}
}
}));
const DupeableTags = /* @__PURE__ */ new Set(["link", "style", "script", "noscript"]);
const HashKeyedPlugin = defineHeadPlugin({
hooks: {
"tag:normalise": ({ tag }) => {
if (tag.key && DupeableTags.has(tag.tag)) {
tag.props["data-hid"] = tag._h = hashCode(tag.key);
}
}
}
});
const PayloadPlugin = defineHeadPlugin({
mode: "server",
hooks: {
"tags:beforeResolve": (ctx) => {
const payload = {};
let hasPayload = false;
for (const tag of ctx.tags) {
if (tag._m !== "server" || tag.tag !== "titleTemplate" && tag.tag !== "templateParams" && tag.tag !== "title") {
continue;
}
payload[tag.tag] = tag.tag === "title" || tag.tag === "titleTemplate" ? tag.textContent : tag.props;
hasPayload = true;
}
if (hasPayload) {
ctx.tags.push({
tag: "script",
innerHTML: JSON.stringify(payload),
props: { id: "unhead:payload", type: "application/json" }
});
}
}
}
});
const SortPlugin = defineHeadPlugin({
hooks: {
"tags:resolve": (ctx) => {
for (const tag of ctx.tags) {
if (typeof tag.tagPriority !== "string") {
continue;
}
for (const { prefix, offset } of SortModifiers) {
if (!tag.tagPriority.startsWith(prefix)) {
continue;
}
const key = tag.tagPriority.substring(prefix.length);
const position = ctx.tags.find((tag2) => tag2._d === key)?._p;
if (position !== void 0) {
tag._p = position + offset;
break;
}
}
}
ctx.tags.sort((a, b) => {
const aWeight = tagWeight(a);
const bWeight = tagWeight(b);
if (aWeight < bWeight) {
return -1;
} else if (aWeight > bWeight) {
return 1;
}
return a._p - b._p;
});
}
}
});
const SupportedAttrs = {
meta: "content",
link: "href",
htmlAttrs: "lang"
};
const contentAttrs = ["innerHTML", "textContent"];
const TemplateParamsPlugin = defineHeadPlugin((head) => ({
hooks: {
"tags:resolve": (ctx) => {
const { tags } = ctx;
let templateParams;
for (let i = 0; i < tags.length; i += 1) {
const tag = tags[i];
if (tag.tag !== "templateParams") {
continue;
}
templateParams = ctx.tags.splice(i, 1)[0].props;
i -= 1;
}
const params = templateParams || {};
const sep = params.separator || "|";
delete params.separator;
params.pageTitle = processTemplateParams(
// find templateParams
params.pageTitle || tags.find((tag) => tag.tag === "title")?.textContent || "",
params,
sep
);
for (const tag of tags) {
if (tag.processTemplateParams === false) {
continue;
}
const v = SupportedAttrs[tag.tag];
if (v && typeof tag.props[v] === "string") {
tag.props[v] = processTemplateParams(tag.props[v], params, sep);
} else if (tag.processTemplateParams || tag.tag === "titleTemplate" || tag.tag === "title") {
for (const p of contentAttrs) {
if (typeof tag[p] === "string")
tag[p] = processTemplateParams(tag[p], params, sep, tag.tag === "script" && tag.props.type.endsWith("json"));
}
}
}
head._templateParams = params;
head._separator = sep;
},
"tags:afterResolve": ({ tags }) => {
let title;
for (let i = 0; i < tags.length; i += 1) {
const tag = tags[i];
if (tag.tag === "title" && tag.processTemplateParams !== false) {
title = tag;
}
}
if (title?.textContent) {
title.textContent = processTemplateParams(title.textContent, head._templateParams, head._separator);
}
}
}
}));
const TitleTemplatePlugin = defineHeadPlugin({
hooks: {
"tags:resolve": (ctx) => {
const { tags } = ctx;
let titleTag;
let titleTemplateTag;
for (let i = 0; i < tags.length; i += 1) {
const tag = tags[i];
if (tag.tag === "title") {
titleTag = tag;
} else if (tag.tag === "titleTemplate") {
titleTemplateTag = tag;
}
}
if (titleTemplateTag && titleTag) {
const newTitle = resolveTitleTemplate(
titleTemplateTag.textContent,
titleTag.textContent
);
if (newTitle !== null) {
titleTag.textContent = newTitle || titleTag.textContent;
} else {
ctx.tags.splice(ctx.tags.indexOf(titleTag), 1);
}
} else if (titleTemplateTag) {
const newTitle = resolveTitleTemplate(
titleTemplateTag.textContent
);
if (newTitle !== null) {
titleTemplateTag.textContent = newTitle;
titleTemplateTag.tag = "title";
titleTemplateTag = void 0;
}
}
if (titleTemplateTag) {
ctx.tags.splice(ctx.tags.indexOf(titleTemplateTag), 1);
}
}
}
});
const XSSPlugin = defineHeadPlugin({
hooks: {
"tags:afterResolve": (ctx) => {
for (const tag of ctx.tags) {
if (typeof tag.innerHTML === "string") {
if (tag.innerHTML && (tag.props.type === "application/ld+json" || tag.props.type === "application/json")) {
tag.innerHTML = tag.innerHTML.replace(/</g, "\\u003C");
} else {
tag.innerHTML = tag.innerHTML.replace(new RegExp(`</${tag.tag}`, "g"), `<\\/${tag.tag}`);
}
}
}
}
}
});
let activeHead;
// @__NO_SIDE_EFFECTS__
function createHead(options = {}) {
const head = createHeadCore(options);
head.use(DomPlugin());
return activeHead = head;
}
// @__NO_SIDE_EFFECTS__
function createServerHead(options = {}) {
return activeHead = createHeadCore(options);
}
function filterMode(mode, ssr) {
return !mode || mode === "server" && ssr || mode === "client" && !ssr;
}
function createHeadCore(options = {}) {
const hooks = createHooks();
hooks.addHooks(options.hooks || {});
options.document = options.document || (IsBrowser ? document : void 0);
const ssr = !options.document;
const updated = () => {
head.dirty = true;
hooks.callHook("entries:updated", head);
};
let entryCount = 0;
let entries = [];
const plugins = [];
const head = {
plugins,
dirty: false,
resolvedOptions: options,
hooks,
headEntries() {
return entries;
},
use(p) {
const plugin = typeof p === "function" ? p(head) : p;
if (!plugin.key || !plugins.some((p2) => p2.key === plugin.key)) {
plugins.push(plugin);
filterMode(plugin.mode, ssr) && hooks.addHooks(plugin.hooks || {});
}
},
push(input, entryOptions) {
delete entryOptions?.head;
const entry = {
_i: entryCount++,
input,
...entryOptions
};
if (filterMode(entry.mode, ssr)) {
entries.push(entry);
updated();
}
return {
dispose() {
entries = entries.filter((e) => e._i !== entry._i);
updated();
},
// a patch is the same as creating a new entry, just a nice DX
patch(input2) {
for (const e of entries) {
if (e._i === entry._i) {
e.input = entry.input = input2;
}
}
updated();
}
};
},
async resolveTags() {
const resolveCtx = { tags: [], entries: [...entries] };
await hooks.callHook("entries:resolve", resolveCtx);
for (const entry of resolveCtx.entries) {
const resolved = entry.resolvedInput || entry.input;
entry.resolvedInput = await (entry.transform ? entry.transform(resolved) : resolved);
if (entry.resolvedInput) {
for (const tag of await normaliseEntryTags(entry)) {
const tagCtx = { tag, entry, resolvedOptions: head.resolvedOptions };
await hooks.callHook("tag:normalise", tagCtx);
resolveCtx.tags.push(tagCtx.tag);
}
}
}
await hooks.callHook("tags:beforeResolve", resolveCtx);
await hooks.callHook("tags:resolve", resolveCtx);
await hooks.callHook("tags:afterResolve", resolveCtx);
return resolveCtx.tags;
},
ssr
};
[
DedupePlugin,
PayloadPlugin,
EventHandlersPlugin,
HashKeyedPlugin,
SortPlugin,
TemplateParamsPlugin,
TitleTemplatePlugin,
XSSPlugin,
...options?.plugins || []
].forEach((p) => head.use(p));
head.hooks.callHook("init", head);
return head;
}
const unheadComposablesImports = [
{
from: "unhead",
imports: composableNames
}
];
function getActiveHead() {
return activeHead;
}
function useHead(input, options = {}) {
const head = options.head || getActiveHead();
return head?.push(input, options);
}
function useHeadSafe(input, options) {
return useHead(input, {
...options,
transform: whitelistSafeInput
});
}
const ScriptProxyTarget = Symbol("ScriptProxyTarget");
function scriptProxy() {
}
scriptProxy[ScriptProxyTarget] = true;
function resolveScriptKey(input) {
return input.key || hashCode(input.src || (typeof input.innerHTML === "string" ? input.innerHTML : ""));
}
function useScript(_input, _options) {
const input = typeof _input === "string" ? { src: _input } : _input;
const options = _options || {};
const head = options.head || getActiveHead();
if (!head)
throw new Error("Missing Unhead context.");
const id = resolveScriptKey(input);
const prevScript = head._scripts?.[id];
if (prevScript) {
prevScript.setupTriggerHandler(options.trigger);
return prevScript;
}
options.beforeInit?.();
const syncStatus = (s) => {
script.status = s;
head.hooks.callHook(`script:updated`, hookCtx);
};
ScriptNetworkEvents.forEach((fn) => {
const _fn = typeof input[fn] === "function" ? input[fn].bind(options.eventContext) : null;
input[fn] = (e) => {
syncStatus(fn === "onload" ? "loaded" : fn === "onerror" ? "error" : "loading");
_fn?.(e);
};
});
const _cbs = { loaded: [], error: [] };
const _registerCb = (key, cb) => {
if (_cbs[key]) {
const i = _cbs[key].push(cb);
return () => _cbs[key]?.splice(i - 1, 1);
}
cb(script.instance);
return () => {
};
};
const loadPromise = new Promise((resolve) => {
if (head.ssr)
return;
const emit = (api) => requestAnimationFrame(() => resolve(api));
const _ = head.hooks.hook("script:updated", ({ script: script2 }) => {
const status = script2.status;
if (script2.id === id && (status === "loaded" || status === "error")) {
if (status === "loaded") {
if (typeof options.use === "function") {
const api = options.use();
if (api) {
emit(api);
}
} else {
emit({});
}
} else if (status === "error") {
resolve(false);
}
_();
}
});
});
const script = Object.assign(loadPromise, {
instance: !head.ssr && options?.use?.() || null,
proxy: null,
id,
status: "awaitingLoad",
remove() {
script._triggerAbortController?.abort();
script._triggerPromises = [];
if (script.entry) {
script.entry.dispose();
script.entry = void 0;
syncStatus("removed");
delete head._scripts?.[id];
return true;
}
return false;
},
load(cb) {
script._triggerAbortController?.abort();
script._triggerPromises = [];
if (!script.entry) {
syncStatus("loading");
const defaults = {
defer: true,
fetchpriority: "low"
};
if (input.src && (input.src.startsWith("http") || input.src.startsWith("//"))) {
defaults.crossorigin = "anonymous";
defaults.referrerpolicy = "no-referrer";
}
script.entry = head.push({
script: [{ ...defaults, ...input, key: `script.${id}` }]
}, options);
}
if (cb)
_registerCb("loaded", cb);
return loadPromise;
},
onLoaded(cb) {
return _registerCb("loaded", cb);
},
onError(cb) {
return _registerCb("error", cb);
},
setupTriggerHandler(trigger) {
if (script.status !== "awaitingLoad") {
return;
}
if ((typeof trigger === "undefined" || trigger === "client") && !head.ssr || trigger === "server") {
script.load();
} else if (trigger instanceof Promise) {
if (head.ssr) {
return;
}
if (!script._triggerAbortController) {
script._triggerAbortController = new AbortController();
script._triggerAbortPromise = new Promise((resolve) => {
script._triggerAbortController.signal.addEventListener("abort", () => {
script._triggerAbortController = null;
resolve();
});
});
}
script._triggerPromises = script._triggerPromises || [];
const idx = script._triggerPromises.push(Promise.race([
trigger.then((v) => typeof v === "undefined" || v ? script.load : void 0),
script._triggerAbortPromise
]).catch(() => {
}).then((res2) => {
res2?.();
}).finally(() => {
script._triggerPromises?.splice(idx, 1);
}));
} else if (typeof trigger === "function") {
trigger(script.load);
}
},
_cbs
});
loadPromise.then((api) => {
if (api !== false) {
script.instance = api;
_cbs.loaded?.forEach((cb) => cb(api));
_cbs.loaded = null;
} else {
_cbs.error?.forEach((cb) => cb());
_cbs.error = null;
}
});
const hookCtx = { script };
script.setupTriggerHandler(options.trigger);
script.$script = script;
const proxyChain = (instance, accessor, accessors) => {
return new Proxy((!accessor ? instance : instance?.[accessor]) || scriptProxy, {
get(_, k, r) {
head.hooks.callHook("script:instance-fn", { script, fn: k, exists: k in _ });
if (!accessor) {
const stub = options.stub?.({ script, fn: k });
if (stub)
return stub;
}
if (_ && k in _ && typeof _[k] !== "undefined") {
return Reflect.get(_, k, r);
}
if (k === Symbol.iterator) {
return [][Symbol.iterator];
}
return proxyChain(accessor ? instance?.[accessor] : instance, k, accessors || [k]);
},
async apply(_, _this, args) {
if (head.ssr && _[ScriptProxyTarget])
return;
let instance2;
const access = (fn2) => {
instance2 = fn2 || instance2;
for (let i = 0; i < (accessors || []).length; i++) {
const k = (accessors || [])[i];
fn2 = fn2?.[k];
}
return fn2;
};
let fn = access(script.instance);
if (!fn) {
fn = await new Promise((resolve) => {
script.onLoaded((api) => {
resolve(access(api));
});
});
}
return typeof fn === "function" ? Reflect.apply(fn, instance2, args) : fn;
}
});
};
script.proxy = proxyChain(script.instance);
const res = new Proxy(script, {
get(_, k) {
const target = k in script || String(k)[0] === "_" ? script : script.proxy;
if (k === "then" || k === "catch") {
return script[k].bind(script);
}
return Reflect.get(target, k, target);
}
});
head._scripts = Object.assign(head._scripts || {}, { [id]: res });
return res;
}
function useSeoMeta(input, options) {
const { title, titleTemplate, ...meta } = input;
return useHead({
title,
titleTemplate,
// we need to input the meta so the reactivity will be resolved
// @ts-expect-error runtime type
_flatMeta: meta
}, {
...options,
transform(t) {
const meta2 = unpackMeta({ ...t._flatMeta });
delete t._flatMeta;
return {
// @ts-expect-error runtime type
...t,
meta: meta2
};
}
});
}
function useServerHead(input, options = {}) {
return useHead(input, { ...options, mode: "server" });
}
function useServerHeadSafe(input, options = {}) {
return useHeadSafe(input, { ...options, mode: "server" });
}
function useServerSeoMeta(input, options) {
return useSeoMeta(input, {
...options,
mode: "server"
});
}
const importRe = /@import/;
// @__NO_SIDE_EFFECTS__
function CapoPlugin(options) {
return defineHeadPlugin({
hooks: {
"tags:beforeResolve": ({ tags }) => {
for (const tag of tags) {
if (tag.tagPosition && tag.tagPosition !== "head")
continue;
tag.tagPriority = tag.tagPriority || tagWeight(tag);
if (tag.tagPriority !== 100)
continue;
const isTruthy = (val) => val === "" || val === true;
const isScript = tag.tag === "script";
const isLink = tag.tag === "link";
if (isScript && isTruthy(tag.props.async)) {
tag.tagPriority = 30;
} else if (tag.tag === "style" && tag.innerHTML && importRe.test(tag.innerHTML)) {
tag.tagPriority = 40;
} else if (isScript && tag.props.src && !isTruthy(tag.props.defer) && !isTruthy(tag.props.async) && tag.props.type !== "module" && !tag.props.type?.endsWith("json")) {
tag.tagPriority = 50;
} else if (isLink && tag.props.rel === "stylesheet" || tag.tag === "style") {
tag.tagPriority = 60;
} else if (isLink && (tag.props.rel === "preload" || tag.props.rel === "modulepreload")) {
tag.tagPriority = 70;
} else if (isScript && isTruthy(tag.props.defer) && tag.props.src && !isTruthy(tag.props.async)) {
tag.tagPriority = 80;
} else if (isLink && (tag.props.rel === "prefetch" || tag.props.rel === "dns-prefetch" || tag.props.rel === "prerender")) {
tag.tagPriority = 90;
}
}
options?.track && tags.push({
tag: "htmlAttrs",
props: {
"data-capo": ""
}
});
}
}
});
}
// @__NO_SIDE_EFFECTS__
function HashHydrationPlugin() {
return defineHeadPlugin({});
}
export { CapoPlugin, HashHydrationPlugin, createHead, createHeadCore, createServerHead, getActiveHead, resolveScriptKey, unheadComposablesImports, useHead, useHeadSafe, useScript, useSeoMeta, useServerHead, useServerHeadSafe, useServerSeoMeta };