nuxt-og-image
Version:
Enlightened OG Image generation for Nuxt.
112 lines (110 loc) • 3.46 kB
JavaScript
import { theme } from "#og-image-virtual/unocss-config.mjs";
import { createHeadCore } from "@unhead/vue";
import { renderSSRHead } from "@unhead/vue/server";
import { createError } from "h3";
import { normaliseFontInput, useOgImageRuntimeConfig } from "../../../shared.js";
import { fetchIsland } from "../../util/kit.js";
import { applyEmojis } from "../satori/transforms/emojis.js";
export async function html(ctx) {
const { options } = ctx;
const { fonts } = useOgImageRuntimeConfig();
if (!options.component) {
throw createError({
statusCode: 500,
statusMessage: `[Nuxt OG Image] Rendering an invalid component. Received options: ${JSON.stringify(options)}.`
});
}
const island = await fetchIsland(ctx.e, ctx.options.component, typeof ctx.options.props !== "undefined" ? ctx.options.props : ctx.options);
const head = createHeadCore();
head.push(island.head);
let defaultFontFamily = "sans-serif";
const normalisedFonts = normaliseFontInput([...options.fonts || [], ...fonts]);
const firstFont = normalisedFonts[0];
if (firstFont)
defaultFontFamily = firstFont.name.replaceAll("+", " ");
await applyEmojis(ctx, island);
let html2 = island.html;
head.push({
style: [
{
// default font is the first font family
innerHTML: `body { font-family: '${defaultFontFamily}', sans-serif; }`
},
{
innerHTML: `body {
transform: scale(${options.props?.scale || 1});
transform-origin: top left;
max-height: 100vh;
position: relative;
width: ${options.width}px;
height: ${options.height}px;
overflow: hidden;
background-color: ${options.props?.colorMode === "dark" ? "#1b1b1b" : "#fff"};
}
div {
display: flex;
}
div:has(div, p, ul, ol, li, blockquote, pre, hr, table, dl) {
display: flex;
flex-direction: column;
}
div:not(:has(div, p, ul, ol, li, blockquote, pre, hr, table, dl)) {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
svg[data-emoji] {
display: inline-block;
}
`
},
...fonts.map((font) => {
return `
@font-face {
font-family: '${font.name.replaceAll("+", " ")}';
font-style: normal;
font-weight: ${font.weight};
src: url('/__og-image__/font/${font.key}') format('truetype');
}
`;
})
],
meta: [
{
charset: "utf-8"
}
],
script: [
{
src: "https://cdn.jsdelivr.net/npm/@unocss/runtime/preset-wind.global.js"
},
{
innerHTML: `
window.__unocss = {
theme: ${JSON.stringify(theme)},
presets: [
() => window.__unocss_runtime.presets.presetWind(),
],
}
`
},
{
src: "https://cdn.jsdelivr.net/npm/@unocss/runtime/core.global.js"
}
],
link: [
{
// reset css to match svg output
href: "https://cdn.jsdelivr.net/npm/gardevoir",
rel: "stylesheet"
}
]
});
html2 = html2.replaceAll(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
const headChunk = await renderSSRHead(head);
return `<!DOCTYPE html>
<html ${headChunk.htmlAttrs}>
<head>${headChunk.headTags}</head>
<body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div data-v-inspector-ignore="true" style="position: relative; display: flex; margin: 0 auto; width: ${options.width}px; height: ${options.height}px; overflow: hidden;">${html2}</div>${headChunk.bodyTags}</body>
</html>`;
}