nuxt-og-image
Version:
Enlightened OG Image generation for Nuxt.
121 lines (120 loc) • 4.15 kB
JavaScript
import { fontCache } from "#og-image-cache";
import { theme } from "#og-image-virtual/unocss-config.mjs";
import compatibility from "#og-image/compatibility";
import { defu } from "defu";
import { sendError } from "h3";
import { normaliseFontInput, useOgImageRuntimeConfig } from "../../../shared.js";
import { loadFont } from "./font.js";
import { useResvg, useSatori, useSharp } from "./instances.js";
import { createVNodes } from "./vnodes.js";
const fontPromises = {};
async function resolveFonts(event) {
const { fonts } = useOgImageRuntimeConfig();
const normalisedFonts = normaliseFontInput([...event.options.fonts || [], ...fonts]);
const localFontPromises = [];
const preloadedFonts = [];
if (fontCache) {
for (const font of normalisedFonts) {
if (await fontCache.hasItem(font.cacheKey)) {
font.data = await fontCache.getItemRaw(font.cacheKey);
preloadedFonts.push(font);
} else {
if (!fontPromises[font.cacheKey]) {
fontPromises[font.cacheKey] = loadFont(event, font).then(async (_font) => {
if (_font?.data)
await fontCache?.setItemRaw(_font.cacheKey, _font.data);
return _font;
});
}
localFontPromises.push(fontPromises[font.cacheKey]);
}
}
}
const awaitedFonts = await Promise.all(localFontPromises);
return [...preloadedFonts, ...awaitedFonts].map((_f) => {
return { name: _f.name, data: _f.data, style: _f.style, weight: Number(_f.weight) };
});
}
export async function createSvg(event) {
const { options } = event;
const { satoriOptions: _satoriOptions } = useOgImageRuntimeConfig();
const [satori, vnodes, fonts] = await Promise.all([
useSatori(),
createVNodes(event),
resolveFonts(event)
]);
await event._nitro.hooks.callHook("nuxt-og-image:satori:vnodes", vnodes, event);
const satoriOptions = defu(options.satori, _satoriOptions, {
fonts,
tailwindConfig: { theme },
embedFont: true,
width: options.width,
height: options.height
});
return satori(vnodes, satoriOptions).catch((err) => {
return sendError(event.e, err, import.meta.dev);
});
}
async function createPng(event) {
const { resvgOptions } = useOgImageRuntimeConfig();
const svg = await createSvg(event);
const Resvg = await useResvg();
const resvg = new Resvg(svg, defu(
event.options.resvg,
resvgOptions
));
const pngData = resvg.render();
return pngData.asPng();
}
async function createJpeg(event) {
const { sharpOptions } = useOgImageRuntimeConfig();
if (compatibility.sharp === false) {
if (import.meta.dev) {
throw new Error("Sharp dependency is not accessible. Please check you have it installed and are using a compatible runtime.");
} else {
console.error("Sharp dependency is not accessible. Please check you have it installed and are using a compatible runtime. Falling back to png.");
}
return createPng(event);
}
const svg = await createSvg(event);
if (!svg) {
throw new Error("Failed to create SVG for JPEG rendering.");
}
const svgBuffer = Buffer.from(svg);
const sharp = await useSharp().catch(() => {
if (import.meta.dev) {
throw new Error("Sharp dependency could not be loaded. Please check you have it installed and are using a compatible runtime.");
}
return null;
});
if (!sharp) {
console.error("Sharp dependency is not accessible. Please check you have it installed and are using a compatible runtime. Falling back to png.");
return createPng(event);
}
const options = defu(event.options.sharp, sharpOptions);
return sharp(svgBuffer, options).jpeg(options).toBuffer();
}
const SatoriRenderer = {
name: "satori",
supportedFormats: ["png", "jpeg", "jpg", "json"],
async createImage(e) {
switch (e.extension) {
case "png":
return createPng(e);
case "jpeg":
case "jpg":
return createJpeg(e);
}
},
async debug(e) {
const [vnodes, svg] = await Promise.all([
createVNodes(e),
createSvg(e)
]);
return {
vnodes,
svg
};
}
};
export default SatoriRenderer;