'use strict'; const TagsWithInnerContent = ["script", "style", "noscript"]; const HasElementTags = [ "base", "meta", "link", "style", "script", "noscript" ]; 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 setAttrs = (ctx, markSideEffect) => { const { tag, $el } = ctx; if (!$el) return; Object.entries(tag.props).forEach(([k, value]) => { value = String(value); const attrSdeKey = `attr:${k}`; if (k === "class") { if (!value) return; for (const c of value.split(" ")) { const classSdeKey = `${attrSdeKey}:${c}`; if (markSideEffect) markSideEffect(ctx, classSdeKey, () => $el.classList.remove(c)); if (!$el.classList.contains(c)) $el.classList.add(c); } return; } if (markSideEffect && !k.startsWith("data-h-")) markSideEffect(ctx, attrSdeKey, () => $el.removeAttribute(k)); if ($el.getAttribute(k) !== value) $el.setAttribute(k, value); }); if (TagsWithInnerContent.includes(tag.tag) && $el.innerHTML !== (tag.children || "")) $el.innerHTML = tag.children || ""; }; function hashCode(s) { let h = 9; for (let i = 0; i < s.length; ) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase(); } async function renderDOMHead(head, options = {}) { const ctx = { shouldRender: true }; await head.hooks.callHook("dom:beforeRender", ctx); if (!ctx.shouldRender) return; const dom = options.document || window.document; const staleSideEffects = head._popSideEffectQueue(); head.headEntries().map((entry) => entry._sde).forEach((sde) => { Object.entries(sde).forEach(([key, fn]) => { staleSideEffects[key] = fn; }); }); const preRenderTag = async (tag) => { const entry = head.headEntries().find((e) => e._i === tag._e); const renderCtx = { renderId: tag._d || hashCode(JSON.stringify({ ...tag, _e: void 0, _p: void 0 })), $el: null, shouldRender: true, tag, entry, staleSideEffects }; await head.hooks.callHook("dom:beforeRenderTag", renderCtx); return renderCtx; }; const renders = []; const pendingRenders = { body: [], head: [] }; const markSideEffect = (ctx2, key, fn) => { key = `${ctx2.renderId}:${key}`; if (ctx2.entry) ctx2.entry._sde[key] = fn; delete staleSideEffects[key]; }; const markEl = (ctx2) => { head._elMap[ctx2.renderId] = ctx2.$el; renders.push(ctx2); markSideEffect(ctx2, "el", () => { ctx2.$el?.remove(); delete head._elMap[ctx2.renderId]; }); }; for (const t of await head.resolveTags()) { const ctx2 = await preRenderTag(t); if (!ctx2.shouldRender) continue; const { tag } = ctx2; if (tag.tag === "title") { dom.title = tag.children || ""; renders.push(ctx2); continue; } if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") { ctx2.$el = dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"]; setAttrs(ctx2, markSideEffect); renders.push(ctx2); continue; } ctx2.$el = head._elMap[ctx2.renderId]; if (!ctx2.$el && tag._hash) { ctx2.$el = dom.querySelector(`${tag.tagPosition?.startsWith("body") ? "body" : "head"} > ${tag.tag}[data-h-${tag._hash}]`); } if (ctx2.$el) { if (ctx2.tag._d) setAttrs(ctx2); markEl(ctx2); continue; } ctx2.$el = dom.createElement(tag.tag); setAttrs(ctx2); pendingRenders[tag.tagPosition?.startsWith("body") ? "body" : "head"].push(ctx2); } Object.entries(pendingRenders).forEach(([pos, queue]) => { if (!queue.length) return; for (const $el of [...dom[pos].children].reverse()) { const elTag = $el.tagName.toLowerCase(); if (!HasElementTags.includes(elTag)) continue; const dedupeKey = tagDedupeKey({ tag: elTag, props: $el.getAttributeNames().reduce((props, name) => ({ ...props, [name]: $el.getAttribute(name) }), {}) }); const matchIdx = queue.findIndex((ctx2) => ctx2 && (ctx2.tag._d === dedupeKey || $el.isEqualNode(ctx2.$el))); if (matchIdx !== -1) { const ctx2 = queue[matchIdx]; ctx2.$el = $el; setAttrs(ctx2); markEl(ctx2); delete queue[matchIdx]; } } queue.forEach((ctx2) => { if (!ctx2.$el) return; switch (ctx2.tag.tagPosition) { case "bodyClose": dom.body.appendChild(ctx2.$el); break; case "bodyOpen": dom.body.insertBefore(ctx2.$el, dom.body.firstChild); break; case "head": default: dom.head.appendChild(ctx2.$el); break; } markEl(ctx2); }); }); for (const ctx2 of renders) await head.hooks.callHook("dom:renderTag", ctx2); Object.values(staleSideEffects).forEach((fn) => fn()); } exports.domUpdatePromise = null; async function debouncedRenderDOMHead(head, options = {}) { function doDomUpdate() { exports.domUpdatePromise = null; return renderDOMHead(head, options); } const delayFn = options.delayFn || ((fn) => setTimeout(fn, 10)); return exports.domUpdatePromise = exports.domUpdatePromise || new Promise((resolve) => delayFn(() => resolve(doDomUpdate()))); } exports.debouncedRenderDOMHead = debouncedRenderDOMHead; exports.hashCode = hashCode; exports.renderDOMHead = renderDOMHead;