171 lines
6.4 KiB
JavaScript
171 lines
6.4 KiB
JavaScript
|
import { createHooks } from 'hookable';
|
||
|
import { n as normalizeEntryToTags, d as dedupeKey, i as isMetaArrayDupeKey } from './unhead.BaPU1zLf.mjs';
|
||
|
import { t as tagWeight, s as sortTags } from './unhead.DZbvapt-.mjs';
|
||
|
import { c as UsesMergeStrategy, V as ValidHeadTags } from './unhead.yem5I2v_.mjs';
|
||
|
|
||
|
function registerPlugin(head, p) {
|
||
|
const plugin = typeof p === "function" ? p(head) : p;
|
||
|
const key = plugin.key || String(head.plugins.size + 1);
|
||
|
const exists = head.plugins.get(key);
|
||
|
if (!exists) {
|
||
|
head.plugins.set(key, plugin);
|
||
|
head.hooks.addHooks(plugin.hooks || {});
|
||
|
}
|
||
|
}
|
||
|
function createHeadCore(resolvedOptions = {}) {
|
||
|
return createUnhead(resolvedOptions);
|
||
|
}
|
||
|
function createUnhead(resolvedOptions = {}) {
|
||
|
const hooks = createHooks();
|
||
|
hooks.addHooks(resolvedOptions.hooks || {});
|
||
|
const ssr = !resolvedOptions.document;
|
||
|
const entries = /* @__PURE__ */ new Map();
|
||
|
const plugins = /* @__PURE__ */ new Map();
|
||
|
const normalizeQueue = [];
|
||
|
const head = {
|
||
|
_entryCount: 1,
|
||
|
// 0 is reserved for internal use
|
||
|
plugins,
|
||
|
dirty: false,
|
||
|
resolvedOptions,
|
||
|
hooks,
|
||
|
ssr,
|
||
|
entries,
|
||
|
headEntries() {
|
||
|
return [...entries.values()];
|
||
|
},
|
||
|
use: (p) => registerPlugin(head, p),
|
||
|
push(input, _options) {
|
||
|
const options = { ..._options || {} };
|
||
|
delete options.head;
|
||
|
const _i = options._index ?? head._entryCount++;
|
||
|
const inst = { _i, input, options };
|
||
|
const _ = {
|
||
|
_poll(rm = false) {
|
||
|
head.dirty = true;
|
||
|
!rm && normalizeQueue.push(_i);
|
||
|
hooks.callHook("entries:updated", head);
|
||
|
},
|
||
|
dispose() {
|
||
|
if (entries.delete(_i)) {
|
||
|
_._poll(true);
|
||
|
}
|
||
|
},
|
||
|
// a patch is the same as creating a new entry, just a nice DX
|
||
|
patch(input2) {
|
||
|
if (!options.mode || options.mode === "server" && ssr || options.mode === "client" && !ssr) {
|
||
|
inst.input = input2;
|
||
|
entries.set(_i, inst);
|
||
|
_._poll();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
_.patch(input);
|
||
|
return _;
|
||
|
},
|
||
|
async resolveTags() {
|
||
|
const ctx = {
|
||
|
tagMap: /* @__PURE__ */ new Map(),
|
||
|
tags: [],
|
||
|
entries: [...head.entries.values()]
|
||
|
};
|
||
|
await hooks.callHook("entries:resolve", ctx);
|
||
|
while (normalizeQueue.length) {
|
||
|
const i = normalizeQueue.shift();
|
||
|
const e = entries.get(i);
|
||
|
if (e) {
|
||
|
const normalizeCtx = {
|
||
|
tags: normalizeEntryToTags(e.input, resolvedOptions.propResolvers || []).map((t) => Object.assign(t, e.options)),
|
||
|
entry: e
|
||
|
};
|
||
|
await hooks.callHook("entries:normalize", normalizeCtx);
|
||
|
e._tags = normalizeCtx.tags.map((t, i2) => {
|
||
|
t._w = tagWeight(head, t);
|
||
|
t._p = (e._i << 10) + i2;
|
||
|
t._d = dedupeKey(t);
|
||
|
return t;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
let hasFlatMeta = false;
|
||
|
ctx.entries.flatMap((e) => (e._tags || []).map((t) => ({ ...t, props: { ...t.props } }))).sort(sortTags).reduce((acc, next) => {
|
||
|
const k = String(next._d || next._p);
|
||
|
if (!acc.has(k))
|
||
|
return acc.set(k, next);
|
||
|
const prev = acc.get(k);
|
||
|
const strategy = next?.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? "merge" : null) || (next.key && next.key === prev.key ? "merge" : null);
|
||
|
if (strategy === "merge") {
|
||
|
const newProps = { ...prev.props };
|
||
|
Object.entries(next.props).forEach(([p, v]) => (
|
||
|
// @ts-expect-error untyped
|
||
|
newProps[p] = p === "style" ? new Map([...prev.props.style || /* @__PURE__ */ new Map(), ...v]) : p === "class" ? /* @__PURE__ */ new Set([...prev.props.class || /* @__PURE__ */ new Set(), ...v]) : v
|
||
|
));
|
||
|
acc.set(k, { ...next, props: newProps });
|
||
|
} else if (next._p >> 10 === prev._p >> 10 && isMetaArrayDupeKey(next._d)) {
|
||
|
acc.set(k, Object.assign([...Array.isArray(prev) ? prev : [prev], next], next));
|
||
|
hasFlatMeta = true;
|
||
|
} else if (next._w === prev._w ? next._p > prev._p : next?._w < prev?._w) {
|
||
|
acc.set(k, next);
|
||
|
}
|
||
|
return acc;
|
||
|
}, ctx.tagMap);
|
||
|
const title = ctx.tagMap.get("title");
|
||
|
const titleTemplate = ctx.tagMap.get("titleTemplate");
|
||
|
head._title = title?.textContent;
|
||
|
if (titleTemplate) {
|
||
|
const titleTemplateFn = titleTemplate?.textContent;
|
||
|
head._titleTemplate = typeof titleTemplateFn === "string" ? titleTemplateFn : void 0;
|
||
|
if (titleTemplateFn) {
|
||
|
let newTitle = typeof titleTemplateFn === "function" ? titleTemplateFn(title?.textContent) : titleTemplateFn;
|
||
|
if (typeof newTitle === "string" && !head.plugins.has("template-params")) {
|
||
|
newTitle = newTitle.replace("%s", title?.textContent || "");
|
||
|
}
|
||
|
if (title) {
|
||
|
newTitle === null ? ctx.tagMap.delete("title") : ctx.tagMap.set("title", { ...title, textContent: newTitle });
|
||
|
} else {
|
||
|
titleTemplate.tag = "title";
|
||
|
titleTemplate.textContent = newTitle;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ctx.tags = Array.from(ctx.tagMap.values());
|
||
|
if (hasFlatMeta) {
|
||
|
ctx.tags = ctx.tags.flat().sort(sortTags);
|
||
|
}
|
||
|
await hooks.callHook("tags:beforeResolve", ctx);
|
||
|
await hooks.callHook("tags:resolve", ctx);
|
||
|
await hooks.callHook("tags:afterResolve", ctx);
|
||
|
const finalTags = [];
|
||
|
for (const t of ctx.tags) {
|
||
|
const { innerHTML, tag, props } = t;
|
||
|
if (!ValidHeadTags.has(tag)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (Object.keys(props).length === 0 && !t.innerHTML && !t.textContent) {
|
||
|
continue;
|
||
|
}
|
||
|
if (tag === "meta" && !props.content && !props["http-equiv"] && !props.charset) {
|
||
|
continue;
|
||
|
}
|
||
|
if (tag === "script" && innerHTML) {
|
||
|
if (props.type?.endsWith("json")) {
|
||
|
const v = typeof innerHTML === "string" ? innerHTML : JSON.stringify(innerHTML);
|
||
|
t.innerHTML = v.replace(/</g, "\\u003C");
|
||
|
} else if (typeof innerHTML === "string") {
|
||
|
t.innerHTML = innerHTML.replace(new RegExp(`</${tag}`, "g"), `<\\/${tag}`);
|
||
|
}
|
||
|
t._d = dedupeKey(t);
|
||
|
}
|
||
|
finalTags.push(t);
|
||
|
}
|
||
|
return finalTags;
|
||
|
}
|
||
|
};
|
||
|
(resolvedOptions?.plugins || []).forEach((p) => registerPlugin(head, p));
|
||
|
head.hooks.callHook("init", head);
|
||
|
resolvedOptions.init?.forEach((e) => e && head.push(e));
|
||
|
return head;
|
||
|
}
|
||
|
|
||
|
export { createUnhead as a, createHeadCore as c };
|