nuxt-og-image
Version:
Enlightened OG Image generation for Nuxt.
782 lines (769 loc) • 31.2 kB
JavaScript
import * as fs from 'node:fs';
import { existsSync } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import { useNuxt, addTemplate, loadNuxtModuleInstance, createResolver, resolvePath, tryResolveModule, defineNuxtModule, useLogger, addImports, hasNuxtModule, addServerPlugin, addServerHandler, addComponentsDir, addComponent, addPlugin } from '@nuxt/kit';
import { assertSiteConfig, installNuxtSiteConfig } from 'nuxt-site-config-kit';
import { provider, env, isCI, isDevelopment } from 'std-env';
import { hash } from 'ohash';
import { relative, dirname, basename, join } from 'pathe';
import { defu } from 'defu';
import { readPackageJSON } from 'pkg-types';
import { createStorage } from 'unstorage';
import fsDriver from 'unstorage/drivers/fs';
import { withoutLeadingSlash } from 'ufo';
import { ensureDependencyInstalled } from 'nypm';
import { relative as relative$1 } from 'node:path';
import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
import { createHash } from 'node:crypto';
import { Launcher } from 'chrome-launcher';
import { $fetch } from 'ofetch';
const autodetectableProviders = {
azure_static: "azure",
cloudflare_pages: "cloudflare-pages",
netlify: "netlify",
stormkit: "stormkit",
vercel: "vercel",
cleavr: "cleavr",
stackblitz: "stackblitz"
};
const autodetectableStaticProviders = {
netlify: "netlify-static",
vercel: "vercel-static"
};
const NodeRuntime = {
// node-server runtime
"chromium": "on-demand",
// this gets changed build start
"css-inline": "node",
"resvg": "node",
"satori": "node",
"sharp": "node"
// will be disabled if they're missing the dependency
};
const cloudflare = {
"chromium": false,
"css-inline": false,
"resvg": "wasm",
"satori": "node",
"sharp": false,
"wasm": {
esmImport: true,
lazy: true
}
};
const awsLambda = {
"chromium": false,
"css-inline": "node",
"resvg": "node",
"satori": "node",
"sharp": false
// 0.33.x has issues
};
const WebContainer = {
"chromium": false,
"css-inline": "wasm-fs",
"resvg": "wasm-fs",
"satori": "wasm-fs",
"sharp": false
};
const RuntimeCompatibility = {
"nitro-dev": NodeRuntime,
"nitro-prerender": NodeRuntime,
"node-server": NodeRuntime,
"stackblitz": WebContainer,
"codesandbox": WebContainer,
"aws-lambda": awsLambda,
"netlify": awsLambda,
"netlify-edge": {
"chromium": false,
"css-inline": "wasm",
"resvg": "wasm",
"satori": "node",
"sharp": false,
"wasm": {
rollup: {
targetEnv: "auto-inline",
sync: ["@resvg/resvg-wasm/index_bg.wasm"]
}
}
},
"firebase": awsLambda,
"vercel": awsLambda,
"vercel-edge": {
"chromium": false,
"css-inline": false,
// size constraint (2mb is max)
"resvg": "wasm",
"satori": "node",
"sharp": false,
"wasm": {
// lowers workers kb size
esmImport: true,
lazy: true
}
},
"cloudflare-pages": cloudflare,
"cloudflare": cloudflare,
"cloudflare-module": cloudflare
};
function detectTarget(options = {}) {
return options?.static ? autodetectableStaticProviders[provider] : autodetectableProviders[provider];
}
function resolveNitroPreset(nitroConfig) {
if (provider === "stackblitz" || provider === "codesandbox")
return provider;
const nuxt = useNuxt();
if (nuxt.options.dev)
return "nitro-dev";
if (nuxt.options._generate)
return "nitro-prerender";
let preset;
if (nitroConfig && nitroConfig?.preset)
preset = nitroConfig.preset;
if (!preset)
preset = env.NITRO_PRESET || detectTarget() || "node-server";
return preset.replace("_", "-");
}
function getPresetNitroPresetCompatibility(target) {
let compatibility = RuntimeCompatibility[target];
if (!compatibility)
compatibility = RuntimeCompatibility["nitro-dev"];
return compatibility;
}
function applyNitroPresetCompatibility(nitroConfig, options) {
const target = resolveNitroPreset(nitroConfig);
const compatibility = getPresetNitroPresetCompatibility(target);
const { resolve } = options;
const satoriEnabled = typeof options.compatibility?.satori !== "undefined" ? !!options.compatibility.satori : !!compatibility.satori;
const chromiumEnabled = typeof options.compatibility?.chromium !== "undefined" ? !!options.compatibility.chromium : !!compatibility.chromium;
nitroConfig.alias["#nuxt-og-image/renderers/satori"] = satoriEnabled ? resolve("./runtime/nitro/og-image/satori/renderer") : "unenv/runtime/mock/empty";
nitroConfig.alias["#nuxt-og-image/renderers/chromium"] = chromiumEnabled ? resolve("./runtime/nitro/og-image/chromium/renderer") : "unenv/runtime/mock/empty";
const resolvedCompatibility = {};
function applyBinding(key) {
let binding = options.compatibility?.[key];
if (typeof binding === "undefined")
binding = compatibility[key];
if (key === "chromium" && binding === "node")
binding = "playwright";
resolvedCompatibility[key] = binding;
return {
[`#nuxt-og-image/bindings/${key}`]: binding === false ? "unenv/runtime/mock/empty" : resolve(`./runtime/nitro/og-image/bindings/${key}/${binding}`)
};
}
nitroConfig.alias = defu(
applyBinding("chromium"),
applyBinding("satori"),
applyBinding("resvg"),
applyBinding("sharp"),
applyBinding("css-inline"),
nitroConfig.alias || {}
);
if (Object.values(compatibility).includes("wasm")) {
nitroConfig.experimental = nitroConfig.experimental || {};
nitroConfig.experimental.wasm = true;
}
nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
nitroConfig.wasm = defu(compatibility.wasm, nitroConfig.wasm);
nitroConfig.virtual["#nuxt-og-image/compatibility"] = () => `export default ${JSON.stringify(resolvedCompatibility)}`;
addTemplate({
filename: "nuxt-og-image/compatibility.mjs",
getContents() {
return `export default ${JSON.stringify(resolvedCompatibility)}`;
},
options: { mode: "server" }
});
return resolvedCompatibility;
}
function ensureDependencies(dep, nuxt = useNuxt()) {
return Promise.all(dep.map((d) => {
return ensureDependencyInstalled(d, {
cwd: nuxt.options.rootDir,
dev: true
});
}));
}
async function getNuxtModuleOptions(module, nuxt = useNuxt()) {
const moduleMeta = (typeof module === "string" ? { name: module } : await module.getMeta?.()) || {};
const { nuxtModule } = await loadNuxtModuleInstance(module, nuxt);
let moduleEntry;
for (const m of nuxt.options.modules) {
if (Array.isArray(m) && m.length >= 2) {
const _module = m[0];
const _moduleEntryName = typeof _module === "string" ? _module : (await _module.getMeta?.())?.name || "";
if (_moduleEntryName === moduleMeta.name)
moduleEntry = m;
}
}
let inlineOptions = {};
if (moduleEntry)
inlineOptions = moduleEntry[1];
if (nuxtModule.getOptions)
return nuxtModule.getOptions(inlineOptions, nuxt);
return inlineOptions;
}
function extendTypes(module, template) {
const nuxt = useNuxt();
const { resolve } = createResolver(import.meta.url);
addTemplate({
filename: `module/${module}.d.ts`,
getContents: async () => {
const typesPath = relative(resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"), resolve("runtime/types"));
const s = await template({ typesPath });
return `// Generated by ${module}
${s}
export {}
`;
}
});
nuxt.hooks.hook("prepare:types", ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, `module/${module}.d.ts`) });
});
nuxt.hooks.hook("nitro:config", (config) => {
config.typescript = config.typescript || {};
config.typescript.tsConfig = config.typescript.tsConfig || {};
config.typescript.tsConfig.include = config.typescript.tsConfig.include || [];
config.typescript.tsConfig.include.push(`./module/${module}.d.ts`);
});
}
function isNuxtGenerate(nuxt = useNuxt()) {
return nuxt.options._generate || nuxt.options.nitro.static || nuxt.options.nitro.preset === "static";
}
const DEVTOOLS_UI_ROUTE = "/__nuxt-og-image";
const DEVTOOLS_UI_LOCAL_PORT = 3030;
function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
const clientPath = resolve("./client");
const isProductionBuild = existsSync(clientPath);
if (isProductionBuild) {
nuxt.hook("vite:serverCreated", async (server) => {
const sirv = await import('sirv').then((r) => r.default || r);
server.middlewares.use(
DEVTOOLS_UI_ROUTE,
sirv(clientPath, { dev: true, single: true })
);
});
} else {
nuxt.hook("vite:extendConfig", (config) => {
config.server = config.server || {};
config.server.proxy = config.server.proxy || {};
config.server.proxy[DEVTOOLS_UI_ROUTE] = {
target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
changeOrigin: true,
followRedirects: true,
rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
};
});
}
onDevToolsInitialized(async () => {
const rpc = extendServerRpc("nuxt-og-image", {});
nuxt.hook("builder:watch", (e, path) => {
path = relative$1(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path));
if ((e === "change" || e.includes("link")) && path.startsWith("pages")) {
rpc.broadcast.refreshRouteData(path).catch(() => {
});
}
if (options.componentDirs.some((dir) => path.includes(dir))) {
if (e === "change") {
rpc.broadcast.refresh().catch(() => {
});
} else {
rpc.broadcast.refreshGlobalData().catch(() => {
});
}
}
});
});
nuxt.hook("devtools:customTabs", (tabs) => {
tabs.push({
// unique identifier
name: "nuxt-og-image",
// title to display in the tab
title: "OG Image",
// any icon from Iconify, or a URL to an image
icon: "carbon:image-search",
// iframe view
view: {
type: "iframe",
src: DEVTOOLS_UI_ROUTE
}
});
});
}
function setupDevHandler(options, resolve, nuxt = useNuxt()) {
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
applyNitroPresetCompatibility(nitroConfig, { compatibility: options.compatibility?.dev, resolve });
});
}
function setupGenerateHandler(options, resolve, nuxt = useNuxt()) {
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
applyNitroPresetCompatibility(nitroConfig, {
compatibility: {
"chromium": false,
"satori": false,
"css-inline": false,
"resvg": false,
"sharp": false
},
resolve
});
assertSiteConfig("nuxt-og-image", {
url: "OG Image tags are required to be absolute URLs."
}, {
throwError: true
});
});
}
function setupPrerenderHandler(options, resolve, nuxt = useNuxt()) {
nuxt.hooks.hook("nitro:init", async (nitro) => {
nitro.hooks.hook("prerender:config", async (nitroConfig) => {
applyNitroPresetCompatibility(nitroConfig, { compatibility: options.compatibility?.prerender, resolve });
nitroConfig.wasm = nitroConfig.wasm || {};
nitroConfig.wasm.esmImport = false;
const prerenderingPages = (nuxt.options.nitro.prerender?.routes || []).some((r) => r && (!r.includes(".") || r.includes("*")));
prerenderingPages && assertSiteConfig("nuxt-og-image", {
url: "OG Image tags are required to be absolute URLs."
}, {
throwError: false
});
});
});
}
async function setupBuildHandler(config, resolve, nuxt = useNuxt()) {
nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
if (typeof config.runtimeCacheStorage === "object")
nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
applyNitroPresetCompatibility(nitroConfig, { compatibility: config.compatibility?.runtime, resolve });
nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
nitroConfig.alias.queue = "unenv/runtime/mock/proxy-cjs";
});
nuxt.hooks.hook("nitro:init", async (nitro) => {
nitro.hooks.hook("compiled", async (_nitro) => {
const target = resolveNitroPreset(_nitro.options);
const compatibility = getPresetNitroPresetCompatibility(target);
if (compatibility.wasm?.esmImport !== true)
return;
const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
let serverEntry = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
const isCloudflarePagesOrModule = target === "cloudflare-pages" || target === "cloudflare-module";
if (isCloudflarePagesOrModule) {
serverEntry = [
resolve(dirname(serverEntry), "./chunks/wasm.mjs"),
resolve(dirname(serverEntry), "./chunks/_/wasm.mjs")
].filter(existsSync)[0] || serverEntry;
}
const contents = await readFile(serverEntry, "utf-8");
const resvgHash = sha1(await readFile(await resolvePath("@resvg/resvg-wasm/index_bg.wasm")));
const yogaHash = sha1(await readFile(await resolvePath("yoga-wasm-web/dist/yoga.wasm")));
const cssInlineHash = sha1(await readFile(await resolvePath("@css-inline/css-inline-wasm/index_bg.wasm")));
const postfix = target === "vercel-edge" ? "?module" : "";
const path = isCloudflarePagesOrModule ? `../wasm/` : `./wasm/`;
await writeFile(serverEntry, contents.replaceAll('"@resvg/resvg-wasm/index_bg.wasm?module"', `"${path}index_bg-${resvgHash}.wasm${postfix}"`).replaceAll('"@css-inline/css-inline-wasm/index_bg.wasm?module"', `"${path}index_bg-${cssInlineHash}.wasm${postfix}"`).replaceAll('"yoga-wasm-web/dist/yoga.wasm?module"', `"${path}yoga-${yogaHash}.wasm${postfix}"`), { encoding: "utf-8" });
});
});
}
function sha1(source) {
return createHash("sha1").update(source).digest("hex").slice(0, 16);
}
const isUndefinedOrTruthy = (v) => typeof v === "undefined" || v !== false;
function checkLocalChrome() {
if (isCI)
return false;
let hasChromeLocally = false;
try {
hasChromeLocally = !!Launcher.getFirstInstallation();
} catch {
}
return hasChromeLocally;
}
async function checkPlaywrightDependency() {
return !!await tryResolveModule("playwright");
}
async function downloadFont(font, storage, mirror) {
const { name, weight } = font;
const key = `${name}-${weight}.ttf.base64`;
if (await storage.hasItem(key))
return true;
const host = typeof mirror === "undefined" ? "fonts.googleapis.com" : mirror === true ? "fonts.font.im" : mirror;
const css = await $fetch(`https://${host}/css2?family=${name}:wght@${weight}`, {
timeout: 10 * 1e3,
// 10 second timeout
headers: {
// Make sure it returns TTF.
"User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"
}
}).catch(() => {
return false;
});
if (!css)
return false;
const ttfResource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
if (ttfResource?.[1]) {
const buf = await $fetch(ttfResource[1], { baseURL: host, responseType: "arrayBuffer" });
const base64Font = Buffer.from(buf).toString("base64");
await storage.setItem(key, base64Font);
return true;
}
return false;
}
function normaliseFontInput(fonts) {
return fonts.map((f) => {
if (typeof f === "string") {
const [name, weight] = f.split(":");
return {
cacheKey: f,
name,
weight: weight || 400,
style: "normal",
path: void 0
};
}
return {
cacheKey: f.key || `${f.name}:${f.weight}`,
style: "normal",
weight: 400,
...f
};
});
}
const module = defineNuxtModule({
meta: {
name: "nuxt-og-image",
compatibility: {
nuxt: "^3.10.3",
bridge: false
},
configKey: "ogImage"
},
defaults() {
return {
enabled: true,
defaults: {
emojis: "noto",
renderer: "satori",
component: "NuxtSeo",
extension: "png",
width: 1200,
height: 600,
// default is to cache the image for 3 day (72 hours)
cacheMaxAgeSeconds: 60 * 60 * 24 * 3
},
componentDirs: ["OgImage", "OgImageTemplate"],
fonts: [],
runtimeCacheStorage: true,
debug: isDevelopment
};
},
async setup(config, nuxt) {
const { resolve } = createResolver(import.meta.url);
const { name, version } = await readPackageJSON(resolve("../package.json"));
const logger = useLogger(name);
logger.level = config.debug || nuxt.options.debug ? 4 : 3;
if (config.enabled === false) {
logger.debug("The module is disabled, skipping setup.");
["defineOgImage", "defineOgImageComponent", "defineOgImageScreenshot"].forEach((name2) => {
addImports({ name: name2, from: resolve(`./runtime/nuxt/composables/mock`) });
});
return;
}
if (config.enabled && !nuxt.options.ssr) {
logger.warn("Nuxt OG Image is enabled but SSR is disabled.\n\nYou should enable SSR (`ssr: true`) or disable the module (`ogImage: { enabled: false }`).");
return;
}
nuxt.options.build.transpile.push(resolve("./runtime"));
const preset = resolveNitroPreset(nuxt.options.nitro);
const targetCompatibility = getPresetNitroPresetCompatibility(preset);
const hasSharpDependency = !!await tryResolveModule("sharp");
const userConfiguredExtension = config.defaults.extension;
const hasConfiguredJpegs = userConfiguredExtension && ["jpeg", "jpg"].includes(userConfiguredExtension);
if (hasConfiguredJpegs && config.defaults.renderer !== "chromium") {
if (hasSharpDependency && !targetCompatibility.sharp) {
logger.warn(`Rendering JPEGs requires sharp which does not work with ${preset}. Images will be rendered as PNG at runtime.`);
config.compatibility = defu(config.compatibility, {
runtime: { sharp: false }
});
} else if (!hasSharpDependency) {
logger.warn("You have enabled `JPEG` images. These require the `sharp` dependency which is missing, installing it for you.");
await ensureDependencies(["sharp"]);
logger.warn("Support for `sharp` is limited so check the compatibility guide.");
}
} else if (!hasSharpDependency) {
config.compatibility = defu(config.compatibility, {
runtime: { sharp: false },
dev: { sharp: false },
prerender: { sharp: false }
});
}
const hasChromeLocally = checkLocalChrome();
const hasPlaywrightDependency = await checkPlaywrightDependency();
const chromeCompatibilityFlags = {
prerender: config.compatibility?.prerender?.chromium,
dev: config.compatibility?.dev?.chromium,
runtime: config.compatibility?.runtime?.chromium
};
const chromiumBinding = {
dev: null,
prerender: null,
runtime: null
};
if (nuxt.options.dev) {
if (isUndefinedOrTruthy(chromeCompatibilityFlags.dev))
chromiumBinding.dev = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
} else {
if (isUndefinedOrTruthy(chromeCompatibilityFlags.prerender))
chromiumBinding.prerender = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
if (isUndefinedOrTruthy(chromeCompatibilityFlags.runtime))
chromiumBinding.runtime = hasPlaywrightDependency ? "playwright" : null;
}
config.compatibility = defu(config.compatibility, {
runtime: { chromium: chromiumBinding.runtime },
dev: { chromium: chromiumBinding.dev },
prerender: { chromium: chromiumBinding.prerender }
});
await import('@resvg/resvg-js').catch(() => {
logger.warn("ReSVG is missing dependencies for environment. Falling back to WASM version, this may slow down PNG rendering.");
config.compatibility = defu(config.compatibility, {
dev: { resvg: "wasm-fs" },
prerender: { resvg: "wasm-fs" }
});
if (targetCompatibility.resvg === "node") {
config.compatibility = defu(config.compatibility, {
runtime: { resvg: "wasm" }
});
}
});
await installNuxtSiteConfig();
if (hasNuxtModule("@nuxt/content"))
addServerPlugin(resolve("./runtime/nitro/plugins/nuxt-content"));
if (!config.fonts.length) {
config.fonts = [
{ name: "Inter", weight: 400, path: resolve("./runtime/assets/Inter-400.ttf.base64"), absolutePath: true },
{ name: "Inter", weight: 700, path: resolve("./runtime/assets/Inter-700.ttf.base64"), absolutePath: true }
];
}
const serverFontsDir = resolve(nuxt.options.buildDir, "cache", `nuxt-og-image@${version}`, "_fonts");
const fontStorage = createStorage({
driver: fsDriver({
base: serverFontsDir
})
});
config.fonts = (await Promise.all(normaliseFontInput(config.fonts).map(async (f) => {
if (!f.key && !f.path) {
if (preset === "stackblitz") {
logger.warn(`The ${f.name}:${f.weight} font was skipped because remote fonts are not available in StackBlitz, please use a local font.`);
return false;
}
if (await downloadFont(f, fontStorage, config.googleFontMirror)) {
f.key = `nuxt-og-image:fonts:${f.name}-${f.weight}.ttf.base64`;
} else {
logger.warn(`Failed to download font ${f.name}:${f.weight}. You may be offline or behind a firewall blocking Google. Consider setting \`googleFontMirror: true\`.`);
return false;
}
} else if (f.path) {
const extension = basename(f.path.replace(".base64", "")).split(".").pop();
if (!["woff", "ttf", "otf"].includes(extension)) {
logger.warn(`The ${f.name}:${f.weight} font was skipped because the file extension ${extension} is not supported. Only woff, ttf and otf are supported.`);
return false;
}
if (!f.absolutePath)
f.path = join(nuxt.options.rootDir, nuxt.options.dir.public, withoutLeadingSlash(f.path));
if (!existsSync(f.path)) {
logger.warn(`The ${f.name}:${f.weight} font was skipped because the file does not exist at path ${f.path}.`);
return false;
}
const fontData = await readFile(f.path, f.path.endsWith(".base64") ? "utf-8" : "base64");
f.key = `nuxt-og-image:fonts:${f.name}-${f.weight}.${extension}.base64`;
await fontStorage.setItem(`${f.name}-${f.weight}.${extension}.base64`, fontData);
delete f.path;
delete f.absolutePath;
}
return f;
}))).filter(Boolean);
nuxt.options.nitro.serverAssets = nuxt.options.nitro.serverAssets || [];
nuxt.options.nitro.serverAssets.push({ baseName: "nuxt-og-image:fonts", dir: serverFontsDir });
nuxt.options.experimental.componentIslands = true;
addServerHandler({
lazy: true,
route: "/__og-image__/font/**",
handler: resolve("./runtime/nitro/routes/font")
});
if (config.debug || nuxt.options.dev) {
addServerHandler({
lazy: true,
route: "/__og-image__/debug.json",
handler: resolve("./runtime/nitro/routes/debug.json")
});
}
addServerHandler({
lazy: true,
route: "/__og-image__/image/**",
handler: resolve("./runtime/nitro/routes/image")
});
nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = [];
["defineOgImage", "defineOgImageComponent", "defineOgImageScreenshot"].forEach((name2) => {
addImports({
name: name2,
from: resolve(`./runtime/nuxt/composables/${name2}`)
});
nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"].push(name2);
});
await addComponentsDir({
path: resolve("./runtime/nuxt/components/Templates/Community"),
island: true,
watch: true
});
[
// new
"OgImage",
"OgImageScreenshot"
].forEach((name2) => {
addComponent({
name: name2,
filePath: resolve(`./runtime/nuxt/components/OgImage/${name2}`),
...config.componentOptions
});
});
addPlugin({ mode: "server", src: resolve("./runtime/nuxt/plugins/route-rule-og-image.server") });
addPlugin({ mode: "server", src: resolve("./runtime/nuxt/plugins/og-image-canonical-urls.server") });
const ogImageComponentCtx = { components: [] };
nuxt.hook("components:extend", (components) => {
ogImageComponentCtx.components = [];
components.forEach((component) => {
let valid = false;
config.componentDirs.forEach((dir) => {
if (component.pascalName.startsWith(dir) || component.kebabName.startsWith(dir) || component.shortPath.includes(`/${dir}/`))
valid = true;
});
if (component.filePath.includes(resolve("./runtime/nuxt/components/Templates")))
valid = true;
if (valid && fs.existsSync(component.filePath)) {
component.island = true;
component.mode = "server";
let category = "app";
if (component.filePath.includes(resolve("./runtime/nuxt/components/Templates/Community")))
category = "community";
const componentFile = fs.readFileSync(component.filePath, "utf-8");
const credits = componentFile.split("\n").find((line) => line.startsWith(" * @credits"))?.replace("* @credits", "").trim();
ogImageComponentCtx.components.push({
// purge cache when component changes
hash: hash(componentFile),
pascalName: component.pascalName,
kebabName: component.kebabName,
path: nuxt.options.dev ? component.filePath : void 0,
category,
credits
});
}
});
nuxt.hooks.hook("nuxt-og-image:components", ogImageComponentCtx);
});
addTemplate({
filename: "nuxt-og-image/components.mjs",
getContents() {
return `export const componentNames = ${JSON.stringify(ogImageComponentCtx.components)}`;
},
options: { mode: "server" }
});
nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {};
nuxt.options.nitro.virtual["#nuxt-og-image/component-names.mjs"] = () => {
return `export const componentNames = ${JSON.stringify(ogImageComponentCtx.components)}`;
};
let unoCssConfig = {};
nuxt.hook("tailwindcss:config", (tailwindConfig) => {
unoCssConfig = defu(tailwindConfig.theme?.extend, { ...tailwindConfig.theme, extend: void 0 });
});
nuxt.hook("unocss:config", (_unoCssConfig) => {
unoCssConfig = { ..._unoCssConfig.theme };
});
nuxt.options.nitro.virtual["#nuxt-og-image/unocss-config.mjs"] = () => {
return `export const theme = ${JSON.stringify(unoCssConfig)}`;
};
extendTypes("nuxt-og-image", ({ typesPath }) => {
const componentImports = ogImageComponentCtx.components.map((component) => {
const relativeComponentPath = relative(resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"), component.path);
const name2 = config.componentDirs.sort((a, b) => b.length - a.length).reduce((name3, dir) => {
return name3.replace(new RegExp(`^${dir}`), "");
}, component.pascalName);
return ` '${name2}': typeof import('${relativeComponentPath}')['default']`;
}).join("\n");
return `
declare module 'nitropack' {
interface NitroRouteRules {
ogImage?: false | import('${typesPath}').OgImageOptions & Record<string, any>
}
interface NitroRouteConfig {
ogImage?: false | import('${typesPath}').OgImageOptions & Record<string, any>
}
interface NitroRuntimeHooks {
'nuxt-og-image:context': (ctx: import('${typesPath}').OgImageRenderEventContext) => void | Promise<void>
'nuxt-og-image:satori:vnodes': (vnodes: import('${typesPath}').VNode, ctx: import('${typesPath}').OgImageRenderEventContext) => void | Promise<void>
}
}
declare module '#nuxt-og-image/components' {
export interface OgImageComponents {
${componentImports}
}
}
declare module '#nuxt-og-image/unocss-config' {
export type theme = any
}
`;
});
const cacheEnabled = typeof config.runtimeCacheStorage !== "undefined" && config.runtimeCacheStorage !== false;
const runtimeCacheStorage = typeof config.runtimeCacheStorage === "boolean" ? "default" : config.runtimeCacheStorage.driver;
let baseCacheKey = runtimeCacheStorage === "default" ? `/cache/nuxt-og-image@${version}` : `/nuxt-og-image/${version}`;
if (!cacheEnabled)
baseCacheKey = false;
if (!nuxt.options.dev && config.runtimeCacheStorage && typeof config.runtimeCacheStorage === "object") {
nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
nuxt.options.nitro.storage["nuxt-og-image"] = config.runtimeCacheStorage;
}
nuxt.hooks.hook("modules:done", async () => {
const normalisedFonts = normaliseFontInput(config.fonts);
if (!isNuxtGenerate() && nuxt.options.build) {
nuxt.options.nitro = nuxt.options.nitro || {};
nuxt.options.nitro.prerender = nuxt.options.nitro.prerender || {};
nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes || [];
}
const hasColorModeModule = hasNuxtModule("@nuxtjs/color-mode");
const colorModeOptions = hasColorModeModule ? await getNuxtModuleOptions("@nuxtjs/color-mode") : {};
let colorPreference = colorModeOptions.preference;
if (!colorPreference || colorPreference === "system")
colorPreference = colorModeOptions.fallback;
if (!colorPreference || colorPreference === "system")
colorPreference = "light";
const runtimeConfig = {
version,
// binding options
satoriOptions: config.satoriOptions || {},
resvgOptions: config.resvgOptions || {},
sharpOptions: config.sharpOptions || {},
defaults: config.defaults,
debug: config.debug,
// avoid adding credentials
baseCacheKey,
// convert the fonts to uniform type to fix ts issue
fonts: normalisedFonts,
hasNuxtIcon: hasNuxtModule("nuxt-icon"),
colorPreference,
// @ts-expect-error runtime type
isNuxtContentDocumentDriven: !!nuxt.options.content?.documentDriven
};
nuxt.hooks.callHook("nuxt-og-image:runtime-config", runtimeConfig);
nuxt.options.runtimeConfig["nuxt-og-image"] = runtimeConfig;
});
if (nuxt.options.dev) {
setupDevHandler(config, resolve);
setupDevToolsUI(config, resolve);
} else if (isNuxtGenerate()) {
setupGenerateHandler(config, resolve);
} else if (nuxt.options.build) {
await setupBuildHandler(config, resolve);
}
if (nuxt.options.build)
addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
setupPrerenderHandler(config, resolve);
}
});
export { module as default };