A1300399510 69bfb3b262 1111
2025-02-20 19:30:25 +08:00

96 lines
2.8 KiB
JavaScript

import { TagsWithInnerContent, SelfClosingTags } from '@unhead/shared';
function encodeAttribute(value) {
return String(value).replace(/"/g, """);
}
function propsToString(props) {
let attrs = "";
for (const key in props) {
if (!Object.prototype.hasOwnProperty.call(props, key)) {
continue;
}
const value = props[key];
if (value !== false && value !== null) {
attrs += value === true ? ` ${key}` : ` ${key}="${encodeAttribute(value)}"`;
}
}
return attrs;
}
function ssrRenderTags(tags, options) {
const schema = { htmlAttrs: {}, bodyAttrs: {}, tags: { head: "", bodyClose: "", bodyOpen: "" } };
const lineBreaks = !options?.omitLineBreaks ? "\n" : "";
for (const tag of tags) {
if (Object.keys(tag.props).length === 0 && !tag.innerHTML && !tag.textContent) {
continue;
}
if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
Object.assign(schema[tag.tag], tag.props);
continue;
}
const s = tagToString(tag);
const tagPosition = tag.tagPosition || "head";
schema.tags[tagPosition] += schema.tags[tagPosition] ? `${lineBreaks}${s}` : s;
}
return {
headTags: schema.tags.head,
bodyTags: schema.tags.bodyClose,
bodyTagsOpen: schema.tags.bodyOpen,
htmlAttrs: propsToString(schema.htmlAttrs),
bodyAttrs: propsToString(schema.bodyAttrs)
};
}
function escapeHtml(str) {
return str.replace(/[&<>"'/]/g, (char) => {
switch (char) {
case "&":
return "&amp;";
case "<":
return "&lt;";
case ">":
return "&gt;";
case '"':
return "&quot;";
case "'":
return "&#x27;";
case "/":
return "&#x2F;";
default:
return char;
}
});
}
function tagToString(tag) {
const attrs = propsToString(tag.props);
const openTag = `<${tag.tag}${attrs}>`;
if (!TagsWithInnerContent.has(tag.tag))
return SelfClosingTags.has(tag.tag) ? openTag : `${openTag}</${tag.tag}>`;
let content = String(tag.innerHTML || "");
if (tag.textContent)
content = escapeHtml(String(tag.textContent));
return SelfClosingTags.has(tag.tag) ? openTag : `${openTag}${content}</${tag.tag}>`;
}
async function renderSSRHead(head, options) {
const beforeRenderCtx = { shouldRender: true };
await head.hooks.callHook("ssr:beforeRender", beforeRenderCtx);
if (!beforeRenderCtx.shouldRender) {
return {
headTags: "",
bodyTags: "",
bodyTagsOpen: "",
htmlAttrs: "",
bodyAttrs: ""
};
}
const ctx = { tags: await head.resolveTags() };
await head.hooks.callHook("ssr:render", ctx);
const html = ssrRenderTags(ctx.tags, options);
const renderCtx = { tags: ctx.tags, html };
await head.hooks.callHook("ssr:rendered", renderCtx);
return renderCtx.html;
}
export { escapeHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString };