unhead 3.0.5 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +5 -5
- package/dist/client.d.ts +5 -5
- package/dist/client.mjs +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.mjs +2 -2
- package/dist/legacy.d.mts +3 -3
- package/dist/legacy.d.ts +3 -3
- package/dist/legacy.mjs +5 -5
- package/dist/parser.d.mts +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/plugins.d.mts +6 -33
- package/dist/plugins.d.ts +6 -33
- package/dist/plugins.mjs +35 -199
- package/dist/scripts.d.mts +4 -4
- package/dist/scripts.d.ts +4 -4
- package/dist/scripts.mjs +2 -2
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.mjs +4 -4
- package/dist/shared/{unhead.-D8hRpkn.d.ts → unhead.B92-AP20.d.ts} +2 -2
- package/dist/shared/{unhead.gui9LmZS.d.ts → unhead.BWNOp3cv.d.ts} +1 -1
- package/dist/shared/{unhead.Cv5yrrUd.d.mts → unhead.BjPIONVY.d.mts} +2 -2
- package/dist/shared/{unhead.pv34ME7O.mjs → unhead.BjuLn3hu.mjs} +2 -2
- package/dist/shared/{unhead.BoZ-Ul8T.d.mts → unhead.BodZ6XwK.d.mts} +1 -1
- package/dist/shared/{unhead.B7bBMqva.d.mts → unhead.Brajh4vg.d.mts} +1 -1
- package/dist/shared/{unhead.C5ypJnIO.mjs → unhead.Bu5O0NaA.mjs} +3 -3
- package/dist/shared/{unhead.DeoGMp34.d.mts → unhead.Bz11580x.d.mts} +43 -9
- package/dist/shared/{unhead.DeoGMp34.d.ts → unhead.Bz11580x.d.ts} +43 -9
- package/dist/shared/{unhead.BX9134DF.d.mts → unhead.CG80LYNi.d.mts} +1 -1
- package/dist/shared/{unhead.CqVawd25.d.ts → unhead.CHwJVuR_.d.ts} +1 -1
- package/dist/shared/{unhead.cXt2Dgt5.d.ts → unhead.CJ5MDR84.d.ts} +1 -1
- package/dist/shared/{unhead.YnmwhfIN.mjs → unhead.CJY-UeEt.mjs} +2 -2
- package/dist/shared/{unhead.ClE7lB2Z.d.mts → unhead.CuuHFiTB.d.mts} +1 -1
- package/dist/shared/{unhead.Dc5Pn4qI.d.mts → unhead.CwE6ye0n.d.mts} +2 -2
- package/dist/shared/{unhead.B2jfOxG1.d.mts → unhead.CwvlUXnx.d.mts} +2 -2
- package/dist/shared/{unhead.DdarjSpi.d.ts → unhead.DbMowiUH.d.ts} +2 -2
- package/dist/shared/unhead.DsqXiUvy.d.mts +38 -0
- package/dist/shared/unhead.DsqXiUvy.d.ts +38 -0
- package/dist/shared/{unhead.BRwJvCZb.d.ts → unhead.DwiUIw06.d.ts} +2 -2
- package/dist/shared/{unhead.DiRbsb3I.mjs → unhead.DzSj5qjO.mjs} +5 -4
- package/dist/shared/{unhead.DctB-0lW.mjs → unhead.eQfSuvE2.mjs} +2 -2
- package/dist/shared/unhead.ebqUBTt1.mjs +513 -0
- package/dist/shared/{unhead.q49lHHFN.d.ts → unhead.f0Z4Ygga.d.ts} +1 -1
- package/dist/shared/{unhead.DvIxXxuO.mjs → unhead.mB5lMBMV.mjs} +1 -1
- package/dist/stream/client.d.mts +24 -5
- package/dist/stream/client.d.ts +24 -5
- package/dist/stream/client.mjs +1 -1
- package/dist/stream/iife.global.js +1 -1
- package/dist/stream/iife.mjs +2 -2
- package/dist/stream/server.d.mts +9 -6
- package/dist/stream/server.d.ts +9 -6
- package/dist/stream/server.mjs +18 -7
- package/dist/stream/unplugin.d.mts +81 -0
- package/dist/stream/unplugin.d.ts +81 -0
- package/dist/stream/unplugin.mjs +166 -0
- package/dist/stream/vite.d.mts +19 -27
- package/dist/stream/vite.d.ts +19 -27
- package/dist/stream/vite.mjs +6 -78
- package/dist/types.d.mts +6 -6
- package/dist/types.d.ts +6 -6
- package/dist/utils.d.mts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.mjs +2 -2
- package/dist/validate.d.mts +236 -0
- package/dist/validate.d.ts +236 -0
- package/dist/validate.mjs +49 -0
- package/package.json +19 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
var __unhead_iife__ = (function (exports) { 'use strict'; const DupeableTags = new Set(["link", "style", "script", "noscript"]); const TagsWithInnerContent = new Set(["title", "titleTemplate", "script", "style", "noscript"]); const HasElementTags = new Set(["base", "meta", "link", "style", "script", "noscript"]); const ValidHeadTags = new Set(["title", "base", "htmlAttrs", "bodyAttrs", "meta", "link", "style", "script", "noscript"]); const UniqueTags = new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]); const TagConfigKeys = new Set(["key", "tagPosition", "tagPriority", "tagDuplicateStrategy", "innerHTML", "textContent", "processTemplateParams"]); const UsesMergeStrategy = new Set(["templateParams", "htmlAttrs", "bodyAttrs"]); const MetaTagsArrayable = new Set([ "theme-color", "google-site-verification", "og", "article", "book", "profile", "twitter", "author" ]); function callHook(head, hook, ctx) { return head.hooks?.callHook(hook, ctx); } const META_NOREWRITE_RE = /^(?:viewport|description|keywords|robots)$/; function isMetaArrayDupeKey(v) { return MetaTagsArrayable.has(v.split(":")[1]); } function dedupeKey(tag) { const { props, tag: t, key } = tag; if (UniqueTags.has(t)) return t; if (t === "link" && props.rel === "canonical") return "canonical"; if (t === "link" && props.rel === "alternate") { const altKey = props.hreflang || props.type; if (altKey) return `alternate:${altKey}`; } if (props.charset) return "charset"; if (t === "meta") { for (const n of ["name", "property", "http-equiv"]) { const v = props[n]; if (v !== void 0) return `meta:${v}${(typeof v !== "string" || !v.includes(":")) && !META_NOREWRITE_RE.test(v) && key ? `:key:${key}` : ""}`; } } if (key) return `${t}:key:${key}`; if (props.id) return `${t}:id:${props.id}`; if (t === "link" && props.rel === "alternate") return `alternate:${props.href || ""}`; return TagsWithInnerContent.has(t) && (tag.textContent || tag.innerHTML) ? `${t}:content:${tag.textContent || tag.innerHTML}` : void 0; } function hashTag(tag) { return tag._h || tag._d || tag.textContent || tag.innerHTML || `${tag.tag}:${Object.entries(tag.props).map(([k, v]) => `${k}:${String(v)}`).join()}`; } function walkResolver(val, resolve, key) { if (key === "_resolver") return val; if (typeof val === "function" && (!key || key !== "titleTemplate" && !key.startsWith("on"))) val = val(); const v = resolve ? resolve(key, val) : val; if (Array.isArray(v)) return v.map((r) => walkResolver(r, resolve)); if (v?.constructor === Object) { const next = {}; for (const k in v) { if (k === "__proto__" || k === "constructor" || k === "prototype") continue; next[k] = walkResolver(v[k], resolve, k); } return next; } return v; } function normalizeStyleClassProps(key, value) { const isStyle = key === "style"; const store = isStyle ? new Map() : new Set(); const add = (v) => { if (!v) return; if (isStyle) { const i = v.indexOf(":"); i > 0 && store.set(v.slice(0, i).trim(), v.slice(i + 1).trim()); } else { v.split(" ").forEach((c) => c && store.add(c)); } }; if (typeof value === "string") { (isStyle ? value.split(";") : [value]).forEach(add); } else if (Array.isArray(value)) { value.forEach(add); } else if (value && typeof value === "object") { for (const k in value) { const v = value[k]; v && v !== "false" && (isStyle ? store.set(k.trim(), String(v)) : add(k)); } } return store; } function normalizeProps(tag, input) { tag.props = tag.props || {}; if (!input) return tag; if (tag.tag === "templateParams") { tag.props = input; return tag; } const isHtmlTag = HasElementTags.has(tag.tag) || tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"; for (const prop in input) { if (prop === "__proto__" || prop === "constructor" || prop === "prototype") continue; const value = input[prop]; if (value === null) { tag.props[prop] = null; } else if (prop === "class" || prop === "style") { tag.props[prop] = normalizeStyleClassProps(prop, value); } else if (TagConfigKeys.has(prop)) { if ((prop === "textContent" || prop === "innerHTML") && typeof value === "object") { const type = input.type || "application/json"; if (type.endsWith("json") || type === "speculationrules" || type === "importmap") { tag.props.type = input.type = type; tag[prop] = JSON.stringify(value); } } else { tag[prop] = value; } } else if (value !== void 0) { const isData = prop.startsWith("data-"); const key = isHtmlTag && !isData ? prop.toLowerCase() : prop; const str = String(value); const isMeta = tag.tag === "meta" && key === "content"; tag.props[key] = str === "true" || str === "" ? isData || isMeta ? str : true : !value && isData && str === "false" ? "false" : value; } } return tag; } function normalizeTag(tagName, _input) { const input = typeof _input === "object" && typeof _input !== "function" ? _input : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: _input }; const tag = normalizeProps({ tag: tagName, props: {} }, input); if (tag.key && DupeableTags.has(tag.tag)) tag.props["data-hid"] = tag._h = tag.key; if (tag.tag === "script" && typeof tag.innerHTML === "object") { tag.innerHTML = JSON.stringify(tag.innerHTML); tag.props.type = tag.props.type || "application/json"; } return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag; } function normalizeEntryToTags(input, propResolvers) { if (!input) return []; if (typeof input === "function") input = input(); const resolvers = (key, val) => { for (const r of propResolvers) val = r(key, val); return val; }; input = walkResolver(resolvers(void 0, input), resolvers); const tags = []; for (const key in input) { const value = input[key]; if (value !== void 0) { for (const v of Array.isArray(value) ? value : [value]) tags.push(normalizeTag(key, v)); } } return tags.flat(); } const LT_RE = /</g; const SCRIPT_END_RE = /<\/script/g; const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w; function dedupeTags(ctx) { let hasFlatMeta = false; for (const next of ctx.tags.sort(sortTags)) { const k = next._d || hashTag(next); const prev = ctx.tagMap.get(k); if (!prev) { ctx.tagMap.set(k, next); continue; } const strategy = next.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? "merge" : null) || (next.key && next.key === prev.key ? "merge" : null); if (strategy === "merge") { const props = { ...prev.props }; for (const p in next.props) { props[p] = p === "style" ? new Map([...prev.props.style || new Map(), ...next.props[p]]) : p === "class" ? new Set([...prev.props.class || [], ...next.props[p]]) : next.props[p]; } ctx.tagMap.set(k, { ...next, props }); } else if (next._p >> 10 === prev._p >> 10 && next.tag === "meta" && isMetaArrayDupeKey(k)) { ctx.tagMap.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) { ctx.tagMap.set(k, next); } } return hasFlatMeta; } function resolveTitleTemplate(ctx, head) { const title = ctx.tagMap.get("title"); const tpl = ctx.tagMap.get("titleTemplate"); head._title = title?.textContent; if (!tpl) return; const fn = tpl.textContent; head._titleTemplate = fn; if (!fn) return; let v = typeof fn === "function" ? fn(title?.textContent) : fn; if (typeof v === "string" && !head.plugins.has("template-params")) v = v.replace("%s", title?.textContent || ""); if (title) { v === null ? ctx.tagMap.delete("title") : ctx.tagMap.set("title", { ...title, textContent: v }); } else { ctx.tagMap.set("titleTemplate", { ...tpl, tag: "title", textContent: v }); } } function sanitizeTags(tags) { return tags.filter((t) => { const { innerHTML, tag, props } = t; if (!ValidHeadTags.has(tag) || !Object.keys(props).length && !innerHTML && !t.textContent) return false; if (tag === "meta" && !props.content && !props["http-equiv"] && !props.charset) return false; if (tag === "script" && (innerHTML || t.textContent)) { const type = String(props.type); const isJsonLike = type.endsWith("json") || type === "importmap" || type === "speculationrules"; const escape = (content) => isJsonLike ? (typeof content === "string" ? content : JSON.stringify(content)).replace(LT_RE, "\\u003C") : typeof content === "string" ? content.replace(SCRIPT_END_RE, "<\\/script") : content; if (innerHTML) t.innerHTML = escape(innerHTML); if (t.textContent) t.textContent = escape(t.textContent); t._d = dedupeKey(t); } return true; }); } function resolveTags(head, options) { const weightFn = options?.tagWeight ?? head.resolvedOptions._tagWeight ?? (() => 100); const ctx = { tagMap: new Map(), tags: [] }; const entries = [...head.entries.values()]; for (const e of entries) { if (e._pending !== void 0) { e.input = e._pending; delete e._pending; delete e._tags; } } callHook(head, "entries:resolve", { entries, ...ctx }); for (const e of entries) { if (!e._tags) { const normalizeCtx = { tags: normalizeEntryToTags(e.input, head.resolvedOptions.propResolvers || []).map((t) => Object.assign(t, e.options)), entry: e }; callHook(head, "entries:normalize", normalizeCtx); e._tags = normalizeCtx.tags.map((t, i) => { t._w = weightFn(t); t._p = (e._i << 10) + i; t._d = dedupeKey(t); if (!t._d) t._h = hashTag(t); return t; }); } } ctx.tags = entries.flatMap((e) => (e._tags || []).map((t) => ({ ...t, props: { ...t.props } }))); const hasFlatMeta = dedupeTags(ctx); resolveTitleTemplate(ctx, head); ctx.tags = [...ctx.tagMap.values()]; if (hasFlatMeta) ctx.tags = ctx.tags.flat().sort(sortTags); callHook(head, "tags:beforeResolve", ctx); callHook(head, "tags:resolve", ctx); callHook(head, "tags:afterResolve", ctx); return sanitizeTags(ctx.tags); } const WHITESPACE_RE = /\s+/; function createDomRenderer(options = {}) { return (head) => _renderDOMHead(head, options); } function _renderDOMHead(head, options = {}) { const dom = options.document || head.resolvedOptions.document; if (!dom || !head.dirty && ![...head.entries.values()].some((e) => e._pending !== void 0)) return false; const beforeRenderCtx = { shouldRender: true, tags: [] }; callHook(head, "dom:beforeRender", beforeRenderCtx); if (!beforeRenderCtx.shouldRender || head._du) return false; head._du = true; let state = head._dom; if (!state) { state = { _t: dom.title, _e: new Map([["htmlAttrs", dom.documentElement], ["bodyAttrs", dom.body]]), _p: {}, _s: {} }; for (const el of [...dom.body.children, ...dom.head.children]) { const tag = el.tagName.toLowerCase(); if (!HasElementTags.has(tag)) continue; const props = { innerHTML: el.innerHTML }; for (const n of el.getAttributeNames()) props[n] = el.getAttribute(n); const next = normalizeProps({ tag, props: {} }, props); next.key = el.getAttribute("data-hid") || void 0; let k = next._d = dedupeKey(next) || hashTag(next); let c = 1; while (state._e.has(k)) k = `${next._d}:${c++}`; state._e.set(k, el); } for (const entry of head.entries.values()) { if (entry._o !== void 0) { const orig = entry._o; for (const t of ["bodyAttrs", "htmlAttrs"]) { const cls = orig[t]?.class; if (typeof cls === "string") { const $el = state._e.get(t); for (const c of cls.split(WHITESPACE_RE)) { if (c) state._p[`${t}:attr:class:${c}`] = () => $el.classList.remove(c); } } } delete entry._o; } } } else { state._p = { ...state._s }; } state._s = {}; function track(id, scope, fn) { const k = `${id}:${scope}`; state._s[k] = fn; delete state._p[k]; } function trackCtx({ id, $el, tag }) { const isAttr = tag.tag.endsWith("Attrs"); state._e.set(id, $el); if (!isAttr) { if (tag.textContent && tag.textContent !== $el.textContent) $el.textContent = tag.textContent; if (tag.innerHTML && tag.innerHTML !== $el.innerHTML) $el.innerHTML = tag.innerHTML; track(id, "el", () => { $el?.remove(); state._e.delete(id); }); } for (const k in tag.props) { const v = tag.props[k]; if (k[0] === "o" && k[1] === "n" && typeof v === "function") { const ev = k.slice(2); if ($el?.dataset?.[`${k}fired`]) v.call($el, new Event(ev)); if ($el.getAttribute(`data-${k}`) !== "") { (tag.tag === "bodyAttrs" ? dom.defaultView : $el).addEventListener(ev, v.bind($el)); $el.setAttribute(`data-${k}`, ""); } continue; } const ck = `attr:${k}`; if (k === "class" && v) { for (const c of v) { if (isAttr) track(id, `${ck}:${c}`, () => $el.classList.remove(c)); if (!$el.classList.contains(c)) $el.classList.add(c); } } else if (k === "style" && v) { for (const [sk, sv] of v) { track(id, `${ck}:${sk}`, () => $el.style.removeProperty(sk)); $el.style.setProperty(sk, sv); } } else if (v !== false && v !== null) { if ($el.getAttribute(k) !== v) $el.setAttribute(k, v === true ? "" : String(v)); if (isAttr) track(id, ck, () => $el.removeAttribute(k)); } } } const pending = []; const frag = {}; const rawTags = resolveTags(head, options.tagWeight ? { tagWeight: options.tagWeight } : void 0); const tags = []; const dupeKeyCounter = new Map(); for (const tag of rawTags) { const count = dupeKeyCounter.get(tag._d) || 0; const id = (count ? `${tag._d}:${count}` : tag._d) || tag._h; const ctx = { tag, id, shouldRender: true }; if (tag._d && isMetaArrayDupeKey(tag._d)) dupeKeyCounter.set(tag._d, count + 1); tags.push(ctx); if (tag.tag === "title") { dom.title = tag.textContent; track("title", "", () => dom.title = state._t); continue; } ctx.$el = state._e.get(id); if (ctx.$el) trackCtx(ctx); else if (HasElementTags.has(tag.tag)) pending.push(ctx); } for (const ctx of pending) { ctx.$el = dom.createElement(ctx.tag.tag); trackCtx(ctx); (frag[ctx.tag.tagPosition || "head"] ??= dom.createDocumentFragment()).appendChild(ctx.$el); } if (frag.head) dom.head.appendChild(frag.head); if (frag.bodyOpen) dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild); if (frag.bodyClose) dom.body.appendChild(frag.bodyClose); for (const k in state._p) state._p[k](); head._dom = state; callHook(head, "dom:rendered", { renders: tags }); head._du = false; head.dirty = false; return true; } function registerPlugin(head, p) { const plugin = typeof p === "function" ? p(head) : p; const key = plugin.key || String(head.plugins.size + 1); if (!head.plugins.get(key)) { head.plugins.set(key, plugin); for (const k in plugin.hooks || {}) head.hooks?.hook(k, plugin.hooks[k]); } } function createUnhead(renderer, resolvedOptions = {}) { const ssr = !resolvedOptions.document; const entries = new Map(); const plugins = new Map(); const head = { _entryCount: 1, plugins, resolvedOptions, ssr, entries, render: () => renderer(head), use: (p) => registerPlugin(head, p), push(input, _options) { const _i = _options?._index ?? head._entryCount++; const options = _options ? { ..._options } : {}; delete options.head; delete options.onRendered; const entry = { _i, input, options }; entries.set(_i, entry); const active = { _i, dispose() { entries.delete(_i); }, patch(input2) { if (ssr) { entry.input = input2; delete entry._tags; } else { entry._pending = input2; } if (!entries.has(_i)) entries.set(_i, entry); } }; return active; } }; resolvedOptions.init?.forEach((e) => e && head.push(e)); return head; } const DEFAULT_STREAM_KEY = "__unhead__"; function init(options = {}) { const { streamKey = DEFAULT_STREAM_KEY } = options; const win = typeof window !== "undefined" ? window : void 0; if (!win) return; const queue = win[streamKey]; if (queue?._head) return queue._head; const doc = typeof document !== "undefined" ? document : void 0; const head = createUnhead(createDomRenderer(), { document: doc }); let hydrationLocked = true; queueMicrotask(() => { hydrationLocked = false; }); function pushStreamed(entry) { const active = head.push(entry); const stored = head.entries.get(active._i); if (stored) stored._streamed = true; } if (queue?._q) { for (const entries of queue._q) { for (const entry of entries) { pushStreamed(entry); } } head.dirty = true; head.render(); } win[streamKey] = { _q: queue?._q || [], _head: head, _hydrationLocked: () => hydrationLocked, push: (entries) => { for (const entry of entries) { pushStreamed(entry); } head.dirty = true; head.render(); } }; return head; } init(); exports.init = init; return exports; })({});
|
|
1
|
+
var __unhead_iife__ = (function (exports) { 'use strict'; const DupeableTags = new Set(["link", "style", "script", "noscript"]); const TagsWithInnerContent = new Set(["title", "titleTemplate", "script", "style", "noscript"]); const HasElementTags = new Set(["base", "meta", "link", "style", "script", "noscript"]); const ValidHeadTags = new Set(["title", "base", "htmlAttrs", "bodyAttrs", "meta", "link", "style", "script", "noscript"]); const UniqueTags = new Set(["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"]); const TagConfigKeys = new Set(["key", "tagPosition", "tagPriority", "tagDuplicateStrategy", "innerHTML", "textContent", "processTemplateParams"]); const UsesMergeStrategy = new Set(["templateParams", "htmlAttrs", "bodyAttrs"]); const MetaTagsArrayable = new Set([ "theme-color", "google-site-verification", "og", "article", "book", "profile", "twitter", "author" ]); function callHook(head, hook, ctx) { return head.hooks?.callHook(hook, ctx); } const META_NOREWRITE_RE = /^(?:viewport|description|keywords|robots)$/; function isMetaArrayDupeKey(v) { return MetaTagsArrayable.has(v.split(":")[1]); } function dedupeKey(tag) { const { props, tag: t, key } = tag; if (UniqueTags.has(t)) return t; if (t === "link" && props.rel === "canonical") return "canonical"; if (t === "link" && props.rel === "alternate") { if (props.hreflang) return `alternate:${props.hreflang}`; if (props.type) return `alternate:${props.type}:${props.href || ""}`; } if (props.charset) return "charset"; if (t === "meta") { for (const n of ["name", "property", "http-equiv"]) { const v = props[n]; if (v !== void 0) return `meta:${v}${(typeof v !== "string" || !v.includes(":")) && !META_NOREWRITE_RE.test(v) && key ? `:key:${key}` : ""}`; } } if (key) return `${t}:key:${key}`; if (props.id) return `${t}:id:${props.id}`; if (t === "link" && props.rel === "alternate") return `alternate:${props.href || ""}`; return TagsWithInnerContent.has(t) && (tag.textContent || tag.innerHTML) ? `${t}:content:${tag.textContent || tag.innerHTML}` : void 0; } function hashTag(tag) { return tag._h || tag._d || tag.textContent || tag.innerHTML || `${tag.tag}:${Object.entries(tag.props).map(([k, v]) => `${k}:${String(v)}`).join()}`; } function walkResolver(val, resolve, key) { if (key === "_resolver") return val; if (typeof val === "function" && (!key || key !== "titleTemplate" && !key.startsWith("on"))) val = val(); const v = resolve ? resolve(key, val) : val; if (Array.isArray(v)) return v.map((r) => walkResolver(r, resolve)); if (v?.constructor === Object) { const next = {}; for (const k in v) { if (k === "__proto__" || k === "constructor" || k === "prototype") continue; next[k] = walkResolver(v[k], resolve, k); } return next; } return v; } function normalizeStyleClassProps(key, value) { const isStyle = key === "style"; const store = isStyle ? new Map() : new Set(); const add = (v) => { if (!v) return; if (isStyle) { const i = v.indexOf(":"); i > 0 && store.set(v.slice(0, i).trim(), v.slice(i + 1).trim()); } else { v.split(" ").forEach((c) => c && store.add(c)); } }; if (typeof value === "string") { (isStyle ? value.split(";") : [value]).forEach(add); } else if (Array.isArray(value)) { value.forEach(add); } else if (value && typeof value === "object") { for (const k in value) { const v = value[k]; v && v !== "false" && (isStyle ? store.set(k.trim(), String(v)) : add(k)); } } return store; } function normalizeProps(tag, input) { tag.props = tag.props || {}; if (!input) return tag; if (tag.tag === "templateParams") { tag.props = input; return tag; } const isHtmlTag = HasElementTags.has(tag.tag) || tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs"; for (const prop in input) { if (prop === "__proto__" || prop === "constructor" || prop === "prototype") continue; const value = input[prop]; if (value === null) { tag.props[prop] = null; } else if (prop === "class" || prop === "style") { tag.props[prop] = normalizeStyleClassProps(prop, value); } else if (TagConfigKeys.has(prop)) { if ((prop === "textContent" || prop === "innerHTML") && typeof value === "object") { const type = input.type || "application/json"; if (type.endsWith("json") || type === "speculationrules" || type === "importmap") { tag.props.type = input.type = type; tag[prop] = JSON.stringify(value); } } else { tag[prop] = value; } } else if (value !== void 0) { const isData = prop.startsWith("data-"); const key = isHtmlTag && !isData ? prop.toLowerCase() : prop; const str = String(value); const isMeta = tag.tag === "meta" && key === "content"; tag.props[key] = str === "true" || str === "" ? isData || isMeta ? str : true : !value && isData && str === "false" ? "false" : value; } } return tag; } function normalizeTag(tagName, _input) { const input = typeof _input === "object" && typeof _input !== "function" ? _input : { [tagName === "script" || tagName === "noscript" || tagName === "style" ? "innerHTML" : "textContent"]: _input }; const tag = normalizeProps({ tag: tagName, props: {} }, input); if (tag.key && DupeableTags.has(tag.tag)) tag.props["data-hid"] = tag._h = tag.key; if (tag.tag === "script" && typeof tag.innerHTML === "object") { tag.innerHTML = JSON.stringify(tag.innerHTML); tag.props.type = tag.props.type || "application/json"; } return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag; } function normalizeEntryToTags(input, propResolvers) { if (!input) return []; if (typeof input === "function") input = input(); const resolvers = (key, val) => { for (const r of propResolvers) val = r(key, val); return val; }; input = walkResolver(resolvers(void 0, input), resolvers); const tags = []; for (const key in input) { const value = input[key]; if (value !== void 0) { for (const v of Array.isArray(value) ? value : [value]) tags.push(normalizeTag(key, v)); } } return tags.flat(); } const LT_RE = /</g; const SCRIPT_END_RE = /<\/script/g; const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w; function dedupeTags(ctx) { let hasFlatMeta = false; for (const next of ctx.tags.sort(sortTags)) { const k = next._d || hashTag(next); const prev = ctx.tagMap.get(k); if (!prev) { ctx.tagMap.set(k, next); continue; } const strategy = next.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? "merge" : null) || (next.key && next.key === prev.key ? "merge" : null); if (strategy === "merge") { const props = { ...prev.props }; for (const p in next.props) { props[p] = p === "style" ? new Map([...prev.props.style || new Map(), ...next.props[p]]) : p === "class" ? new Set([...prev.props.class || [], ...next.props[p]]) : next.props[p]; } ctx.tagMap.set(k, { ...next, props }); } else if (next._p >> 10 === prev._p >> 10 && next.tag === "meta" && isMetaArrayDupeKey(k)) { ctx.tagMap.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) { ctx.tagMap.set(k, next); } } return hasFlatMeta; } function resolveTitleTemplate(ctx, head) { const title = ctx.tagMap.get("title"); const tpl = ctx.tagMap.get("titleTemplate"); head._title = title?.textContent; if (!tpl) return; const fn = tpl.textContent; head._titleTemplate = fn; if (!fn) return; let v = typeof fn === "function" ? fn(title?.textContent) : fn; if (typeof v === "string" && !head.plugins.has("template-params")) v = v.replace("%s", title?.textContent || ""); if (title) { v === null ? ctx.tagMap.delete("title") : ctx.tagMap.set("title", { ...title, textContent: v }); } else { ctx.tagMap.set("titleTemplate", { ...tpl, tag: "title", textContent: v }); } } function sanitizeTags(tags) { return tags.filter((t) => { const { innerHTML, tag, props } = t; if (!ValidHeadTags.has(tag) || !Object.keys(props).length && !innerHTML && !t.textContent) return false; if (tag === "meta" && !props.content && !props["http-equiv"] && !props.charset) return false; if (tag === "script" && (innerHTML || t.textContent)) { const type = String(props.type); const isJsonLike = type.endsWith("json") || type === "importmap" || type === "speculationrules"; const escape = (content) => isJsonLike ? (typeof content === "string" ? content : JSON.stringify(content)).replace(LT_RE, "\\u003C") : typeof content === "string" ? content.replace(SCRIPT_END_RE, "<\\/script") : content; if (innerHTML) t.innerHTML = escape(innerHTML); if (t.textContent) t.textContent = escape(t.textContent); t._d = dedupeKey(t); } return true; }); } function resolveTags(head, options) { const weightFn = options?.tagWeight ?? head.resolvedOptions._tagWeight ?? (() => 100); const ctx = { tagMap: new Map(), tags: [] }; const entries = [...head.entries.values()]; for (const e of entries) { if (e._pending !== void 0) { e.input = e._pending; delete e._pending; delete e._tags; } } callHook(head, "entries:resolve", { entries, ...ctx }); for (const e of entries) { if (!e._tags) { const normalizeCtx = { tags: normalizeEntryToTags(e.input, head.resolvedOptions.propResolvers || []).map((t) => Object.assign(t, e.options)), entry: e }; callHook(head, "entries:normalize", normalizeCtx); e._tags = normalizeCtx.tags.map((t, i) => { t._w = weightFn(t); t._p = (e._i << 10) + i; t._d = dedupeKey(t); if (!t._d) t._h = hashTag(t); return t; }); } } ctx.tags = entries.flatMap((e) => (e._tags || []).map((t) => ({ ...t, props: { ...t.props } }))); const hasFlatMeta = dedupeTags(ctx); resolveTitleTemplate(ctx, head); ctx.tags = [...ctx.tagMap.values()]; if (hasFlatMeta) ctx.tags = ctx.tags.flat().sort(sortTags); callHook(head, "tags:beforeResolve", ctx); callHook(head, "tags:resolve", ctx); callHook(head, "tags:afterResolve", ctx); return sanitizeTags(ctx.tags); } const WHITESPACE_RE = /\s+/; function createDomRenderer(options = {}) { return (head) => _renderDOMHead(head, options); } function _renderDOMHead(head, options = {}) { const dom = options.document || head.resolvedOptions.document; if (!dom || !head.dirty && ![...head.entries.values()].some((e) => e._pending !== void 0)) return false; const beforeRenderCtx = { shouldRender: true, tags: [] }; callHook(head, "dom:beforeRender", beforeRenderCtx); if (!beforeRenderCtx.shouldRender || head._du) return false; head._du = true; let state = head._dom; if (!state) { state = { _t: dom.title, _e: new Map([["htmlAttrs", dom.documentElement], ["bodyAttrs", dom.body]]), _p: {}, _s: {} }; for (const el of [...dom.body.children, ...dom.head.children]) { const tag = el.tagName.toLowerCase(); if (!HasElementTags.has(tag)) continue; const props = { innerHTML: el.innerHTML }; for (const n of el.getAttributeNames()) props[n] = el.getAttribute(n); const next = normalizeProps({ tag, props: {} }, props); next.key = el.getAttribute("data-hid") || void 0; let k = next._d = dedupeKey(next) || hashTag(next); let c = 1; while (state._e.has(k)) k = `${next._d}:${c++}`; state._e.set(k, el); } for (const entry of head.entries.values()) { if (entry._o !== void 0) { const orig = entry._o; for (const t of ["bodyAttrs", "htmlAttrs"]) { const cls = orig[t]?.class; if (typeof cls === "string") { const $el = state._e.get(t); for (const c of cls.split(WHITESPACE_RE)) { if (c) state._p[`${t}:attr:class:${c}`] = () => $el.classList.remove(c); } } } delete entry._o; } } } else { state._p = { ...state._s }; } state._s = {}; function track(id, scope, fn) { const k = `${id}:${scope}`; state._s[k] = fn; delete state._p[k]; } function trackCtx({ id, $el, tag }) { const isAttr = tag.tag.endsWith("Attrs"); state._e.set(id, $el); if (!isAttr) { if (tag.textContent && tag.textContent !== $el.textContent) $el.textContent = tag.textContent; if (tag.innerHTML && tag.innerHTML !== $el.innerHTML) $el.innerHTML = tag.innerHTML; track(id, "el", () => { $el?.remove(); state._e.delete(id); }); } for (const k in tag.props) { const v = tag.props[k]; if (k[0] === "o" && k[1] === "n" && typeof v === "function") { const ev = k.slice(2); if ($el?.dataset?.[`${k}fired`]) v.call($el, new Event(ev)); if ($el.getAttribute(`data-${k}`) !== "") { (tag.tag === "bodyAttrs" ? dom.defaultView : $el).addEventListener(ev, v.bind($el)); $el.setAttribute(`data-${k}`, ""); } continue; } const ck = `attr:${k}`; if (k === "class" && v) { for (const c of v) { if (isAttr) track(id, `${ck}:${c}`, () => $el.classList.remove(c)); if (!$el.classList.contains(c)) $el.classList.add(c); } } else if (k === "style" && v) { for (const [sk, sv] of v) { track(id, `${ck}:${sk}`, () => $el.style.removeProperty(sk)); $el.style.setProperty(sk, sv); } } else if (v !== false && v !== null) { if ($el.getAttribute(k) !== v) $el.setAttribute(k, v === true ? "" : String(v)); if (isAttr) track(id, ck, () => $el.removeAttribute(k)); } } } const pending = []; const frag = {}; const rawTags = resolveTags(head, options.tagWeight ? { tagWeight: options.tagWeight } : void 0); const tags = []; const dupeKeyCounter = new Map(); for (const tag of rawTags) { const count = dupeKeyCounter.get(tag._d) || 0; const id = (count ? `${tag._d}:${count}` : tag._d) || tag._h; const ctx = { tag, id, shouldRender: true }; if (tag._d && isMetaArrayDupeKey(tag._d)) dupeKeyCounter.set(tag._d, count + 1); tags.push(ctx); if (tag.tag === "title") { dom.title = tag.textContent; track("title", "", () => dom.title = state._t); continue; } ctx.$el = state._e.get(id); if (ctx.$el) trackCtx(ctx); else if (HasElementTags.has(tag.tag)) pending.push(ctx); } for (const ctx of pending) { ctx.$el = dom.createElement(ctx.tag.tag); trackCtx(ctx); (frag[ctx.tag.tagPosition || "head"] ??= dom.createDocumentFragment()).appendChild(ctx.$el); } if (frag.head) dom.head.appendChild(frag.head); if (frag.bodyOpen) dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild); if (frag.bodyClose) dom.body.appendChild(frag.bodyClose); for (const k in state._p) state._p[k](); head._dom = state; callHook(head, "dom:rendered", { renders: tags }); head._du = false; head.dirty = false; return true; } function registerPlugin(head, p) { const plugin = typeof p === "function" ? p(head) : p; const key = plugin.key || String(head.plugins.size + 1); if (!head.plugins.get(key)) { head.plugins.set(key, plugin); for (const k in plugin.hooks || {}) head.hooks?.hook(k, plugin.hooks[k]); } } function createUnhead(renderer, resolvedOptions = {}) { const ssr = !resolvedOptions.document; const entries = new Map(); const plugins = new Map(); const head = { _entryCount: 1, plugins, resolvedOptions, ssr, entries, render: () => renderer(head), use: (p) => registerPlugin(head, p), push(input, _options) { const _i = _options?._index ?? head._entryCount++; const options = _options ? { ..._options } : {}; delete options.head; delete options.onRendered; const entry = { _i, input, options }; entries.set(_i, entry); const active = { _i, dispose() { entries.delete(_i); }, patch(input2) { if (ssr) { entry.input = input2; delete entry._tags; } else { entry._pending = input2; } if (!entries.has(_i)) entries.set(_i, entry); } }; return active; } }; resolvedOptions.init?.forEach((e) => e && head.push(e)); return head; } const DEFAULT_STREAM_KEY = "__unhead__"; function init(options = {}) { const { streamKey = DEFAULT_STREAM_KEY } = options; const win = typeof window !== "undefined" ? window : void 0; if (!win) return; const queue = win[streamKey]; if (queue?._head) return queue._head; const doc = typeof document !== "undefined" ? document : void 0; const head = createUnhead(createDomRenderer(), { document: doc }); let hydrationLocked = true; queueMicrotask(() => { hydrationLocked = false; }); function pushStreamed(entry) { const active = head.push(entry); const stored = head.entries.get(active._i); if (stored) stored._streamed = true; } if (queue?._q) { for (const entries of queue._q) { for (const entry of entries) { pushStreamed(entry); } } head.dirty = true; head.render(); } win[streamKey] = { _q: queue?._q || [], _head: head, _hydrationLocked: () => hydrationLocked, push: (entries) => { for (const entry of entries) { pushStreamed(entry); } head.dirty = true; head.render(); } }; return head; } init(); exports.init = init; return exports; })({});
|
package/dist/stream/iife.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const streamingIifeCode = "var __unhead_iife__ = (function (exports) { 'use strict'; const DupeableTags = new Set([\"link\", \"style\", \"script\", \"noscript\"]); const TagsWithInnerContent = new Set([\"title\", \"titleTemplate\", \"script\", \"style\", \"noscript\"]); const HasElementTags = new Set([\"base\", \"meta\", \"link\", \"style\", \"script\", \"noscript\"]); const ValidHeadTags = new Set([\"title\", \"base\", \"htmlAttrs\", \"bodyAttrs\", \"meta\", \"link\", \"style\", \"script\", \"noscript\"]); const UniqueTags = new Set([\"base\", \"title\", \"titleTemplate\", \"bodyAttrs\", \"htmlAttrs\", \"templateParams\"]); const TagConfigKeys = new Set([\"key\", \"tagPosition\", \"tagPriority\", \"tagDuplicateStrategy\", \"innerHTML\", \"textContent\", \"processTemplateParams\"]); const UsesMergeStrategy = new Set([\"templateParams\", \"htmlAttrs\", \"bodyAttrs\"]); const MetaTagsArrayable = new Set([ \"theme-color\", \"google-site-verification\", \"og\", \"article\", \"book\", \"profile\", \"twitter\", \"author\" ]); function callHook(head, hook, ctx) { return head.hooks?.callHook(hook, ctx); } const META_NOREWRITE_RE = /^(?:viewport|description|keywords|robots)$/; function isMetaArrayDupeKey(v) { return MetaTagsArrayable.has(v.split(\":\")[1]); } function dedupeKey(tag) { const { props, tag: t, key } = tag; if (UniqueTags.has(t)) return t; if (t === \"link\" && props.rel === \"canonical\") return \"canonical\"; if (t === \"link\" && props.rel === \"alternate\") { const altKey = props.hreflang || props.type; if (altKey) return `alternate:${altKey}`; } if (props.charset) return \"charset\"; if (t === \"meta\") { for (const n of [\"name\", \"property\", \"http-equiv\"]) { const v = props[n]; if (v !== void 0) return `meta:${v}${(typeof v !== \"string\" || !v.includes(\":\")) && !META_NOREWRITE_RE.test(v) && key ? `:key:${key}` : \"\"}`; } } if (key) return `${t}:key:${key}`; if (props.id) return `${t}:id:${props.id}`; if (t === \"link\" && props.rel === \"alternate\") return `alternate:${props.href || \"\"}`; return TagsWithInnerContent.has(t) && (tag.textContent || tag.innerHTML) ? `${t}:content:${tag.textContent || tag.innerHTML}` : void 0; } function hashTag(tag) { return tag._h || tag._d || tag.textContent || tag.innerHTML || `${tag.tag}:${Object.entries(tag.props).map(([k, v]) => `${k}:${String(v)}`).join()}`; } function walkResolver(val, resolve, key) { if (key === \"_resolver\") return val; if (typeof val === \"function\" && (!key || key !== \"titleTemplate\" && !key.startsWith(\"on\"))) val = val(); const v = resolve ? resolve(key, val) : val; if (Array.isArray(v)) return v.map((r) => walkResolver(r, resolve)); if (v?.constructor === Object) { const next = {}; for (const k in v) { if (k === \"__proto__\" || k === \"constructor\" || k === \"prototype\") continue; next[k] = walkResolver(v[k], resolve, k); } return next; } return v; } function normalizeStyleClassProps(key, value) { const isStyle = key === \"style\"; const store = isStyle ? new Map() : new Set(); const add = (v) => { if (!v) return; if (isStyle) { const i = v.indexOf(\":\"); i > 0 && store.set(v.slice(0, i).trim(), v.slice(i + 1).trim()); } else { v.split(\" \").forEach((c) => c && store.add(c)); } }; if (typeof value === \"string\") { (isStyle ? value.split(\";\") : [value]).forEach(add); } else if (Array.isArray(value)) { value.forEach(add); } else if (value && typeof value === \"object\") { for (const k in value) { const v = value[k]; v && v !== \"false\" && (isStyle ? store.set(k.trim(), String(v)) : add(k)); } } return store; } function normalizeProps(tag, input) { tag.props = tag.props || {}; if (!input) return tag; if (tag.tag === \"templateParams\") { tag.props = input; return tag; } const isHtmlTag = HasElementTags.has(tag.tag) || tag.tag === \"htmlAttrs\" || tag.tag === \"bodyAttrs\"; for (const prop in input) { if (prop === \"__proto__\" || prop === \"constructor\" || prop === \"prototype\") continue; const value = input[prop]; if (value === null) { tag.props[prop] = null; } else if (prop === \"class\" || prop === \"style\") { tag.props[prop] = normalizeStyleClassProps(prop, value); } else if (TagConfigKeys.has(prop)) { if ((prop === \"textContent\" || prop === \"innerHTML\") && typeof value === \"object\") { const type = input.type || \"application/json\"; if (type.endsWith(\"json\") || type === \"speculationrules\" || type === \"importmap\") { tag.props.type = input.type = type; tag[prop] = JSON.stringify(value); } } else { tag[prop] = value; } } else if (value !== void 0) { const isData = prop.startsWith(\"data-\"); const key = isHtmlTag && !isData ? prop.toLowerCase() : prop; const str = String(value); const isMeta = tag.tag === \"meta\" && key === \"content\"; tag.props[key] = str === \"true\" || str === \"\" ? isData || isMeta ? str : true : !value && isData && str === \"false\" ? \"false\" : value; } } return tag; } function normalizeTag(tagName, _input) { const input = typeof _input === \"object\" && typeof _input !== \"function\" ? _input : { [tagName === \"script\" || tagName === \"noscript\" || tagName === \"style\" ? \"innerHTML\" : \"textContent\"]: _input }; const tag = normalizeProps({ tag: tagName, props: {} }, input); if (tag.key && DupeableTags.has(tag.tag)) tag.props[\"data-hid\"] = tag._h = tag.key; if (tag.tag === \"script\" && typeof tag.innerHTML === \"object\") { tag.innerHTML = JSON.stringify(tag.innerHTML); tag.props.type = tag.props.type || \"application/json\"; } return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag; } function normalizeEntryToTags(input, propResolvers) { if (!input) return []; if (typeof input === \"function\") input = input(); const resolvers = (key, val) => { for (const r of propResolvers) val = r(key, val); return val; }; input = walkResolver(resolvers(void 0, input), resolvers); const tags = []; for (const key in input) { const value = input[key]; if (value !== void 0) { for (const v of Array.isArray(value) ? value : [value]) tags.push(normalizeTag(key, v)); } } return tags.flat(); } const LT_RE = /</g; const SCRIPT_END_RE = /<\\/script/g; const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w; function dedupeTags(ctx) { let hasFlatMeta = false; for (const next of ctx.tags.sort(sortTags)) { const k = next._d || hashTag(next); const prev = ctx.tagMap.get(k); if (!prev) { ctx.tagMap.set(k, next); continue; } const strategy = next.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? \"merge\" : null) || (next.key && next.key === prev.key ? \"merge\" : null); if (strategy === \"merge\") { const props = { ...prev.props }; for (const p in next.props) { props[p] = p === \"style\" ? new Map([...prev.props.style || new Map(), ...next.props[p]]) : p === \"class\" ? new Set([...prev.props.class || [], ...next.props[p]]) : next.props[p]; } ctx.tagMap.set(k, { ...next, props }); } else if (next._p >> 10 === prev._p >> 10 && next.tag === \"meta\" && isMetaArrayDupeKey(k)) { ctx.tagMap.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) { ctx.tagMap.set(k, next); } } return hasFlatMeta; } function resolveTitleTemplate(ctx, head) { const title = ctx.tagMap.get(\"title\"); const tpl = ctx.tagMap.get(\"titleTemplate\"); head._title = title?.textContent; if (!tpl) return; const fn = tpl.textContent; head._titleTemplate = fn; if (!fn) return; let v = typeof fn === \"function\" ? fn(title?.textContent) : fn; if (typeof v === \"string\" && !head.plugins.has(\"template-params\")) v = v.replace(\"%s\", title?.textContent || \"\"); if (title) { v === null ? ctx.tagMap.delete(\"title\") : ctx.tagMap.set(\"title\", { ...title, textContent: v }); } else { ctx.tagMap.set(\"titleTemplate\", { ...tpl, tag: \"title\", textContent: v }); } } function sanitizeTags(tags) { return tags.filter((t) => { const { innerHTML, tag, props } = t; if (!ValidHeadTags.has(tag) || !Object.keys(props).length && !innerHTML && !t.textContent) return false; if (tag === \"meta\" && !props.content && !props[\"http-equiv\"] && !props.charset) return false; if (tag === \"script\" && (innerHTML || t.textContent)) { const type = String(props.type); const isJsonLike = type.endsWith(\"json\") || type === \"importmap\" || type === \"speculationrules\"; const escape = (content) => isJsonLike ? (typeof content === \"string\" ? content : JSON.stringify(content)).replace(LT_RE, \"\\\\u003C\") : typeof content === \"string\" ? content.replace(SCRIPT_END_RE, \"<\\\\/script\") : content; if (innerHTML) t.innerHTML = escape(innerHTML); if (t.textContent) t.textContent = escape(t.textContent); t._d = dedupeKey(t); } return true; }); } function resolveTags(head, options) { const weightFn = options?.tagWeight ?? head.resolvedOptions._tagWeight ?? (() => 100); const ctx = { tagMap: new Map(), tags: [] }; const entries = [...head.entries.values()]; for (const e of entries) { if (e._pending !== void 0) { e.input = e._pending; delete e._pending; delete e._tags; } } callHook(head, \"entries:resolve\", { entries, ...ctx }); for (const e of entries) { if (!e._tags) { const normalizeCtx = { tags: normalizeEntryToTags(e.input, head.resolvedOptions.propResolvers || []).map((t) => Object.assign(t, e.options)), entry: e }; callHook(head, \"entries:normalize\", normalizeCtx); e._tags = normalizeCtx.tags.map((t, i) => { t._w = weightFn(t); t._p = (e._i << 10) + i; t._d = dedupeKey(t); if (!t._d) t._h = hashTag(t); return t; }); } } ctx.tags = entries.flatMap((e) => (e._tags || []).map((t) => ({ ...t, props: { ...t.props } }))); const hasFlatMeta = dedupeTags(ctx); resolveTitleTemplate(ctx, head); ctx.tags = [...ctx.tagMap.values()]; if (hasFlatMeta) ctx.tags = ctx.tags.flat().sort(sortTags); callHook(head, \"tags:beforeResolve\", ctx); callHook(head, \"tags:resolve\", ctx); callHook(head, \"tags:afterResolve\", ctx); return sanitizeTags(ctx.tags); } const WHITESPACE_RE = /\\s+/; function createDomRenderer(options = {}) { return (head) => _renderDOMHead(head, options); } function _renderDOMHead(head, options = {}) { const dom = options.document || head.resolvedOptions.document; if (!dom || !head.dirty && ![...head.entries.values()].some((e) => e._pending !== void 0)) return false; const beforeRenderCtx = { shouldRender: true, tags: [] }; callHook(head, \"dom:beforeRender\", beforeRenderCtx); if (!beforeRenderCtx.shouldRender || head._du) return false; head._du = true; let state = head._dom; if (!state) { state = { _t: dom.title, _e: new Map([[\"htmlAttrs\", dom.documentElement], [\"bodyAttrs\", dom.body]]), _p: {}, _s: {} }; for (const el of [...dom.body.children, ...dom.head.children]) { const tag = el.tagName.toLowerCase(); if (!HasElementTags.has(tag)) continue; const props = { innerHTML: el.innerHTML }; for (const n of el.getAttributeNames()) props[n] = el.getAttribute(n); const next = normalizeProps({ tag, props: {} }, props); next.key = el.getAttribute(\"data-hid\") || void 0; let k = next._d = dedupeKey(next) || hashTag(next); let c = 1; while (state._e.has(k)) k = `${next._d}:${c++}`; state._e.set(k, el); } for (const entry of head.entries.values()) { if (entry._o !== void 0) { const orig = entry._o; for (const t of [\"bodyAttrs\", \"htmlAttrs\"]) { const cls = orig[t]?.class; if (typeof cls === \"string\") { const $el = state._e.get(t); for (const c of cls.split(WHITESPACE_RE)) { if (c) state._p[`${t}:attr:class:${c}`] = () => $el.classList.remove(c); } } } delete entry._o; } } } else { state._p = { ...state._s }; } state._s = {}; function track(id, scope, fn) { const k = `${id}:${scope}`; state._s[k] = fn; delete state._p[k]; } function trackCtx({ id, $el, tag }) { const isAttr = tag.tag.endsWith(\"Attrs\"); state._e.set(id, $el); if (!isAttr) { if (tag.textContent && tag.textContent !== $el.textContent) $el.textContent = tag.textContent; if (tag.innerHTML && tag.innerHTML !== $el.innerHTML) $el.innerHTML = tag.innerHTML; track(id, \"el\", () => { $el?.remove(); state._e.delete(id); }); } for (const k in tag.props) { const v = tag.props[k]; if (k[0] === \"o\" && k[1] === \"n\" && typeof v === \"function\") { const ev = k.slice(2); if ($el?.dataset?.[`${k}fired`]) v.call($el, new Event(ev)); if ($el.getAttribute(`data-${k}`) !== \"\") { (tag.tag === \"bodyAttrs\" ? dom.defaultView : $el).addEventListener(ev, v.bind($el)); $el.setAttribute(`data-${k}`, \"\"); } continue; } const ck = `attr:${k}`; if (k === \"class\" && v) { for (const c of v) { if (isAttr) track(id, `${ck}:${c}`, () => $el.classList.remove(c)); if (!$el.classList.contains(c)) $el.classList.add(c); } } else if (k === \"style\" && v) { for (const [sk, sv] of v) { track(id, `${ck}:${sk}`, () => $el.style.removeProperty(sk)); $el.style.setProperty(sk, sv); } } else if (v !== false && v !== null) { if ($el.getAttribute(k) !== v) $el.setAttribute(k, v === true ? \"\" : String(v)); if (isAttr) track(id, ck, () => $el.removeAttribute(k)); } } } const pending = []; const frag = {}; const rawTags = resolveTags(head, options.tagWeight ? { tagWeight: options.tagWeight } : void 0); const tags = []; const dupeKeyCounter = new Map(); for (const tag of rawTags) { const count = dupeKeyCounter.get(tag._d) || 0; const id = (count ? `${tag._d}:${count}` : tag._d) || tag._h; const ctx = { tag, id, shouldRender: true }; if (tag._d && isMetaArrayDupeKey(tag._d)) dupeKeyCounter.set(tag._d, count + 1); tags.push(ctx); if (tag.tag === \"title\") { dom.title = tag.textContent; track(\"title\", \"\", () => dom.title = state._t); continue; } ctx.$el = state._e.get(id); if (ctx.$el) trackCtx(ctx); else if (HasElementTags.has(tag.tag)) pending.push(ctx); } for (const ctx of pending) { ctx.$el = dom.createElement(ctx.tag.tag); trackCtx(ctx); (frag[ctx.tag.tagPosition || \"head\"] ??= dom.createDocumentFragment()).appendChild(ctx.$el); } if (frag.head) dom.head.appendChild(frag.head); if (frag.bodyOpen) dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild); if (frag.bodyClose) dom.body.appendChild(frag.bodyClose); for (const k in state._p) state._p[k](); head._dom = state; callHook(head, \"dom:rendered\", { renders: tags }); head._du = false; head.dirty = false; return true; } function registerPlugin(head, p) { const plugin = typeof p === \"function\" ? p(head) : p; const key = plugin.key || String(head.plugins.size + 1); if (!head.plugins.get(key)) { head.plugins.set(key, plugin); for (const k in plugin.hooks || {}) head.hooks?.hook(k, plugin.hooks[k]); } } function createUnhead(renderer, resolvedOptions = {}) { const ssr = !resolvedOptions.document; const entries = new Map(); const plugins = new Map(); const head = { _entryCount: 1, plugins, resolvedOptions, ssr, entries, render: () => renderer(head), use: (p) => registerPlugin(head, p), push(input, _options) { const _i = _options?._index ?? head._entryCount++; const options = _options ? { ..._options } : {}; delete options.head; delete options.onRendered; const entry = { _i, input, options }; entries.set(_i, entry); const active = { _i, dispose() { entries.delete(_i); }, patch(input2) { if (ssr) { entry.input = input2; delete entry._tags; } else { entry._pending = input2; } if (!entries.has(_i)) entries.set(_i, entry); } }; return active; } }; resolvedOptions.init?.forEach((e) => e && head.push(e)); return head; } const DEFAULT_STREAM_KEY = \"__unhead__\"; function init(options = {}) { const { streamKey = DEFAULT_STREAM_KEY } = options; const win = typeof window !== \"undefined\" ? window : void 0; if (!win) return; const queue = win[streamKey]; if (queue?._head) return queue._head; const doc = typeof document !== \"undefined\" ? document : void 0; const head = createUnhead(createDomRenderer(), { document: doc }); let hydrationLocked = true; queueMicrotask(() => { hydrationLocked = false; }); function pushStreamed(entry) { const active = head.push(entry); const stored = head.entries.get(active._i); if (stored) stored._streamed = true; } if (queue?._q) { for (const entries of queue._q) { for (const entry of entries) { pushStreamed(entry); } } head.dirty = true; head.render(); } win[streamKey] = { _q: queue?._q || [], _head: head, _hydrationLocked: () => hydrationLocked, push: (entries) => { for (const entry of entries) { pushStreamed(entry); } head.dirty = true; head.render(); } }; return head; } init(); exports.init = init; return exports; })({});";
|
|
2
|
-
export const streamingIifeSize =
|
|
1
|
+
export const streamingIifeCode = "var __unhead_iife__ = (function (exports) { 'use strict'; const DupeableTags = new Set([\"link\", \"style\", \"script\", \"noscript\"]); const TagsWithInnerContent = new Set([\"title\", \"titleTemplate\", \"script\", \"style\", \"noscript\"]); const HasElementTags = new Set([\"base\", \"meta\", \"link\", \"style\", \"script\", \"noscript\"]); const ValidHeadTags = new Set([\"title\", \"base\", \"htmlAttrs\", \"bodyAttrs\", \"meta\", \"link\", \"style\", \"script\", \"noscript\"]); const UniqueTags = new Set([\"base\", \"title\", \"titleTemplate\", \"bodyAttrs\", \"htmlAttrs\", \"templateParams\"]); const TagConfigKeys = new Set([\"key\", \"tagPosition\", \"tagPriority\", \"tagDuplicateStrategy\", \"innerHTML\", \"textContent\", \"processTemplateParams\"]); const UsesMergeStrategy = new Set([\"templateParams\", \"htmlAttrs\", \"bodyAttrs\"]); const MetaTagsArrayable = new Set([ \"theme-color\", \"google-site-verification\", \"og\", \"article\", \"book\", \"profile\", \"twitter\", \"author\" ]); function callHook(head, hook, ctx) { return head.hooks?.callHook(hook, ctx); } const META_NOREWRITE_RE = /^(?:viewport|description|keywords|robots)$/; function isMetaArrayDupeKey(v) { return MetaTagsArrayable.has(v.split(\":\")[1]); } function dedupeKey(tag) { const { props, tag: t, key } = tag; if (UniqueTags.has(t)) return t; if (t === \"link\" && props.rel === \"canonical\") return \"canonical\"; if (t === \"link\" && props.rel === \"alternate\") { if (props.hreflang) return `alternate:${props.hreflang}`; if (props.type) return `alternate:${props.type}:${props.href || \"\"}`; } if (props.charset) return \"charset\"; if (t === \"meta\") { for (const n of [\"name\", \"property\", \"http-equiv\"]) { const v = props[n]; if (v !== void 0) return `meta:${v}${(typeof v !== \"string\" || !v.includes(\":\")) && !META_NOREWRITE_RE.test(v) && key ? `:key:${key}` : \"\"}`; } } if (key) return `${t}:key:${key}`; if (props.id) return `${t}:id:${props.id}`; if (t === \"link\" && props.rel === \"alternate\") return `alternate:${props.href || \"\"}`; return TagsWithInnerContent.has(t) && (tag.textContent || tag.innerHTML) ? `${t}:content:${tag.textContent || tag.innerHTML}` : void 0; } function hashTag(tag) { return tag._h || tag._d || tag.textContent || tag.innerHTML || `${tag.tag}:${Object.entries(tag.props).map(([k, v]) => `${k}:${String(v)}`).join()}`; } function walkResolver(val, resolve, key) { if (key === \"_resolver\") return val; if (typeof val === \"function\" && (!key || key !== \"titleTemplate\" && !key.startsWith(\"on\"))) val = val(); const v = resolve ? resolve(key, val) : val; if (Array.isArray(v)) return v.map((r) => walkResolver(r, resolve)); if (v?.constructor === Object) { const next = {}; for (const k in v) { if (k === \"__proto__\" || k === \"constructor\" || k === \"prototype\") continue; next[k] = walkResolver(v[k], resolve, k); } return next; } return v; } function normalizeStyleClassProps(key, value) { const isStyle = key === \"style\"; const store = isStyle ? new Map() : new Set(); const add = (v) => { if (!v) return; if (isStyle) { const i = v.indexOf(\":\"); i > 0 && store.set(v.slice(0, i).trim(), v.slice(i + 1).trim()); } else { v.split(\" \").forEach((c) => c && store.add(c)); } }; if (typeof value === \"string\") { (isStyle ? value.split(\";\") : [value]).forEach(add); } else if (Array.isArray(value)) { value.forEach(add); } else if (value && typeof value === \"object\") { for (const k in value) { const v = value[k]; v && v !== \"false\" && (isStyle ? store.set(k.trim(), String(v)) : add(k)); } } return store; } function normalizeProps(tag, input) { tag.props = tag.props || {}; if (!input) return tag; if (tag.tag === \"templateParams\") { tag.props = input; return tag; } const isHtmlTag = HasElementTags.has(tag.tag) || tag.tag === \"htmlAttrs\" || tag.tag === \"bodyAttrs\"; for (const prop in input) { if (prop === \"__proto__\" || prop === \"constructor\" || prop === \"prototype\") continue; const value = input[prop]; if (value === null) { tag.props[prop] = null; } else if (prop === \"class\" || prop === \"style\") { tag.props[prop] = normalizeStyleClassProps(prop, value); } else if (TagConfigKeys.has(prop)) { if ((prop === \"textContent\" || prop === \"innerHTML\") && typeof value === \"object\") { const type = input.type || \"application/json\"; if (type.endsWith(\"json\") || type === \"speculationrules\" || type === \"importmap\") { tag.props.type = input.type = type; tag[prop] = JSON.stringify(value); } } else { tag[prop] = value; } } else if (value !== void 0) { const isData = prop.startsWith(\"data-\"); const key = isHtmlTag && !isData ? prop.toLowerCase() : prop; const str = String(value); const isMeta = tag.tag === \"meta\" && key === \"content\"; tag.props[key] = str === \"true\" || str === \"\" ? isData || isMeta ? str : true : !value && isData && str === \"false\" ? \"false\" : value; } } return tag; } function normalizeTag(tagName, _input) { const input = typeof _input === \"object\" && typeof _input !== \"function\" ? _input : { [tagName === \"script\" || tagName === \"noscript\" || tagName === \"style\" ? \"innerHTML\" : \"textContent\"]: _input }; const tag = normalizeProps({ tag: tagName, props: {} }, input); if (tag.key && DupeableTags.has(tag.tag)) tag.props[\"data-hid\"] = tag._h = tag.key; if (tag.tag === \"script\" && typeof tag.innerHTML === \"object\") { tag.innerHTML = JSON.stringify(tag.innerHTML); tag.props.type = tag.props.type || \"application/json\"; } return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag; } function normalizeEntryToTags(input, propResolvers) { if (!input) return []; if (typeof input === \"function\") input = input(); const resolvers = (key, val) => { for (const r of propResolvers) val = r(key, val); return val; }; input = walkResolver(resolvers(void 0, input), resolvers); const tags = []; for (const key in input) { const value = input[key]; if (value !== void 0) { for (const v of Array.isArray(value) ? value : [value]) tags.push(normalizeTag(key, v)); } } return tags.flat(); } const LT_RE = /</g; const SCRIPT_END_RE = /<\\/script/g; const sortTags = (a, b) => a._w === b._w ? a._p - b._p : a._w - b._w; function dedupeTags(ctx) { let hasFlatMeta = false; for (const next of ctx.tags.sort(sortTags)) { const k = next._d || hashTag(next); const prev = ctx.tagMap.get(k); if (!prev) { ctx.tagMap.set(k, next); continue; } const strategy = next.tagDuplicateStrategy || (UsesMergeStrategy.has(next.tag) ? \"merge\" : null) || (next.key && next.key === prev.key ? \"merge\" : null); if (strategy === \"merge\") { const props = { ...prev.props }; for (const p in next.props) { props[p] = p === \"style\" ? new Map([...prev.props.style || new Map(), ...next.props[p]]) : p === \"class\" ? new Set([...prev.props.class || [], ...next.props[p]]) : next.props[p]; } ctx.tagMap.set(k, { ...next, props }); } else if (next._p >> 10 === prev._p >> 10 && next.tag === \"meta\" && isMetaArrayDupeKey(k)) { ctx.tagMap.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) { ctx.tagMap.set(k, next); } } return hasFlatMeta; } function resolveTitleTemplate(ctx, head) { const title = ctx.tagMap.get(\"title\"); const tpl = ctx.tagMap.get(\"titleTemplate\"); head._title = title?.textContent; if (!tpl) return; const fn = tpl.textContent; head._titleTemplate = fn; if (!fn) return; let v = typeof fn === \"function\" ? fn(title?.textContent) : fn; if (typeof v === \"string\" && !head.plugins.has(\"template-params\")) v = v.replace(\"%s\", title?.textContent || \"\"); if (title) { v === null ? ctx.tagMap.delete(\"title\") : ctx.tagMap.set(\"title\", { ...title, textContent: v }); } else { ctx.tagMap.set(\"titleTemplate\", { ...tpl, tag: \"title\", textContent: v }); } } function sanitizeTags(tags) { return tags.filter((t) => { const { innerHTML, tag, props } = t; if (!ValidHeadTags.has(tag) || !Object.keys(props).length && !innerHTML && !t.textContent) return false; if (tag === \"meta\" && !props.content && !props[\"http-equiv\"] && !props.charset) return false; if (tag === \"script\" && (innerHTML || t.textContent)) { const type = String(props.type); const isJsonLike = type.endsWith(\"json\") || type === \"importmap\" || type === \"speculationrules\"; const escape = (content) => isJsonLike ? (typeof content === \"string\" ? content : JSON.stringify(content)).replace(LT_RE, \"\\\\u003C\") : typeof content === \"string\" ? content.replace(SCRIPT_END_RE, \"<\\\\/script\") : content; if (innerHTML) t.innerHTML = escape(innerHTML); if (t.textContent) t.textContent = escape(t.textContent); t._d = dedupeKey(t); } return true; }); } function resolveTags(head, options) { const weightFn = options?.tagWeight ?? head.resolvedOptions._tagWeight ?? (() => 100); const ctx = { tagMap: new Map(), tags: [] }; const entries = [...head.entries.values()]; for (const e of entries) { if (e._pending !== void 0) { e.input = e._pending; delete e._pending; delete e._tags; } } callHook(head, \"entries:resolve\", { entries, ...ctx }); for (const e of entries) { if (!e._tags) { const normalizeCtx = { tags: normalizeEntryToTags(e.input, head.resolvedOptions.propResolvers || []).map((t) => Object.assign(t, e.options)), entry: e }; callHook(head, \"entries:normalize\", normalizeCtx); e._tags = normalizeCtx.tags.map((t, i) => { t._w = weightFn(t); t._p = (e._i << 10) + i; t._d = dedupeKey(t); if (!t._d) t._h = hashTag(t); return t; }); } } ctx.tags = entries.flatMap((e) => (e._tags || []).map((t) => ({ ...t, props: { ...t.props } }))); const hasFlatMeta = dedupeTags(ctx); resolveTitleTemplate(ctx, head); ctx.tags = [...ctx.tagMap.values()]; if (hasFlatMeta) ctx.tags = ctx.tags.flat().sort(sortTags); callHook(head, \"tags:beforeResolve\", ctx); callHook(head, \"tags:resolve\", ctx); callHook(head, \"tags:afterResolve\", ctx); return sanitizeTags(ctx.tags); } const WHITESPACE_RE = /\\s+/; function createDomRenderer(options = {}) { return (head) => _renderDOMHead(head, options); } function _renderDOMHead(head, options = {}) { const dom = options.document || head.resolvedOptions.document; if (!dom || !head.dirty && ![...head.entries.values()].some((e) => e._pending !== void 0)) return false; const beforeRenderCtx = { shouldRender: true, tags: [] }; callHook(head, \"dom:beforeRender\", beforeRenderCtx); if (!beforeRenderCtx.shouldRender || head._du) return false; head._du = true; let state = head._dom; if (!state) { state = { _t: dom.title, _e: new Map([[\"htmlAttrs\", dom.documentElement], [\"bodyAttrs\", dom.body]]), _p: {}, _s: {} }; for (const el of [...dom.body.children, ...dom.head.children]) { const tag = el.tagName.toLowerCase(); if (!HasElementTags.has(tag)) continue; const props = { innerHTML: el.innerHTML }; for (const n of el.getAttributeNames()) props[n] = el.getAttribute(n); const next = normalizeProps({ tag, props: {} }, props); next.key = el.getAttribute(\"data-hid\") || void 0; let k = next._d = dedupeKey(next) || hashTag(next); let c = 1; while (state._e.has(k)) k = `${next._d}:${c++}`; state._e.set(k, el); } for (const entry of head.entries.values()) { if (entry._o !== void 0) { const orig = entry._o; for (const t of [\"bodyAttrs\", \"htmlAttrs\"]) { const cls = orig[t]?.class; if (typeof cls === \"string\") { const $el = state._e.get(t); for (const c of cls.split(WHITESPACE_RE)) { if (c) state._p[`${t}:attr:class:${c}`] = () => $el.classList.remove(c); } } } delete entry._o; } } } else { state._p = { ...state._s }; } state._s = {}; function track(id, scope, fn) { const k = `${id}:${scope}`; state._s[k] = fn; delete state._p[k]; } function trackCtx({ id, $el, tag }) { const isAttr = tag.tag.endsWith(\"Attrs\"); state._e.set(id, $el); if (!isAttr) { if (tag.textContent && tag.textContent !== $el.textContent) $el.textContent = tag.textContent; if (tag.innerHTML && tag.innerHTML !== $el.innerHTML) $el.innerHTML = tag.innerHTML; track(id, \"el\", () => { $el?.remove(); state._e.delete(id); }); } for (const k in tag.props) { const v = tag.props[k]; if (k[0] === \"o\" && k[1] === \"n\" && typeof v === \"function\") { const ev = k.slice(2); if ($el?.dataset?.[`${k}fired`]) v.call($el, new Event(ev)); if ($el.getAttribute(`data-${k}`) !== \"\") { (tag.tag === \"bodyAttrs\" ? dom.defaultView : $el).addEventListener(ev, v.bind($el)); $el.setAttribute(`data-${k}`, \"\"); } continue; } const ck = `attr:${k}`; if (k === \"class\" && v) { for (const c of v) { if (isAttr) track(id, `${ck}:${c}`, () => $el.classList.remove(c)); if (!$el.classList.contains(c)) $el.classList.add(c); } } else if (k === \"style\" && v) { for (const [sk, sv] of v) { track(id, `${ck}:${sk}`, () => $el.style.removeProperty(sk)); $el.style.setProperty(sk, sv); } } else if (v !== false && v !== null) { if ($el.getAttribute(k) !== v) $el.setAttribute(k, v === true ? \"\" : String(v)); if (isAttr) track(id, ck, () => $el.removeAttribute(k)); } } } const pending = []; const frag = {}; const rawTags = resolveTags(head, options.tagWeight ? { tagWeight: options.tagWeight } : void 0); const tags = []; const dupeKeyCounter = new Map(); for (const tag of rawTags) { const count = dupeKeyCounter.get(tag._d) || 0; const id = (count ? `${tag._d}:${count}` : tag._d) || tag._h; const ctx = { tag, id, shouldRender: true }; if (tag._d && isMetaArrayDupeKey(tag._d)) dupeKeyCounter.set(tag._d, count + 1); tags.push(ctx); if (tag.tag === \"title\") { dom.title = tag.textContent; track(\"title\", \"\", () => dom.title = state._t); continue; } ctx.$el = state._e.get(id); if (ctx.$el) trackCtx(ctx); else if (HasElementTags.has(tag.tag)) pending.push(ctx); } for (const ctx of pending) { ctx.$el = dom.createElement(ctx.tag.tag); trackCtx(ctx); (frag[ctx.tag.tagPosition || \"head\"] ??= dom.createDocumentFragment()).appendChild(ctx.$el); } if (frag.head) dom.head.appendChild(frag.head); if (frag.bodyOpen) dom.body.insertBefore(frag.bodyOpen, dom.body.firstChild); if (frag.bodyClose) dom.body.appendChild(frag.bodyClose); for (const k in state._p) state._p[k](); head._dom = state; callHook(head, \"dom:rendered\", { renders: tags }); head._du = false; head.dirty = false; return true; } function registerPlugin(head, p) { const plugin = typeof p === \"function\" ? p(head) : p; const key = plugin.key || String(head.plugins.size + 1); if (!head.plugins.get(key)) { head.plugins.set(key, plugin); for (const k in plugin.hooks || {}) head.hooks?.hook(k, plugin.hooks[k]); } } function createUnhead(renderer, resolvedOptions = {}) { const ssr = !resolvedOptions.document; const entries = new Map(); const plugins = new Map(); const head = { _entryCount: 1, plugins, resolvedOptions, ssr, entries, render: () => renderer(head), use: (p) => registerPlugin(head, p), push(input, _options) { const _i = _options?._index ?? head._entryCount++; const options = _options ? { ..._options } : {}; delete options.head; delete options.onRendered; const entry = { _i, input, options }; entries.set(_i, entry); const active = { _i, dispose() { entries.delete(_i); }, patch(input2) { if (ssr) { entry.input = input2; delete entry._tags; } else { entry._pending = input2; } if (!entries.has(_i)) entries.set(_i, entry); } }; return active; } }; resolvedOptions.init?.forEach((e) => e && head.push(e)); return head; } const DEFAULT_STREAM_KEY = \"__unhead__\"; function init(options = {}) { const { streamKey = DEFAULT_STREAM_KEY } = options; const win = typeof window !== \"undefined\" ? window : void 0; if (!win) return; const queue = win[streamKey]; if (queue?._head) return queue._head; const doc = typeof document !== \"undefined\" ? document : void 0; const head = createUnhead(createDomRenderer(), { document: doc }); let hydrationLocked = true; queueMicrotask(() => { hydrationLocked = false; }); function pushStreamed(entry) { const active = head.push(entry); const stored = head.entries.get(active._i); if (stored) stored._streamed = true; } if (queue?._q) { for (const entries of queue._q) { for (const entry of entries) { pushStreamed(entry); } } head.dirty = true; head.render(); } win[streamKey] = { _q: queue?._q || [], _head: head, _hydrationLocked: () => hydrationLocked, push: (entries) => { for (const entry of entries) { pushStreamed(entry); } head.dirty = true; head.render(); } }; return head; } init(); exports.init = init; return exports; })({});";
|
|
2
|
+
export const streamingIifeSize = 16079;
|
package/dist/stream/server.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as ServerUnhead } from '../shared/unhead.
|
|
2
|
-
import { f as CreateStreamableServerHeadOptions, U as Unhead, s as SSRHeadPayload } from '../shared/unhead.
|
|
3
|
-
import { aw as ResolvableHead } from '../shared/unhead.
|
|
1
|
+
import { S as ServerUnhead } from '../shared/unhead.CwvlUXnx.mjs';
|
|
2
|
+
import { f as CreateStreamableServerHeadOptions, U as Unhead, s as SSRHeadPayload } from '../shared/unhead.BodZ6XwK.mjs';
|
|
3
|
+
import { aw as ResolvableHead } from '../shared/unhead.Bz11580x.mjs';
|
|
4
4
|
import 'hookable';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -67,9 +67,10 @@ declare function createStreamableHead<T = ResolvableHead>(options?: CreateStream
|
|
|
67
67
|
* use this directly to inject the bootstrap into your shell `<head>`.
|
|
68
68
|
*
|
|
69
69
|
* @param streamKey - The window property name for the stream queue (default: '__unhead__')
|
|
70
|
+
* @param nonce - Optional CSP nonce to stamp on the script tag
|
|
70
71
|
* @returns An inline `<script>` tag string
|
|
71
72
|
*/
|
|
72
|
-
declare function createBootstrapScript(streamKey?: string): string;
|
|
73
|
+
declare function createBootstrapScript(streamKey?: string, nonce?: string): string;
|
|
73
74
|
/**
|
|
74
75
|
* Renders the current head state and clears entries atomically.
|
|
75
76
|
*
|
|
@@ -166,8 +167,10 @@ interface StreamingTemplateParts {
|
|
|
166
167
|
/**
|
|
167
168
|
* @experimental
|
|
168
169
|
*
|
|
169
|
-
* Prepares a template for streaming SSR by splitting it at
|
|
170
|
-
*
|
|
170
|
+
* Prepares a template for streaming SSR by splitting it at the SSR outlet
|
|
171
|
+
* marker (`<!--app-html-->` / `<!--ssr-outlet-->`) when present, so the
|
|
172
|
+
* streamed app content lands inside the mount container. Falls back to
|
|
173
|
+
* splitting at body tag boundaries when no marker is found.
|
|
171
174
|
*
|
|
172
175
|
* This is the recommended way to handle streaming templates as it:
|
|
173
176
|
* - Uses consistent template parsing (same as transformHtmlTemplateRaw)
|
package/dist/stream/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as ServerUnhead } from '../shared/unhead.
|
|
2
|
-
import { f as CreateStreamableServerHeadOptions, U as Unhead, s as SSRHeadPayload } from '../shared/unhead.
|
|
3
|
-
import { aw as ResolvableHead } from '../shared/unhead.
|
|
1
|
+
import { S as ServerUnhead } from '../shared/unhead.DbMowiUH.js';
|
|
2
|
+
import { f as CreateStreamableServerHeadOptions, U as Unhead, s as SSRHeadPayload } from '../shared/unhead.BWNOp3cv.js';
|
|
3
|
+
import { aw as ResolvableHead } from '../shared/unhead.Bz11580x.js';
|
|
4
4
|
import 'hookable';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -67,9 +67,10 @@ declare function createStreamableHead<T = ResolvableHead>(options?: CreateStream
|
|
|
67
67
|
* use this directly to inject the bootstrap into your shell `<head>`.
|
|
68
68
|
*
|
|
69
69
|
* @param streamKey - The window property name for the stream queue (default: '__unhead__')
|
|
70
|
+
* @param nonce - Optional CSP nonce to stamp on the script tag
|
|
70
71
|
* @returns An inline `<script>` tag string
|
|
71
72
|
*/
|
|
72
|
-
declare function createBootstrapScript(streamKey?: string): string;
|
|
73
|
+
declare function createBootstrapScript(streamKey?: string, nonce?: string): string;
|
|
73
74
|
/**
|
|
74
75
|
* Renders the current head state and clears entries atomically.
|
|
75
76
|
*
|
|
@@ -166,8 +167,10 @@ interface StreamingTemplateParts {
|
|
|
166
167
|
/**
|
|
167
168
|
* @experimental
|
|
168
169
|
*
|
|
169
|
-
* Prepares a template for streaming SSR by splitting it at
|
|
170
|
-
*
|
|
170
|
+
* Prepares a template for streaming SSR by splitting it at the SSR outlet
|
|
171
|
+
* marker (`<!--app-html-->` / `<!--ssr-outlet-->`) when present, so the
|
|
172
|
+
* streamed app content lands inside the mount container. Falls back to
|
|
173
|
+
* splitting at body tag boundaries when no marker is found.
|
|
171
174
|
*
|
|
172
175
|
* This is the recommended way to handle streaming templates as it:
|
|
173
176
|
* - Uses consistent template parsing (same as transformHtmlTemplateRaw)
|
package/dist/stream/server.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { parseHtmlForIndexes, applyHeadToHtml } from '../parser.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { a as createHead } from '../shared/unhead.Bu5O0NaA.mjs';
|
|
3
3
|
import { DEFAULT_STREAM_KEY } from './client.mjs';
|
|
4
4
|
import '../shared/unhead.CfgPMHXt.mjs';
|
|
5
|
-
import '../shared/unhead.
|
|
5
|
+
import '../shared/unhead.mB5lMBMV.mjs';
|
|
6
6
|
import 'hookable';
|
|
7
|
-
import '../shared/unhead.
|
|
7
|
+
import '../shared/unhead.DzSj5qjO.mjs';
|
|
8
8
|
import '../shared/unhead.fg-0ge_u.mjs';
|
|
9
9
|
|
|
10
10
|
const LT_RE = /</g;
|
|
@@ -42,9 +42,10 @@ function getStreamKey(head) {
|
|
|
42
42
|
assertValidStreamKey(key);
|
|
43
43
|
return key;
|
|
44
44
|
}
|
|
45
|
-
function createBootstrapScript(streamKey = DEFAULT_STREAM_KEY) {
|
|
45
|
+
function createBootstrapScript(streamKey = DEFAULT_STREAM_KEY, nonce) {
|
|
46
46
|
assertValidStreamKey(streamKey);
|
|
47
|
-
|
|
47
|
+
const nonceAttr = nonce ? ` nonce="${nonce.replace(/"/g, """)}"` : "";
|
|
48
|
+
return `<script${nonceAttr}>window.${streamKey}={_q:[],push(e){this._q.push(e)}}<\/script>`;
|
|
48
49
|
}
|
|
49
50
|
function renderShell(head) {
|
|
50
51
|
const result = head.render();
|
|
@@ -110,8 +111,18 @@ function prepareStreamingTemplate(head, template, preRenderedState) {
|
|
|
110
111
|
const bodyEnd = parsed.indexes.bodyTagEnd;
|
|
111
112
|
const bodyCloseStart = parsed.indexes.bodyCloseTagStart;
|
|
112
113
|
if (bodyEnd >= 0 && bodyCloseStart >= 0) {
|
|
113
|
-
const shellPart = template.substring(0, bodyEnd);
|
|
114
114
|
const bodyInterior = template.substring(bodyEnd, bodyCloseStart);
|
|
115
|
+
const markerMatch = bodyInterior.match(/<!--\s*(?:app-html|ssr-outlet)\s*-->/);
|
|
116
|
+
let beforeStream;
|
|
117
|
+
let afterStream;
|
|
118
|
+
if (markerMatch) {
|
|
119
|
+
beforeStream = bodyInterior.substring(0, markerMatch.index);
|
|
120
|
+
afterStream = bodyInterior.substring(markerMatch.index + markerMatch[0].length);
|
|
121
|
+
} else {
|
|
122
|
+
beforeStream = "";
|
|
123
|
+
afterStream = bodyInterior;
|
|
124
|
+
}
|
|
125
|
+
const shellPart = template.substring(0, bodyEnd) + beforeStream;
|
|
115
126
|
const endPart = template.substring(bodyCloseStart);
|
|
116
127
|
const shellParsed = parseHtmlForIndexes(`${shellPart}</body></html>`);
|
|
117
128
|
const shell = applyHeadToHtml(shellParsed, {
|
|
@@ -122,7 +133,7 @@ function prepareStreamingTemplate(head, template, preRenderedState) {
|
|
|
122
133
|
}).replace("</body></html>", "");
|
|
123
134
|
return {
|
|
124
135
|
shell,
|
|
125
|
-
end:
|
|
136
|
+
end: afterStream + ssr.bodyTags + endPart
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
139
|
return {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as unplugin from 'unplugin';
|
|
2
|
+
import { UnpluginOptions } from 'unplugin';
|
|
3
|
+
|
|
4
|
+
declare const VIRTUAL_CLIENT_ID = "virtual:@unhead/streaming-client";
|
|
5
|
+
declare const VIRTUAL_IIFE_ID = "virtual:@unhead/streaming-iife.js";
|
|
6
|
+
type Nonce = string | (() => string | undefined);
|
|
7
|
+
interface StreamingPluginOptions {
|
|
8
|
+
/** Framework package e.g. '@unhead/vue' */
|
|
9
|
+
framework: string;
|
|
10
|
+
/** Plugin name (optional, defaults to `${framework}:streaming`) */
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* File extension filter for transform hook, e.g. /\.vue$/. Optional;
|
|
14
|
+
* only required by frameworks whose client streaming support relies on
|
|
15
|
+
* source-level AST injection (React/Solid/Svelte). Vue does not use it.
|
|
16
|
+
*/
|
|
17
|
+
filter?: RegExp;
|
|
18
|
+
/** Transform handler called for files matching `filter`. */
|
|
19
|
+
transform?: (code: string, id: string, options?: {
|
|
20
|
+
ssr?: boolean;
|
|
21
|
+
}) => {
|
|
22
|
+
code: string;
|
|
23
|
+
map?: any;
|
|
24
|
+
} | null | undefined | void;
|
|
25
|
+
/**
|
|
26
|
+
* How to load the streaming client (vite-only, ignored on webpack/rspack/rollup where
|
|
27
|
+
* index.html injection isn't available; frameworks inject the iife themselves in SSR).
|
|
28
|
+
* - 'async' (default): Non-blocking external script. In dev served from a virtual
|
|
29
|
+
* module; in production emitted as a real asset chunk via `emitFile`.
|
|
30
|
+
* - 'inline': Inline the IIFE directly in HTML. Largest HTML, smallest TTFB,
|
|
31
|
+
* always safe in production. Recommended for streaming SSR.
|
|
32
|
+
* - 'module': ES module dynamic import of the client bootstrap. Vite rewrites the
|
|
33
|
+
* import path through its module graph so it survives production builds.
|
|
34
|
+
* @default 'async'
|
|
35
|
+
*/
|
|
36
|
+
mode?: 'async' | 'inline' | 'module';
|
|
37
|
+
/**
|
|
38
|
+
* CSP nonce forwarded on every injected `<script>` tag. Pass a string or a
|
|
39
|
+
* function returning a string (useful when the nonce rotates per request).
|
|
40
|
+
* Omit to inject without a nonce.
|
|
41
|
+
*/
|
|
42
|
+
nonce?: Nonce;
|
|
43
|
+
/**
|
|
44
|
+
* Stream key global name; must match `experimentalStreamKey` on the server
|
|
45
|
+
* head instance. Used by dev-mode warnings to detect when the server
|
|
46
|
+
* bootstrap script hasn't run (common misconfig).
|
|
47
|
+
* @default '__unhead__'
|
|
48
|
+
*/
|
|
49
|
+
streamKey?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Emit a warning when the client IIFE runs but no server bootstrap queue
|
|
52
|
+
* has been installed (i.e. server didn't call `wrapStream` /
|
|
53
|
+
* `renderSSRHeadShell`). Dev-only.
|
|
54
|
+
* @default true in dev, false in prod
|
|
55
|
+
*/
|
|
56
|
+
warnOnMissingServerBootstrap?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds the bundler-agnostic unplugin hook set for the streaming plugin. Exposed so
|
|
60
|
+
* framework wrappers (e.g. `@unhead/vue/bundler`) can bake in their own
|
|
61
|
+
* `framework`, `filter`, and `transform` while still using this factory
|
|
62
|
+
* to produce hooks that work across vite/webpack/rspack/rollup/esbuild via `createUnplugin`.
|
|
63
|
+
*
|
|
64
|
+
* SSR detection is bundler-specific:
|
|
65
|
+
* - vite build: `config.env.isSsrBuild`
|
|
66
|
+
* - vite dev (v6+ environments): `this.environment.name === 'ssr'` per-transform
|
|
67
|
+
* - webpack/rspack: `compiler.options.name === 'server'` or `target === 'node'`
|
|
68
|
+
*/
|
|
69
|
+
declare function buildStreamingPluginOptions(options: StreamingPluginOptions): UnpluginOptions;
|
|
70
|
+
/**
|
|
71
|
+
* Internal cross-bundler unplugin factory. Framework wrappers pick a single bundler's
|
|
72
|
+
* output (`.vite`, `.webpack`, `.rspack`, etc.) to expose via their own subpath export.
|
|
73
|
+
*
|
|
74
|
+
* Consumers should prefer the unified framework bundler entry (e.g.
|
|
75
|
+
* `@unhead/{vue,react,svelte,solid-js}/bundler`) rather than importing this
|
|
76
|
+
* directly.
|
|
77
|
+
*/
|
|
78
|
+
declare const createStreamingPlugin: unplugin.UnpluginInstance<StreamingPluginOptions, boolean>;
|
|
79
|
+
|
|
80
|
+
export { VIRTUAL_CLIENT_ID, VIRTUAL_IIFE_ID, buildStreamingPluginOptions, createStreamingPlugin };
|
|
81
|
+
export type { Nonce, StreamingPluginOptions };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as unplugin from 'unplugin';
|
|
2
|
+
import { UnpluginOptions } from 'unplugin';
|
|
3
|
+
|
|
4
|
+
declare const VIRTUAL_CLIENT_ID = "virtual:@unhead/streaming-client";
|
|
5
|
+
declare const VIRTUAL_IIFE_ID = "virtual:@unhead/streaming-iife.js";
|
|
6
|
+
type Nonce = string | (() => string | undefined);
|
|
7
|
+
interface StreamingPluginOptions {
|
|
8
|
+
/** Framework package e.g. '@unhead/vue' */
|
|
9
|
+
framework: string;
|
|
10
|
+
/** Plugin name (optional, defaults to `${framework}:streaming`) */
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* File extension filter for transform hook, e.g. /\.vue$/. Optional;
|
|
14
|
+
* only required by frameworks whose client streaming support relies on
|
|
15
|
+
* source-level AST injection (React/Solid/Svelte). Vue does not use it.
|
|
16
|
+
*/
|
|
17
|
+
filter?: RegExp;
|
|
18
|
+
/** Transform handler called for files matching `filter`. */
|
|
19
|
+
transform?: (code: string, id: string, options?: {
|
|
20
|
+
ssr?: boolean;
|
|
21
|
+
}) => {
|
|
22
|
+
code: string;
|
|
23
|
+
map?: any;
|
|
24
|
+
} | null | undefined | void;
|
|
25
|
+
/**
|
|
26
|
+
* How to load the streaming client (vite-only, ignored on webpack/rspack/rollup where
|
|
27
|
+
* index.html injection isn't available; frameworks inject the iife themselves in SSR).
|
|
28
|
+
* - 'async' (default): Non-blocking external script. In dev served from a virtual
|
|
29
|
+
* module; in production emitted as a real asset chunk via `emitFile`.
|
|
30
|
+
* - 'inline': Inline the IIFE directly in HTML. Largest HTML, smallest TTFB,
|
|
31
|
+
* always safe in production. Recommended for streaming SSR.
|
|
32
|
+
* - 'module': ES module dynamic import of the client bootstrap. Vite rewrites the
|
|
33
|
+
* import path through its module graph so it survives production builds.
|
|
34
|
+
* @default 'async'
|
|
35
|
+
*/
|
|
36
|
+
mode?: 'async' | 'inline' | 'module';
|
|
37
|
+
/**
|
|
38
|
+
* CSP nonce forwarded on every injected `<script>` tag. Pass a string or a
|
|
39
|
+
* function returning a string (useful when the nonce rotates per request).
|
|
40
|
+
* Omit to inject without a nonce.
|
|
41
|
+
*/
|
|
42
|
+
nonce?: Nonce;
|
|
43
|
+
/**
|
|
44
|
+
* Stream key global name; must match `experimentalStreamKey` on the server
|
|
45
|
+
* head instance. Used by dev-mode warnings to detect when the server
|
|
46
|
+
* bootstrap script hasn't run (common misconfig).
|
|
47
|
+
* @default '__unhead__'
|
|
48
|
+
*/
|
|
49
|
+
streamKey?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Emit a warning when the client IIFE runs but no server bootstrap queue
|
|
52
|
+
* has been installed (i.e. server didn't call `wrapStream` /
|
|
53
|
+
* `renderSSRHeadShell`). Dev-only.
|
|
54
|
+
* @default true in dev, false in prod
|
|
55
|
+
*/
|
|
56
|
+
warnOnMissingServerBootstrap?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds the bundler-agnostic unplugin hook set for the streaming plugin. Exposed so
|
|
60
|
+
* framework wrappers (e.g. `@unhead/vue/bundler`) can bake in their own
|
|
61
|
+
* `framework`, `filter`, and `transform` while still using this factory
|
|
62
|
+
* to produce hooks that work across vite/webpack/rspack/rollup/esbuild via `createUnplugin`.
|
|
63
|
+
*
|
|
64
|
+
* SSR detection is bundler-specific:
|
|
65
|
+
* - vite build: `config.env.isSsrBuild`
|
|
66
|
+
* - vite dev (v6+ environments): `this.environment.name === 'ssr'` per-transform
|
|
67
|
+
* - webpack/rspack: `compiler.options.name === 'server'` or `target === 'node'`
|
|
68
|
+
*/
|
|
69
|
+
declare function buildStreamingPluginOptions(options: StreamingPluginOptions): UnpluginOptions;
|
|
70
|
+
/**
|
|
71
|
+
* Internal cross-bundler unplugin factory. Framework wrappers pick a single bundler's
|
|
72
|
+
* output (`.vite`, `.webpack`, `.rspack`, etc.) to expose via their own subpath export.
|
|
73
|
+
*
|
|
74
|
+
* Consumers should prefer the unified framework bundler entry (e.g.
|
|
75
|
+
* `@unhead/{vue,react,svelte,solid-js}/bundler`) rather than importing this
|
|
76
|
+
* directly.
|
|
77
|
+
*/
|
|
78
|
+
declare const createStreamingPlugin: unplugin.UnpluginInstance<StreamingPluginOptions, boolean>;
|
|
79
|
+
|
|
80
|
+
export { VIRTUAL_CLIENT_ID, VIRTUAL_IIFE_ID, buildStreamingPluginOptions, createStreamingPlugin };
|
|
81
|
+
export type { Nonce, StreamingPluginOptions };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createUnplugin } from 'unplugin';
|
|
2
|
+
|
|
3
|
+
const VIRTUAL_CLIENT_ID = "virtual:@unhead/streaming-client";
|
|
4
|
+
const VIRTUAL_IIFE_ID = "virtual:@unhead/streaming-iife.js";
|
|
5
|
+
const RESOLVED_ID = `\0${VIRTUAL_CLIENT_ID}`;
|
|
6
|
+
const RESOLVED_IIFE_ID = `\0${VIRTUAL_IIFE_ID}`;
|
|
7
|
+
const VIRTUAL_RE = /virtual:@unhead\/streaming/;
|
|
8
|
+
const RESOLVED_RE = /^\0virtual:@unhead\/streaming/;
|
|
9
|
+
let iifeCode;
|
|
10
|
+
let iifeCodeLoading;
|
|
11
|
+
async function loadIifeCode() {
|
|
12
|
+
if (iifeCode)
|
|
13
|
+
return;
|
|
14
|
+
iifeCodeLoading ||= import('unhead/stream/iife').then((mod) => {
|
|
15
|
+
iifeCode = mod.streamingIifeCode;
|
|
16
|
+
});
|
|
17
|
+
await iifeCodeLoading;
|
|
18
|
+
}
|
|
19
|
+
function resolveNonce(nonce) {
|
|
20
|
+
if (!nonce)
|
|
21
|
+
return void 0;
|
|
22
|
+
return typeof nonce === "function" ? nonce() : nonce;
|
|
23
|
+
}
|
|
24
|
+
function buildClientStub(framework, streamKey, warnOnMissing) {
|
|
25
|
+
const key = JSON.stringify(streamKey);
|
|
26
|
+
const warnBranch = warnOnMissing ? `else{console.warn('[unhead] streaming client loaded but window['+${key}+'] is undefined; did the server call wrapStream()/renderSSRHeadShell()?')}` : "";
|
|
27
|
+
return `import{createHead}from'${framework}/client'
|
|
28
|
+
const s=window[${key}];if(s){const q=s._q;s._q=[];const h=createHead({document});q.forEach(e=>h.push(e));s.push=e=>h.push(e);s._head=h}${warnBranch}`;
|
|
29
|
+
}
|
|
30
|
+
function buildStreamingPluginOptions(options) {
|
|
31
|
+
const {
|
|
32
|
+
framework,
|
|
33
|
+
name,
|
|
34
|
+
mode = "async",
|
|
35
|
+
nonce,
|
|
36
|
+
streamKey = "__unhead__",
|
|
37
|
+
warnOnMissingServerBootstrap
|
|
38
|
+
} = options;
|
|
39
|
+
const state = {
|
|
40
|
+
isBuild: false,
|
|
41
|
+
ssr: false
|
|
42
|
+
};
|
|
43
|
+
function isSSRCall(hookThis, opts) {
|
|
44
|
+
const envName = hookThis?.environment?.name;
|
|
45
|
+
return envName === "ssr" || envName === "server" || opts?.ssr === true || state.ssr;
|
|
46
|
+
}
|
|
47
|
+
function warnEnabled() {
|
|
48
|
+
return warnOnMissingServerBootstrap ?? !state.isBuild;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: name ?? `${framework}:streaming`,
|
|
52
|
+
enforce: "pre",
|
|
53
|
+
async buildStart() {
|
|
54
|
+
await loadIifeCode();
|
|
55
|
+
if (mode === "async" && state.isBuild && typeof this.emitFile === "function") {
|
|
56
|
+
if (!iifeCode)
|
|
57
|
+
throw new Error("[unhead] Streaming IIFE not built. Run `pnpm build` in packages/unhead first.");
|
|
58
|
+
state.emittedIifeFileName = this.emitFile({
|
|
59
|
+
type: "asset",
|
|
60
|
+
name: "unhead-streaming.js",
|
|
61
|
+
source: iifeCode
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
resolveId: {
|
|
66
|
+
filter: { id: VIRTUAL_RE },
|
|
67
|
+
handler(id) {
|
|
68
|
+
if (id === VIRTUAL_CLIENT_ID || id === `/${VIRTUAL_CLIENT_ID}`)
|
|
69
|
+
return RESOLVED_ID;
|
|
70
|
+
if (id === VIRTUAL_IIFE_ID || id === `/${VIRTUAL_IIFE_ID}`)
|
|
71
|
+
return RESOLVED_IIFE_ID;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
load: {
|
|
75
|
+
filter: { id: RESOLVED_RE },
|
|
76
|
+
handler(id, opts) {
|
|
77
|
+
const isSSR = isSSRCall(this, opts);
|
|
78
|
+
if (id === RESOLVED_ID) {
|
|
79
|
+
if (isSSR)
|
|
80
|
+
return { code: "export {}", moduleType: "js" };
|
|
81
|
+
return {
|
|
82
|
+
code: buildClientStub(framework, streamKey, warnEnabled()),
|
|
83
|
+
moduleType: "js"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (id === RESOLVED_IIFE_ID) {
|
|
87
|
+
if (isSSR)
|
|
88
|
+
return { code: "", moduleType: "js" };
|
|
89
|
+
if (!iifeCode)
|
|
90
|
+
throw new Error("[unhead] Streaming IIFE not built. Run `pnpm build` in packages/unhead first.");
|
|
91
|
+
return { code: iifeCode, moduleType: "js" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
...options.transform && options.filter ? {
|
|
96
|
+
transform: {
|
|
97
|
+
filter: { id: options.filter },
|
|
98
|
+
handler(code, id, opts) {
|
|
99
|
+
return options.transform(code, id, { ssr: isSSRCall(this, opts) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} : {},
|
|
103
|
+
webpack(compiler) {
|
|
104
|
+
const { name: n, target } = compiler.options;
|
|
105
|
+
if (n === "server" || target === "node" || target === "async-node")
|
|
106
|
+
state.ssr = true;
|
|
107
|
+
},
|
|
108
|
+
rspack(compiler) {
|
|
109
|
+
const { name: n, target } = compiler.options;
|
|
110
|
+
if (n === "server" || target === "node" || target === "async-node")
|
|
111
|
+
state.ssr = true;
|
|
112
|
+
},
|
|
113
|
+
vite: {
|
|
114
|
+
apply(_config, env) {
|
|
115
|
+
if (env.isSsrBuild)
|
|
116
|
+
state.ssr = true;
|
|
117
|
+
if (env.command === "build")
|
|
118
|
+
state.isBuild = true;
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
configResolved(config) {
|
|
122
|
+
if (config.command === "build")
|
|
123
|
+
state.isBuild = true;
|
|
124
|
+
},
|
|
125
|
+
transformIndexHtml: {
|
|
126
|
+
// `order: 'pre'` is separate from the plugin-level `enforce: 'pre'`:
|
|
127
|
+
// it runs this HTML transform before other non-pre HTML transforms
|
|
128
|
+
// so the virtual module `<script>` tags we inject go through the
|
|
129
|
+
// full Vite plugin pipeline (resolveId/load) and aren't stripped or
|
|
130
|
+
// rewritten by downstream HTML transforms.
|
|
131
|
+
order: "pre",
|
|
132
|
+
handler() {
|
|
133
|
+
const nonceValue = resolveNonce(nonce);
|
|
134
|
+
const nonceAttr = nonceValue ? { nonce: nonceValue } : {};
|
|
135
|
+
if (mode === "inline") {
|
|
136
|
+
if (!iifeCode)
|
|
137
|
+
throw new Error("[unhead] Streaming IIFE not built. Run `pnpm build` in packages/unhead first.");
|
|
138
|
+
return [{
|
|
139
|
+
tag: "script",
|
|
140
|
+
attrs: nonceAttr,
|
|
141
|
+
children: iifeCode,
|
|
142
|
+
injectTo: "head-prepend"
|
|
143
|
+
}];
|
|
144
|
+
}
|
|
145
|
+
if (mode === "async") {
|
|
146
|
+
const src = state.isBuild && state.emittedIifeFileName ? `/${state.emittedIifeFileName}` : `/${VIRTUAL_IIFE_ID}`;
|
|
147
|
+
return [{
|
|
148
|
+
tag: "script",
|
|
149
|
+
attrs: { ...nonceAttr, async: true, src },
|
|
150
|
+
injectTo: "head-prepend"
|
|
151
|
+
}];
|
|
152
|
+
}
|
|
153
|
+
return [{
|
|
154
|
+
tag: "script",
|
|
155
|
+
attrs: nonceAttr,
|
|
156
|
+
children: `import("/${VIRTUAL_CLIENT_ID}")`,
|
|
157
|
+
injectTo: "head-prepend"
|
|
158
|
+
}];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const createStreamingPlugin = createUnplugin(buildStreamingPluginOptions);
|
|
165
|
+
|
|
166
|
+
export { VIRTUAL_CLIENT_ID, VIRTUAL_IIFE_ID, buildStreamingPluginOptions, createStreamingPlugin };
|