720 lines
22 KiB
JavaScript
720 lines
22 KiB
JavaScript
import { createHooks } from 'hookable';
|
|
import { hashCode } from '@unhead/dom';
|
|
|
|
const ValidHeadTags = [
|
|
"title",
|
|
"titleTemplate",
|
|
"base",
|
|
"htmlAttrs",
|
|
"bodyAttrs",
|
|
"meta",
|
|
"link",
|
|
"style",
|
|
"script",
|
|
"noscript"
|
|
];
|
|
const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy"];
|
|
|
|
async function normaliseTag(tagName, input) {
|
|
const tag = { tag: tagName, props: {} };
|
|
if (tagName === "title" || tagName === "titleTemplate") {
|
|
tag.children = input instanceof Promise ? await input : input;
|
|
return tag;
|
|
}
|
|
tag.props = await normaliseProps({ ...input });
|
|
["children", "innerHtml", "innerHTML"].forEach((key) => {
|
|
if (typeof tag.props[key] !== "undefined") {
|
|
tag.children = tag.props[key];
|
|
if (typeof tag.children === "object")
|
|
tag.children = JSON.stringify(tag.children);
|
|
delete tag.props[key];
|
|
}
|
|
});
|
|
Object.keys(tag.props).filter((k) => TagConfigKeys.includes(k)).forEach((k) => {
|
|
tag[k] = tag.props[k];
|
|
delete tag.props[k];
|
|
});
|
|
if (typeof tag.props.class === "object" && !Array.isArray(tag.props.class)) {
|
|
tag.props.class = Object.keys(tag.props.class).filter((k) => tag.props.class[k]);
|
|
}
|
|
if (Array.isArray(tag.props.class))
|
|
tag.props.class = tag.props.class.join(" ");
|
|
if (tag.props.content && Array.isArray(tag.props.content)) {
|
|
return tag.props.content.map((v, i) => {
|
|
const newTag = { ...tag, props: { ...tag.props } };
|
|
newTag.props.content = v;
|
|
newTag.key = `${tag.props.name || tag.props.property}:${i}`;
|
|
return newTag;
|
|
});
|
|
}
|
|
return tag;
|
|
}
|
|
async function normaliseProps(props) {
|
|
for (const k of Object.keys(props)) {
|
|
if (props[k] instanceof Promise) {
|
|
props[k] = await props[k];
|
|
}
|
|
if (String(props[k]) === "true") {
|
|
props[k] = "";
|
|
} else if (String(props[k]) === "false") {
|
|
delete props[k];
|
|
}
|
|
}
|
|
return props;
|
|
}
|
|
|
|
function unpackToArray(input, options) {
|
|
const unpacked = [];
|
|
const kFn = options.resolveKeyData || ((ctx) => ctx.key);
|
|
const vFn = options.resolveValueData || ((ctx) => ctx.value);
|
|
for (const [k, v] of Object.entries(input)) {
|
|
unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
|
|
const ctx = { key: k, value: i };
|
|
const val = vFn(ctx);
|
|
if (typeof val === "object")
|
|
return unpackToArray(val, options);
|
|
if (Array.isArray(val))
|
|
return val;
|
|
return {
|
|
[typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
|
|
[typeof options.value === "function" ? options.value(ctx) : options.value]: val
|
|
};
|
|
}).flat());
|
|
}
|
|
return unpacked;
|
|
}
|
|
|
|
function unpackToString(value, options) {
|
|
return Object.entries(value).map(([key, value2]) => {
|
|
if (typeof value2 === "object")
|
|
value2 = unpackToString(value2, options);
|
|
if (options.resolve) {
|
|
const resolved = options.resolve({ key, value: value2 });
|
|
if (resolved)
|
|
return resolved;
|
|
}
|
|
if (typeof value2 === "number")
|
|
value2 = value2.toString();
|
|
if (typeof value2 === "string" && options.wrapValue) {
|
|
value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
|
|
value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
|
|
}
|
|
return `${key}${options.keyValueSeparator || ""}${value2}`;
|
|
}).join(options.entrySeparator || "");
|
|
}
|
|
|
|
const MetaPackingSchema = {
|
|
robots: {
|
|
unpack: {
|
|
keyValueSeparator: ":"
|
|
}
|
|
},
|
|
contentSecurityPolicy: {
|
|
unpack: {
|
|
keyValueSeparator: " ",
|
|
entrySeparator: "; "
|
|
},
|
|
metaKey: "http-equiv"
|
|
},
|
|
fbAppId: {
|
|
keyValue: "fb:app_id",
|
|
metaKey: "property"
|
|
},
|
|
msapplicationTileImage: {
|
|
keyValue: "msapplication-TileImage"
|
|
},
|
|
msapplicationTileColor: {
|
|
keyValue: "msapplication-TileColor"
|
|
},
|
|
msapplicationConfig: {
|
|
keyValue: "msapplication-Config"
|
|
},
|
|
charset: {
|
|
metaKey: "charset"
|
|
},
|
|
contentType: {
|
|
metaKey: "http-equiv"
|
|
},
|
|
defaultStyle: {
|
|
metaKey: "http-equiv"
|
|
},
|
|
xUaCompatible: {
|
|
metaKey: "http-equiv"
|
|
},
|
|
refresh: {
|
|
metaKey: "http-equiv"
|
|
}
|
|
};
|
|
function resolveMetaKeyType(key) {
|
|
return PropertyPrefixKeys.test(key) ? "property" : MetaPackingSchema[key]?.metaKey || "name";
|
|
}
|
|
|
|
function unpackMeta(input) {
|
|
const meta = unpackToArray(input, {
|
|
key({ key }) {
|
|
return resolveMetaKeyType(key);
|
|
},
|
|
value({ key }) {
|
|
return key === "charset" ? "charset" : "content";
|
|
},
|
|
resolveKeyData({ key }) {
|
|
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
|
|
},
|
|
resolveValueData({ value, key }) {
|
|
if (value === null)
|
|
return "_null";
|
|
if (typeof value === "object") {
|
|
const definition = MetaPackingSchema[key];
|
|
if (key === "refresh")
|
|
return `${value.seconds};url=${value.url}`;
|
|
return unpackToString(
|
|
changeKeyCasingDeep(value),
|
|
{
|
|
entrySeparator: ", ",
|
|
keyValueSeparator: "=",
|
|
resolve({ value: value2, key: key2 }) {
|
|
if (value2 === null)
|
|
return "";
|
|
if (typeof value2 === "boolean")
|
|
return `${key2}`;
|
|
},
|
|
...definition?.unpack
|
|
}
|
|
);
|
|
}
|
|
return typeof value === "number" ? value.toString() : value;
|
|
}
|
|
});
|
|
return meta.filter((v) => typeof v.content === "undefined" || v.content !== "_null");
|
|
}
|
|
|
|
const PropertyPrefixKeys = /^(og|twitter|fb)/;
|
|
function fixKeyCase(key) {
|
|
key = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
if (PropertyPrefixKeys.test(key)) {
|
|
key = key.replace("secure-url", "secure_url").replace(/-/g, ":");
|
|
}
|
|
return key;
|
|
}
|
|
function changeKeyCasingDeep(input) {
|
|
if (Array.isArray(input)) {
|
|
return input.map((entry) => changeKeyCasingDeep(entry));
|
|
}
|
|
if (typeof input !== "object" || Array.isArray(input))
|
|
return input;
|
|
const output = {};
|
|
for (const [key, value] of Object.entries(input))
|
|
output[fixKeyCase(key)] = changeKeyCasingDeep(value);
|
|
return output;
|
|
}
|
|
|
|
const tagWeight = (tag) => {
|
|
if (typeof tag.tagPriority === "number")
|
|
return tag.tagPriority;
|
|
switch (tag.tagPriority) {
|
|
case "critical":
|
|
return 2;
|
|
case "high":
|
|
return 9;
|
|
case "low":
|
|
return 12;
|
|
}
|
|
switch (tag.tag) {
|
|
case "base":
|
|
return -1;
|
|
case "title":
|
|
return 1;
|
|
case "meta":
|
|
if (tag.props.charset)
|
|
return -2;
|
|
if (tag.props["http-equiv"] === "content-security-policy")
|
|
return 0;
|
|
return 10;
|
|
default:
|
|
return 10;
|
|
}
|
|
};
|
|
const sortTags = (aTag, bTag) => {
|
|
return tagWeight(aTag) - tagWeight(bTag);
|
|
};
|
|
|
|
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs"];
|
|
function tagDedupeKey(tag, fn) {
|
|
const { props, tag: tagName } = tag;
|
|
if (UniqueTags.includes(tagName))
|
|
return tagName;
|
|
if (tagName === "link" && props.rel === "canonical")
|
|
return "canonical";
|
|
if (props.charset)
|
|
return "charset";
|
|
const name = ["id"];
|
|
if (tagName === "meta")
|
|
name.push(...["name", "property", "http-equiv"]);
|
|
for (const n of name) {
|
|
if (typeof props[n] !== "undefined") {
|
|
const val = String(props[n]);
|
|
if (fn && !fn(val))
|
|
return false;
|
|
return `${tagName}:${n}:${val}`;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const renderTitleTemplate = (template, title) => {
|
|
if (template == null)
|
|
return title || null;
|
|
if (typeof template === "function")
|
|
return template(title);
|
|
return template.replace("%s", title ?? "");
|
|
};
|
|
function resolveTitleTemplateFromTags(tags) {
|
|
let titleTemplateIdx = tags.findIndex((i) => i.tag === "titleTemplate");
|
|
const titleIdx = tags.findIndex((i) => i.tag === "title");
|
|
if (titleIdx !== -1 && titleTemplateIdx !== -1) {
|
|
const newTitle = renderTitleTemplate(
|
|
tags[titleTemplateIdx].children,
|
|
tags[titleIdx].children
|
|
);
|
|
if (newTitle !== null) {
|
|
tags[titleIdx].children = newTitle || tags[titleIdx].children;
|
|
} else {
|
|
delete tags[titleIdx];
|
|
}
|
|
} else if (titleTemplateIdx !== -1) {
|
|
const newTitle = renderTitleTemplate(
|
|
tags[titleTemplateIdx].children
|
|
);
|
|
if (newTitle !== null) {
|
|
tags[titleTemplateIdx].children = newTitle;
|
|
tags[titleTemplateIdx].tag = "title";
|
|
titleTemplateIdx = -1;
|
|
}
|
|
}
|
|
if (titleTemplateIdx !== -1) {
|
|
delete tags[titleTemplateIdx];
|
|
}
|
|
return tags.filter(Boolean);
|
|
}
|
|
|
|
const DedupesTagsPlugin = (options) => {
|
|
options = options || {};
|
|
const dedupeKeys = options.dedupeKeys || ["hid", "vmid", "key"];
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"tag:normalise": function({ tag }) {
|
|
dedupeKeys.forEach((key) => {
|
|
if (tag.props[key]) {
|
|
tag.key = tag.props[key];
|
|
delete tag.props[key];
|
|
}
|
|
});
|
|
const dedupe = tag.key ? `${tag.tag}:${tag.key}` : tagDedupeKey(tag);
|
|
if (dedupe)
|
|
tag._d = dedupe;
|
|
},
|
|
"tags:resolve": function(ctx) {
|
|
const deduping = {};
|
|
ctx.tags.forEach((tag) => {
|
|
let dedupeKey = tag._d || tag._p;
|
|
const dupedTag = deduping[dedupeKey];
|
|
if (dupedTag) {
|
|
let strategy = tag?.tagDuplicateStrategy;
|
|
if (!strategy && (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"))
|
|
strategy = "merge";
|
|
if (strategy === "merge") {
|
|
const oldProps = dupedTag.props;
|
|
["class", "style"].forEach((key) => {
|
|
if (tag.props[key] && oldProps[key]) {
|
|
if (key === "style" && !oldProps[key].endsWith(";"))
|
|
oldProps[key] += ";";
|
|
tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
|
|
}
|
|
});
|
|
deduping[dedupeKey].props = {
|
|
...oldProps,
|
|
...tag.props
|
|
};
|
|
return;
|
|
} else if (tag._e === dupedTag._e) {
|
|
dedupeKey = tag._d = `${dedupeKey}:${tag._p}`;
|
|
}
|
|
const propCount = Object.keys(tag.props).length;
|
|
if ((propCount === 0 || propCount === 1 && typeof tag.props["data-h-key"] !== "undefined") && !tag.children) {
|
|
delete deduping[dedupeKey];
|
|
return;
|
|
}
|
|
}
|
|
deduping[dedupeKey] = tag;
|
|
});
|
|
ctx.tags = Object.values(deduping);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const SortTagsPlugin = () => {
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"tags:resolve": (ctx) => {
|
|
const tagIndexForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
|
|
for (const tag of ctx.tags) {
|
|
if (!tag.tagPriority || typeof tag.tagPriority === "number")
|
|
continue;
|
|
const modifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
|
|
for (const { prefix, offset } of modifiers) {
|
|
if (tag.tagPriority.startsWith(prefix)) {
|
|
const key = tag.tagPriority.replace(prefix, "");
|
|
const index = tagIndexForKey(key);
|
|
if (typeof index !== "undefined")
|
|
tag._p = index + offset;
|
|
}
|
|
}
|
|
}
|
|
ctx.tags.sort((a, b) => a._p - b._p).sort(sortTags);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const TitleTemplatePlugin = () => {
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"tags:resolve": (ctx) => {
|
|
ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const DeprecatedTagAttrPlugin = () => {
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"tag:normalise": function({ tag }) {
|
|
if (typeof tag.props.body !== "undefined") {
|
|
tag.tagPosition = "bodyClose";
|
|
delete tag.props.body;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const IsBrowser = typeof window !== "undefined";
|
|
|
|
const ProvideTagHashPlugin = () => {
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"tag:normalise": (ctx) => {
|
|
const { tag, entry } = ctx;
|
|
const isDynamic = typeof tag.props._dynamic !== "undefined";
|
|
if (!HasElementTags.includes(tag.tag) || !tag.key)
|
|
return;
|
|
tag._hash = hashCode(JSON.stringify({ tag: tag.tag, key: tag.key }));
|
|
if (IsBrowser || getActiveHead()?.resolvedOptions?.document)
|
|
return;
|
|
if (entry._m === "server" || isDynamic) {
|
|
tag.props[`data-h-${tag._hash}`] = "";
|
|
}
|
|
},
|
|
"tags:resolve": (ctx) => {
|
|
ctx.tags = ctx.tags.map((t) => {
|
|
delete t.props._dynamic;
|
|
return t;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const PatchDomOnEntryUpdatesPlugin = (options) => {
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"entries:updated": function(head) {
|
|
if (typeof options?.document === "undefined" && typeof window === "undefined")
|
|
return;
|
|
let delayFn = options?.delayFn;
|
|
if (!delayFn && typeof requestAnimationFrame !== "undefined")
|
|
delayFn = requestAnimationFrame;
|
|
import('@unhead/dom').then(({ debouncedRenderDOMHead }) => {
|
|
debouncedRenderDOMHead(head, { document: options?.document || window.document, delayFn });
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
const EventHandlersPlugin = () => {
|
|
const stripEventHandlers = (mode, tag) => {
|
|
const props = {};
|
|
const eventHandlers = {};
|
|
Object.entries(tag.props).forEach(([key, value]) => {
|
|
if (key.startsWith("on") && typeof value === "function")
|
|
eventHandlers[key] = value;
|
|
else
|
|
props[key] = value;
|
|
});
|
|
let delayedSrc;
|
|
if (mode === "dom" && tag.tag === "script" && typeof props.src === "string" && typeof eventHandlers.onload !== "undefined") {
|
|
delayedSrc = props.src;
|
|
delete props.src;
|
|
}
|
|
return { props, eventHandlers, delayedSrc };
|
|
};
|
|
return defineHeadPlugin({
|
|
hooks: {
|
|
"ssr:render": function(ctx) {
|
|
ctx.tags = ctx.tags.map((tag) => {
|
|
tag.props = stripEventHandlers("ssr", tag).props;
|
|
return tag;
|
|
});
|
|
},
|
|
"dom:beforeRenderTag": function(ctx) {
|
|
const { props, eventHandlers, delayedSrc } = stripEventHandlers("dom", ctx.tag);
|
|
if (!Object.keys(eventHandlers).length)
|
|
return;
|
|
ctx.tag.props = props;
|
|
ctx.tag._eventHandlers = eventHandlers;
|
|
ctx.tag._delayedSrc = delayedSrc;
|
|
},
|
|
"dom:renderTag": function(ctx) {
|
|
const $el = ctx.$el;
|
|
if (!ctx.tag._eventHandlers || !$el)
|
|
return;
|
|
const $eventListenerTarget = ctx.tag.tag === "bodyAttrs" && typeof window !== "undefined" ? window : $el;
|
|
Object.entries(ctx.tag._eventHandlers).forEach(([k, value]) => {
|
|
const sdeKey = `${ctx.tag._d || ctx.tag._p}:${k}`;
|
|
const eventName = k.slice(2).toLowerCase();
|
|
const eventDedupeKey = `data-h-${eventName}`;
|
|
delete ctx.staleSideEffects[sdeKey];
|
|
if ($el.hasAttribute(eventDedupeKey))
|
|
return;
|
|
const handler = value;
|
|
$el.setAttribute(eventDedupeKey, "");
|
|
$eventListenerTarget.addEventListener(eventName, handler);
|
|
if (ctx.entry) {
|
|
ctx.entry._sde[sdeKey] = () => {
|
|
$eventListenerTarget.removeEventListener(eventName, handler);
|
|
$el.removeAttribute(eventDedupeKey);
|
|
};
|
|
}
|
|
});
|
|
if (ctx.tag._delayedSrc) {
|
|
$el.setAttribute("src", ctx.tag._delayedSrc);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
function asArray(value) {
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
const HasElementTags = [
|
|
"base",
|
|
"meta",
|
|
"link",
|
|
"style",
|
|
"script",
|
|
"noscript"
|
|
];
|
|
|
|
let activeHead;
|
|
const setActiveHead = (head) => activeHead = head;
|
|
const getActiveHead = () => activeHead;
|
|
|
|
function useHead(input, options = {}) {
|
|
const head = getActiveHead();
|
|
if (head) {
|
|
const isBrowser = IsBrowser || head.resolvedOptions?.document;
|
|
if (options.mode === "server" && isBrowser || options.mode === "client" && !isBrowser)
|
|
return;
|
|
return head.push(input, options);
|
|
}
|
|
}
|
|
const useTagTitle = (title) => useHead({ title });
|
|
const useTagBase = (base) => useHead({ base });
|
|
const useTagMeta = (meta) => useHead({ meta: asArray(meta) });
|
|
const useTagMetaFlat = (meta) => useTagMeta(unpackMeta(meta));
|
|
const useSeoMeta = useTagMetaFlat;
|
|
const useTagLink = (link) => useHead({ link: asArray(link) });
|
|
const useTagScript = (script) => useHead({ script: asArray(script) });
|
|
const useTagStyle = (style) => useHead({ style: asArray(style) });
|
|
const useTagNoscript = (noscript) => useHead({ noscript: asArray(noscript) });
|
|
const useHtmlAttrs = (attrs) => useHead({ htmlAttrs: attrs });
|
|
const useBodyAttrs = (attrs) => useHead({ bodyAttrs: attrs });
|
|
const useTitleTemplate = (titleTemplate) => useHead({ titleTemplate });
|
|
|
|
function useServerHead(input, options = {}) {
|
|
return useHead(input, { ...options, mode: "server" });
|
|
}
|
|
const useServerTagTitle = (title) => useServerHead({ title });
|
|
const useServerTagBase = (base) => useServerHead({ base });
|
|
const useServerTagMeta = (meta) => useServerHead({ meta: asArray(meta) });
|
|
const useServerTagMetaFlat = (meta) => useServerTagMeta(unpackMeta(meta));
|
|
const useServerTagLink = (link) => useServerHead({ link: asArray(link) });
|
|
const useServerTagScript = (script) => useServerHead({ script: asArray(script) });
|
|
const useServerTagStyle = (style) => useServerHead({ style: asArray(style) });
|
|
const useServerTagNoscript = (noscript) => useServerHead({ noscript: asArray(noscript) });
|
|
const useServerHtmlAttrs = (attrs) => useServerHead({ htmlAttrs: attrs });
|
|
const useServerBodyAttrs = (attrs) => useServerHead({ bodyAttrs: attrs });
|
|
const useServerTitleTemplate = (titleTemplate) => useServerHead({ titleTemplate });
|
|
|
|
const TagEntityBits = 10;
|
|
|
|
async function normaliseEntryTags(e) {
|
|
const tagPromises = [];
|
|
Object.entries(e.resolvedInput || e.input).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
|
|
const v = asArray(value);
|
|
tagPromises.push(...v.map((props) => normaliseTag(k, props)).flat());
|
|
});
|
|
return (await Promise.all(tagPromises)).flat().map((t, i) => {
|
|
t._e = e._i;
|
|
t._p = (e._i << TagEntityBits) + i;
|
|
return t;
|
|
});
|
|
}
|
|
|
|
const CorePlugins = () => [
|
|
DedupesTagsPlugin(),
|
|
SortTagsPlugin(),
|
|
TitleTemplatePlugin(),
|
|
ProvideTagHashPlugin(),
|
|
EventHandlersPlugin(),
|
|
DeprecatedTagAttrPlugin()
|
|
];
|
|
const DOMPlugins = (options = {}) => [
|
|
PatchDomOnEntryUpdatesPlugin({ document: options?.document, delayFn: options?.domDelayFn })
|
|
];
|
|
function createHead(options = {}) {
|
|
const head = createHeadCore({
|
|
...options,
|
|
plugins: [...DOMPlugins(options), ...options?.plugins || []]
|
|
});
|
|
setActiveHead(head);
|
|
return head;
|
|
}
|
|
function createHeadCore(options = {}) {
|
|
let entries = [];
|
|
let _sde = {};
|
|
let _eid = 0;
|
|
const hooks = createHooks();
|
|
if (options?.hooks)
|
|
hooks.addHooks(options.hooks);
|
|
options.plugins = [
|
|
...CorePlugins(),
|
|
...options?.plugins || []
|
|
];
|
|
options.plugins.forEach((p) => p.hooks && hooks.addHooks(p.hooks));
|
|
const updated = () => hooks.callHook("entries:updated", head);
|
|
const head = {
|
|
resolvedOptions: options,
|
|
headEntries() {
|
|
return entries;
|
|
},
|
|
get hooks() {
|
|
return hooks;
|
|
},
|
|
use(plugin) {
|
|
if (plugin.hooks)
|
|
hooks.addHooks(plugin.hooks);
|
|
},
|
|
push(input, options2) {
|
|
const activeEntry = {
|
|
_i: _eid++,
|
|
input,
|
|
_sde: {}
|
|
};
|
|
if (options2?.mode)
|
|
activeEntry._m = options2?.mode;
|
|
entries.push(activeEntry);
|
|
updated();
|
|
return {
|
|
dispose() {
|
|
entries = entries.filter((e) => {
|
|
if (e._i !== activeEntry._i)
|
|
return true;
|
|
_sde = { ..._sde, ...e._sde || {} };
|
|
e._sde = {};
|
|
updated();
|
|
return false;
|
|
});
|
|
},
|
|
patch(input2) {
|
|
entries = entries.map((e) => {
|
|
if (e._i === activeEntry._i) {
|
|
activeEntry.input = e.input = input2;
|
|
updated();
|
|
}
|
|
return e;
|
|
});
|
|
}
|
|
};
|
|
},
|
|
async resolveTags() {
|
|
const resolveCtx = { tags: [], entries: [...entries] };
|
|
await hooks.callHook("entries:resolve", resolveCtx);
|
|
for (const entry of resolveCtx.entries) {
|
|
for (const tag of await normaliseEntryTags(entry)) {
|
|
const tagCtx = { tag, entry };
|
|
await hooks.callHook("tag:normalise", tagCtx);
|
|
resolveCtx.tags.push(tagCtx.tag);
|
|
}
|
|
}
|
|
await hooks.callHook("tags:resolve", resolveCtx);
|
|
return resolveCtx.tags;
|
|
},
|
|
_elMap: {},
|
|
_popSideEffectQueue() {
|
|
const sde = { ..._sde };
|
|
_sde = {};
|
|
return sde;
|
|
}
|
|
};
|
|
head.hooks.callHook("init", head);
|
|
return head;
|
|
}
|
|
|
|
function defineHeadPlugin(plugin) {
|
|
return plugin;
|
|
}
|
|
|
|
const coreComposableNames = [
|
|
"getActiveHead"
|
|
];
|
|
const composableNames = [
|
|
"useHead",
|
|
"useTagTitle",
|
|
"useTagBase",
|
|
"useTagMeta",
|
|
"useTagMetaFlat",
|
|
"useSeoMeta",
|
|
"useTagLink",
|
|
"useTagScript",
|
|
"useTagStyle",
|
|
"useTagNoscript",
|
|
"useHtmlAttrs",
|
|
"useBodyAttrs",
|
|
"useTitleTemplate",
|
|
"useServerHead",
|
|
"useServerTagTitle",
|
|
"useServerTagBase",
|
|
"useServerTagMeta",
|
|
"useServerTagMetaFlat",
|
|
"useServerTagLink",
|
|
"useServerTagScript",
|
|
"useServerTagStyle",
|
|
"useServerTagNoscript",
|
|
"useServerHtmlAttrs",
|
|
"useServerBodyAttrs",
|
|
"useServerTitleTemplate"
|
|
];
|
|
const unheadComposablesImports = [
|
|
{
|
|
from: "unhead",
|
|
imports: [...coreComposableNames, ...composableNames]
|
|
}
|
|
];
|
|
|
|
export { CorePlugins, DOMPlugins, DedupesTagsPlugin, DeprecatedTagAttrPlugin, EventHandlersPlugin, HasElementTags, PatchDomOnEntryUpdatesPlugin, ProvideTagHashPlugin, SortTagsPlugin, TitleTemplatePlugin, activeHead, asArray, composableNames, createHead, createHeadCore, defineHeadPlugin, getActiveHead, normaliseEntryTags, setActiveHead, unheadComposablesImports, useBodyAttrs, useHead, useHtmlAttrs, useSeoMeta, useServerBodyAttrs, useServerHead, useServerHtmlAttrs, useServerTagBase, useServerTagLink, useServerTagMeta, useServerTagMetaFlat, useServerTagNoscript, useServerTagScript, useServerTagStyle, useServerTagTitle, useServerTitleTemplate, useTagBase, useTagLink, useTagMeta, useTagMetaFlat, useTagNoscript, useTagScript, useTagStyle, useTagTitle, useTitleTemplate };
|