nuxt-og-image 2.0.0-beta.4 → 2.0.0-beta.41

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.
Files changed (88) hide show
  1. package/README.md +245 -47
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.8bbd2aa2.css +1 -0
  5. package/dist/client/_nuxt/IconCSS.db300c72.js +1 -0
  6. package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
  7. package/dist/client/_nuxt/ImageLoader.9ee91833.js +1 -0
  8. package/dist/client/_nuxt/entry.f180d9dc.js +5 -0
  9. package/dist/client/_nuxt/entry.f4586a2b.css +1 -0
  10. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.7ee6d3e0.js} +1 -1
  11. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.d9b74ae7.js} +1 -1
  12. package/dist/client/_nuxt/error-component.0102aa4e.js +3 -0
  13. package/dist/client/_nuxt/index.18957a6a.js +1 -0
  14. package/dist/client/_nuxt/index.403133d8.css +1 -0
  15. package/dist/client/_nuxt/{options.56a3e5f9.js → options.a1a5b6d3.js} +1 -1
  16. package/dist/client/_nuxt/{png.37f3e77b.js → png.e401832b.js} +1 -1
  17. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.df675964.js} +1 -1
  18. package/dist/client/_nuxt/{svg.186c6bd1.js → svg.a65813fd.js} +1 -1
  19. package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.6e14ce87.js} +1 -1
  20. package/dist/client/index.html +2 -2
  21. package/dist/client/options/index.html +2 -2
  22. package/dist/client/png/index.html +2 -2
  23. package/dist/client/svg/index.html +2 -2
  24. package/dist/client/vnodes/index.html +2 -2
  25. package/dist/module.d.ts +21 -7
  26. package/dist/module.json +1 -1
  27. package/dist/module.mjs +285 -114
  28. package/dist/runtime/browserUtil.d.ts +1 -0
  29. package/dist/runtime/browserUtil.mjs +10 -5
  30. package/dist/runtime/components/OgImageDynamic.d.ts +1 -1
  31. package/dist/runtime/components/OgImageScreenshot.d.ts +1 -1
  32. package/dist/runtime/components/OgImageStatic.d.ts +1 -1
  33. package/dist/runtime/composables/defineOgImage.mjs +15 -12
  34. package/dist/runtime/nitro/middleware/og.png.mjs +51 -7
  35. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  36. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  37. package/dist/runtime/nitro/plugins/prerender.mjs +26 -0
  38. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  39. package/dist/runtime/nitro/providers/browser/lambda.mjs +3 -3
  40. package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
  41. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  42. package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
  43. package/dist/runtime/nitro/providers/png/resvg-node.d.ts +5 -0
  44. package/dist/runtime/nitro/providers/png/resvg-node.mjs +6 -0
  45. package/dist/runtime/nitro/providers/png/resvg-wasm.d.ts +4 -0
  46. package/dist/runtime/nitro/providers/png/resvg-wasm.mjs +11 -0
  47. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  48. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  49. package/dist/runtime/nitro/renderers/browser.d.ts +2 -2
  50. package/dist/runtime/nitro/renderers/browser.mjs +14 -7
  51. package/dist/runtime/nitro/renderers/satori/index.d.ts +2 -2
  52. package/dist/runtime/nitro/renderers/satori/index.mjs +22 -15
  53. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
  54. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +25 -3
  55. package/dist/runtime/nitro/renderers/satori/utils.d.ts +2 -2
  56. package/dist/runtime/nitro/renderers/satori/utils.mjs +25 -12
  57. package/dist/runtime/nitro/routes/debug.d.ts +4 -0
  58. package/dist/runtime/nitro/routes/debug.mjs +9 -0
  59. package/dist/runtime/nitro/routes/html.mjs +52 -14
  60. package/dist/runtime/nitro/routes/options.mjs +20 -22
  61. package/dist/runtime/nitro/routes/svg.mjs +3 -1
  62. package/dist/runtime/nitro/routes/vnode.mjs +3 -1
  63. package/dist/runtime/nitro/util-hostname.d.ts +2 -0
  64. package/dist/runtime/nitro/util-hostname.mjs +15 -0
  65. package/dist/runtime/nitro/utils-pure.d.ts +3 -2
  66. package/dist/runtime/nitro/utils-pure.mjs +9 -8
  67. package/dist/runtime/nitro/utils.d.ts +6 -8
  68. package/dist/runtime/nitro/utils.mjs +50 -47
  69. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  70. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  71. package/dist/types.d.ts +6 -0
  72. package/package.json +33 -23
  73. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  74. package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
  75. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  76. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
  77. package/dist/client/_nuxt/error-component.cf7543e5.js +0 -3
  78. package/dist/client/_nuxt/index.3f356409.js +0 -1
  79. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
  80. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  81. /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
  82. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  83. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  84. /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
  85. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  86. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  87. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  88. /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
@@ -1,7 +1,7 @@
1
- import { useServerHead } from "@vueuse/head";
2
1
  import { withBase } from "ufo";
3
2
  import { useRequestEvent } from "#app";
4
- import { useRouter, useRuntimeConfig } from "#imports";
3
+ import { useHostname } from "../nitro/util-hostname.mjs";
4
+ import { useRouter, useRuntimeConfig, useServerHead } from "#imports";
5
5
  export function defineOgImageScreenshot(options = {}) {
6
6
  const router = useRouter();
7
7
  const route = router?.currentRoute?.value?.path || "";
@@ -14,29 +14,32 @@ export function defineOgImageScreenshot(options = {}) {
14
14
  });
15
15
  }
16
16
  export function defineOgImageDynamic(options = {}) {
17
- const { satoriProvider, forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
17
+ const { runtimeSatori } = useRuntimeConfig()["nuxt-og-image"];
18
18
  defineOgImage({
19
- provider: satoriProvider ? "satori" : "browser",
20
- static: !!forcePrerender,
19
+ provider: runtimeSatori ? "satori" : "browser",
20
+ static: false,
21
+ cacheTtl: 0,
21
22
  ...options
22
23
  });
23
24
  }
24
25
  export function defineOgImageStatic(options = {}) {
25
- const { satoriProvider } = useRuntimeConfig()["nuxt-og-image"];
26
+ const { runtimeSatori } = useRuntimeConfig()["nuxt-og-image"];
26
27
  defineOgImage({
27
- provider: satoriProvider ? "satori" : "browser",
28
+ provider: runtimeSatori ? "satori" : "browser",
28
29
  static: true,
29
30
  ...options
30
31
  });
31
32
  }
32
33
  export function defineOgImage(options = {}) {
33
34
  if (process.server) {
34
- const { forcePrerender, defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
35
+ let resolveSrc = function() {
36
+ return withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl);
37
+ };
38
+ const { defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
35
39
  const router = useRouter();
36
40
  const route = router?.currentRoute?.value?.path || "";
37
41
  const e = useRequestEvent();
38
- if ((forcePrerender || options.static) && options.provider === "satori")
39
- e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
42
+ const baseUrl = process.env.prerender ? siteUrl : useHostname(e);
40
43
  const meta = [
41
44
  {
42
45
  name: "twitter:card",
@@ -44,11 +47,11 @@ export function defineOgImage(options = {}) {
44
47
  },
45
48
  {
46
49
  name: "twitter:image:src",
47
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, siteUrl)
50
+ content: resolveSrc
48
51
  },
49
52
  {
50
53
  property: "og:image",
51
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, siteUrl)
54
+ content: resolveSrc
52
55
  },
53
56
  {
54
57
  property: "og:image:width",
@@ -1,19 +1,21 @@
1
- import { createError, defineEventHandler, setHeader } from "h3";
2
- import { parseURL, withBase, withoutTrailingSlash } from "ufo";
1
+ import { Buffer } from "node:buffer";
2
+ import { createError, defineEventHandler, sendRedirect, setHeader } from "h3";
3
+ import { joinURL, parseURL, withBase, withoutTrailingSlash } from "ufo";
4
+ import { prefixStorage } from "unstorage";
3
5
  import { fetchOptions, useHostname } from "../utils.mjs";
4
6
  import { useProvider } from "#nuxt-og-image/provider";
7
+ import { useRuntimeConfig, useStorage } from "#imports";
5
8
  export default defineEventHandler(async (e) => {
9
+ const { runtimeBrowser, runtimeCacheStorage } = useRuntimeConfig()["nuxt-og-image"];
6
10
  const path = parseURL(e.path).pathname;
7
11
  if (!path.endsWith("__og_image__/og.png"))
8
12
  return;
9
13
  const basePath = withoutTrailingSlash(
10
14
  path.replace("__og_image__/og.png", "")
11
15
  );
12
- setHeader(e, "Content-Type", "image/png");
13
- setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
14
- setHeader(e, "Pragma", "no-cache");
15
- setHeader(e, "Expires", "0");
16
16
  const options = await fetchOptions(e, basePath);
17
+ if (process.env.NODE_ENV === "production" && !process.env.prerender && !runtimeBrowser && options.provider === "browser")
18
+ return sendRedirect(e, joinURL(useHostname(e), "__nuxt_og_image__/browser-provider-not-supported.png"));
17
19
  const provider = await useProvider(options.provider);
18
20
  if (!provider) {
19
21
  throw createError({
@@ -21,5 +23,47 @@ export default defineEventHandler(async (e) => {
21
23
  statusMessage: `Provider ${options.provider} is missing.`
22
24
  });
23
25
  }
24
- return provider.createPng(withBase(basePath, useHostname(e)), options);
26
+ const useCache = runtimeCacheStorage && !process.dev && options.cacheTtl && options.cacheTtl > 0;
27
+ const cache = prefixStorage(useStorage(), "og-image-cache:images");
28
+ const key = options.cacheKey || e.node.req.url;
29
+ let png;
30
+ if (useCache && await cache.hasItem(key)) {
31
+ const { value, expiresAt } = await cache.getItem(key);
32
+ if (expiresAt > Date.now()) {
33
+ setHeader(e, "Cache-Control", "public, max-age=31536000");
34
+ setHeader(e, "Content-Type", "image/png");
35
+ png = Buffer.from(value, "base64");
36
+ } else {
37
+ await cache.removeItem(key);
38
+ }
39
+ }
40
+ if (!png) {
41
+ try {
42
+ png = await provider.createPng(withBase(basePath, useHostname(e)), options);
43
+ if (useCache && png) {
44
+ const base64png = Buffer.from(png).toString("base64");
45
+ await cache.setItem(key, { value: base64png, expiresAt: Date.now() + (options.cacheTtl || 0) });
46
+ }
47
+ } catch (err) {
48
+ throw createError({
49
+ statusCode: 500,
50
+ statusMessage: `Failed to create og image: ${err.message}`
51
+ });
52
+ }
53
+ }
54
+ if (png) {
55
+ if (!process.dev && options.static) {
56
+ setHeader(e, "Cache-Control", "public, max-age=31536000");
57
+ } else {
58
+ setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
59
+ setHeader(e, "Pragma", "no-cache");
60
+ setHeader(e, "Expires", "0");
61
+ }
62
+ setHeader(e, "Content-Type", "image/png");
63
+ return png;
64
+ }
65
+ throw createError({
66
+ statusCode: 500,
67
+ statusMessage: "Failed to create og image, unknown error."
68
+ });
25
69
  });
@@ -1,11 +1,12 @@
1
1
  import { defineEventHandler } from "h3";
2
- import { parseURL, withoutTrailingSlash } from "ufo";
2
+ import { parseURL, withBase, withoutTrailingSlash } from "ufo";
3
3
  import { fetchOptions } from "../utils.mjs";
4
+ import { useRuntimeConfig } from "#imports";
4
5
  export default defineEventHandler(async (e) => {
5
6
  const path = withoutTrailingSlash(parseURL(e.path).pathname);
6
7
  if (!path.endsWith("/__og_image__"))
7
8
  return;
8
- const basePath = path.replace("/__og_image__", "");
9
+ const basePath = withBase(path.replace("/__og_image__", ""), useRuntimeConfig().app.baseURL);
9
10
  const options = await fetchOptions(e, basePath === "" ? "/" : basePath);
10
11
  if (!options)
11
12
  return `The route ${basePath} has not been set up for og:image generation.`;
@@ -22,5 +23,5 @@ export default defineEventHandler(async (e) => {
22
23
  }
23
24
  </style>
24
25
  <title>OG Image Playground</title>
25
- <iframe src="/__nuxt_og_image__/client/?&path=${basePath}"></iframe>`;
26
+ <iframe src="/__nuxt_og_image__/client?&path=${basePath}&base=${useRuntimeConfig().app.baseURL}"></iframe>`;
26
27
  });
@@ -0,0 +1,3 @@
1
+ import type { NitroAppPlugin } from 'nitropack';
2
+ declare const OgImagePrenderNitroPlugin: NitroAppPlugin;
3
+ export default OgImagePrenderNitroPlugin;
@@ -0,0 +1,26 @@
1
+ import { appendHeader } from "h3";
2
+ import { joinURL } from "ufo";
3
+ import { prefixStorage } from "unstorage";
4
+ import { extractOgImageOptions } from "../utils-pure.mjs";
5
+ import { useStorage } from "#imports";
6
+ const OgImagePrenderNitroPlugin = (nitroApp) => {
7
+ if (!process.env.prerender)
8
+ return;
9
+ const cache = prefixStorage(useStorage(), "og-image-cache:options");
10
+ nitroApp.hooks.hook("render:html", async (ctx, { event }) => {
11
+ const url = event.node.req.url;
12
+ if (url.includes(".") || url.startsWith("/__nuxt_island/"))
13
+ return;
14
+ const options = extractOgImageOptions(ctx.head.join("\n"));
15
+ if (!options)
16
+ return;
17
+ await cache.setItem(url, {
18
+ value: JSON.stringify(options),
19
+ expiresAt: Date.now() + 60 * 60 * 1e3
20
+ // 60 minutes to prerender
21
+ });
22
+ if (options.static && options.provider === "satori")
23
+ appendHeader(event, "x-nitro-prerender", joinURL(url, "/__og_image__/og.png"));
24
+ });
25
+ };
26
+ export default OgImagePrenderNitroPlugin;
@@ -1 +1 @@
1
- export default function createBrowser(): Promise<import("puppeteer-core").Browser>;
1
+ export default function createBrowser(): Promise<any>;
@@ -1,9 +1,9 @@
1
- import edgeChromium from "chrome-aws-lambda";
1
+ import edgeChromium from "@sparticuz/chrome-aws-lambda";
2
2
  import puppeteer from "puppeteer-core";
3
3
  export default async function createBrowser() {
4
- return puppeteer.launch({
5
- args: edgeChromium.args,
4
+ return await puppeteer.launch({
6
5
  executablePath: await edgeChromium.executablePath,
6
+ args: edgeChromium.args,
7
7
  headless: true
8
8
  });
9
9
  }
@@ -1,14 +1,5 @@
1
1
  import playwrightCore from "playwright-core";
2
2
  export default async function createBrowser() {
3
- try {
4
- const { Launcher } = await import(String("chrome-launcher"));
5
- const chromePath = Launcher.getFirstInstallation();
6
- return await playwrightCore.chromium.launch({
7
- headless: true,
8
- executablePath: chromePath
9
- });
10
- } catch (e) {
11
- }
12
3
  try {
13
4
  return await playwrightCore.chromium.launch({
14
5
  headless: true
@@ -0,0 +1 @@
1
+ export default function createBrowser(): Promise<any>;
@@ -0,0 +1,33 @@
1
+ import playwrightCore from "playwright-core";
2
+ export default async function createBrowser() {
3
+ if (process.dev || process.env.prerender) {
4
+ try {
5
+ const { Launcher } = await import(String("chrome-launcher"));
6
+ const chromePath = Launcher.getFirstInstallation();
7
+ return await playwrightCore.chromium.launch({
8
+ headless: true,
9
+ executablePath: chromePath
10
+ });
11
+ } catch (e) {
12
+ }
13
+ }
14
+ try {
15
+ return await playwrightCore.chromium.launch({
16
+ headless: true
17
+ });
18
+ } catch (e) {
19
+ }
20
+ try {
21
+ const playwright = await import(String("playwright"));
22
+ return await playwright.chromium.launch({
23
+ headless: true
24
+ });
25
+ } catch (e) {
26
+ if (process.dev) {
27
+ console.warn("Failed to load chromium instance. Ensure you have chrome installed, otherwise add the dependency: `npm add -D playwright`.");
28
+ } else {
29
+ console.error("Failed to load browser instance. Please open an issue with the exception: https://github.com/harlan-zw/nuxt-og-image/issues.");
30
+ throw e;
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,5 @@
1
+ /// <reference types="node" />
2
+ import type { ResvgRenderOptions } from '@resvg/resvg-js';
3
+ export default function (svg: string, options: ResvgRenderOptions & {
4
+ baseUrl: string;
5
+ }): Promise<Buffer>;
@@ -0,0 +1,6 @@
1
+ import { Resvg } from "@resvg/resvg-js";
2
+ export default async function(svg, options) {
3
+ const resvgJS = new Resvg(svg, options);
4
+ const pngData = resvgJS.render();
5
+ return pngData.asPng();
6
+ }
@@ -0,0 +1,4 @@
1
+ import type { ResvgRenderOptions } from '@resvg/resvg-wasm';
2
+ export default function (svg: string, options: ResvgRenderOptions & {
3
+ baseUrl: string;
4
+ }): Promise<Uint8Array>;
@@ -0,0 +1,11 @@
1
+ import { Resvg, initWasm } from "@resvg/resvg-wasm";
2
+ import { wasmLoader } from "../../utils.mjs";
3
+ const ReSvgLoader = wasmLoader("/* NUXT_OG_IMAGE_RESVG_WASM */", "/resvg.wasm");
4
+ export default async function(svg, options) {
5
+ const ReSvgWasm = await ReSvgLoader.load({ baseUrl: options.baseUrl });
6
+ await initWasm(ReSvgWasm).catch(() => {
7
+ });
8
+ const resvgJS = new Resvg(svg, options);
9
+ const pngData = resvgJS.render();
10
+ return pngData.asPng();
11
+ }
@@ -0,0 +1,11 @@
1
+ import { initialize, svg2png } from "svg2png-wasm";
2
+ import { wasmLoader } from "../../utils.mjs";
3
+ const Svg2PngLoader = wasmLoader("/* NUXT_OG_IMAGE_SVG2PNG_WASM */", "/svg2png.wasm");
4
+ export default async function(svg, options) {
5
+ const Svg2PngWasm = await Svg2PngLoader.load({ baseUrl: options.baseUrl });
6
+ await initialize(Svg2PngWasm).catch((e) => {
7
+ if (!e.message.trim().endsWith("function can be used only once."))
8
+ throw e;
9
+ });
10
+ return await svg2png(svg, options);
11
+ }
@@ -1,11 +1,10 @@
1
1
  import satori, { init } from "satori/wasm";
2
2
  import initYoga from "yoga-wasm-web";
3
3
  import { wasmLoader } from "../../utils.mjs";
4
+ const YogaLoader = wasmLoader("/* NUXT_OG_IMAGE_YOGA_WASM */", "/yoga.wasm");
4
5
  export default async function(nodes, options) {
5
- const loader = wasmLoader("/* NUXT_OG_IMAGE_YOGA_WASM */", "/yoga.wasm", options.baseUrl);
6
- if (!await loader.loaded()) {
7
- const yoga = await initYoga(await loader.load());
8
- init(yoga);
9
- }
6
+ const yogaWasm = await YogaLoader.load({ baseUrl: options.baseUrl });
7
+ const yoga = await initYoga(yogaWasm);
8
+ init(yoga);
10
9
  return await satori(nodes, options);
11
10
  }
@@ -1,3 +1,3 @@
1
1
  import type { Renderer } from '../../../types';
2
- declare const _default: Renderer;
3
- export default _default;
2
+ declare const BrowserRenderer: Renderer;
3
+ export default BrowserRenderer;
@@ -1,6 +1,8 @@
1
+ import { withBase } from "ufo";
1
2
  import { screenshot } from "../../browserUtil.mjs";
2
- import loadBrowser from "#nuxt-og-image/browser";
3
- export default {
3
+ import loadBrowserLauncherChunk from "#nuxt-og-image/browser";
4
+ import { useRuntimeConfig } from "#imports";
5
+ const BrowserRenderer = {
4
6
  name: "browser",
5
7
  createSvg: async function createSvg() {
6
8
  throw new Error("Browser provider can't create SVGs.");
@@ -10,19 +12,24 @@ export default {
10
12
  },
11
13
  createPng: async function createPng(basePath, options) {
12
14
  const url = new URL(basePath);
13
- const createBrowser = await loadBrowser();
14
- const browser = await createBrowser();
15
+ const launchBrowser = await loadBrowserLauncherChunk();
16
+ if (!launchBrowser) {
17
+ throw new Error("Failed to load browser. Is the `browserProvider` enabled?");
18
+ }
19
+ const browser = await launchBrowser();
20
+ let res = null;
15
21
  if (browser) {
16
22
  try {
17
- return await screenshot(browser, {
23
+ res = await screenshot(browser, {
18
24
  ...options,
19
- host: url.origin,
25
+ host: withBase(useRuntimeConfig().app.baseURL, url.origin),
20
26
  path: `/api/og-image-html?path=${url.pathname}`
21
27
  });
22
28
  } finally {
23
29
  browser.close();
24
30
  }
25
31
  }
26
- return null;
32
+ return res;
27
33
  }
28
34
  };
35
+ export default BrowserRenderer;
@@ -1,3 +1,3 @@
1
1
  import type { Renderer } from '../../../../types';
2
- declare const _default: Renderer;
3
- export default _default;
2
+ declare const SatoriRenderer: Renderer;
3
+ export default SatoriRenderer;
@@ -7,34 +7,40 @@ import twClasses from "./plugins/twClasses.mjs";
7
7
  import flex from "./plugins/flex.mjs";
8
8
  import emojis from "./plugins/emojis.mjs";
9
9
  import encoding from "./plugins/encoding.mjs";
10
- import loadSvg2png from "#nuxt-og-image/svg2png";
10
+ import loadPngCreator from "#nuxt-og-image/png";
11
11
  import loadSatori from "#nuxt-og-image/satori";
12
- import { useRuntimeConfig } from "#internal/nitro";
12
+ import { useRuntimeConfig } from "#imports";
13
13
  const satoriFonts = [];
14
14
  let fontLoadPromise = null;
15
- function loadFonts(fonts) {
15
+ function loadFonts(baseURL, fonts) {
16
16
  if (fontLoadPromise)
17
17
  return fontLoadPromise;
18
- return fontLoadPromise = Promise.all(fonts.map((font) => loadFont(font)));
18
+ return fontLoadPromise = Promise.all(fonts.map((font) => loadFont(baseURL, font)));
19
19
  }
20
- export default {
20
+ const SatoriRenderer = {
21
21
  name: "satori",
22
22
  createPng: async function createPng(baseUrl, options) {
23
23
  const svg = await this.createSvg(baseUrl, options);
24
- const svg2png = await loadSvg2png();
25
- return svg2png(svg, { baseUrl, ...options });
24
+ const pngCreator = await loadPngCreator();
25
+ return pngCreator(svg, { baseUrl, ...options });
26
26
  },
27
27
  createVNode: async function createVNode(baseUrl, options) {
28
28
  const url = parseURL(baseUrl);
29
29
  const html = await globalThis.$fetch("/api/og-image-html", {
30
- query: { path: url.pathname, options: JSON.stringify(options) }
30
+ params: {
31
+ path: url.pathname,
32
+ options: JSON.stringify(options)
33
+ }
31
34
  });
32
- const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1];
33
- const emojiedFont = twemoji.parse(body, {
34
- folder: "svg",
35
- ext: ".svg"
36
- });
37
- const satoriTree = convertHtmlToSatori(emojiedFont);
35
+ let body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1] || "";
36
+ try {
37
+ body = twemoji.parse(body, {
38
+ folder: "svg",
39
+ ext: ".svg"
40
+ });
41
+ } catch (e) {
42
+ }
43
+ const satoriTree = convertHtmlToSatori(body);
38
44
  await walkSatoriTree(url, satoriTree, [
39
45
  // @todo add user land support
40
46
  emojis(url),
@@ -49,7 +55,7 @@ export default {
49
55
  const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
50
56
  const vnodes = await this.createVNode(baseUrl, options);
51
57
  if (!satoriFonts.length)
52
- satoriFonts.push(...await loadFonts(fonts));
58
+ satoriFonts.push(...await loadFonts(baseUrl, fonts));
53
59
  const satori = await loadSatori();
54
60
  return await satori(vnodes, {
55
61
  ...satoriOptions,
@@ -61,3 +67,4 @@ export default {
61
67
  });
62
68
  }
63
69
  };
70
+ export default SatoriRenderer;
@@ -1,9 +1,10 @@
1
1
  import { defineSatoriTransformer } from "../utils.mjs";
2
+ import { decodeHtml } from "../../../utils-pure.mjs";
2
3
  export default defineSatoriTransformer(() => {
3
4
  return {
4
5
  filter: (node) => typeof node.props?.children === "string",
5
6
  transform: async (node) => {
6
- node.props.children = node.props.children.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
7
+ node.props.children = decodeHtml(node.props.children);
7
8
  }
8
9
  };
9
10
  });
@@ -7,11 +7,33 @@ export default defineSatoriTransformer((url) => {
7
7
  transform: async (node) => {
8
8
  const src = node.props?.src;
9
9
  if (src && src.startsWith("/")) {
10
+ let updated = false;
10
11
  const file = await readPublicAssetBase64(src);
11
- if (file)
12
+ if (file) {
12
13
  node.props.src = file;
13
- else
14
- node.props.src = withBase(src, `${url.protocol}//${url.host}`);
14
+ updated = true;
15
+ }
16
+ if (!updated) {
17
+ try {
18
+ const response = await globalThis.$fetch(src);
19
+ node.props.src = response.arrayBuffer();
20
+ updated = true;
21
+ } catch (e) {
22
+ }
23
+ }
24
+ if (!updated) {
25
+ try {
26
+ const response = await globalThis.$fetch.raw(src);
27
+ if (response.status === 200) {
28
+ node.props.src = response.arrayBuffer();
29
+ updated = true;
30
+ }
31
+ } catch (e) {
32
+ }
33
+ }
34
+ if (!updated) {
35
+ node.props.src = `${withBase(src, `${url.protocol}//${url.host}`)}?${Date.now()}`;
36
+ }
15
37
  }
16
38
  }
17
39
  };
@@ -1,5 +1,5 @@
1
1
  import type { ParsedURL } from 'ufo';
2
- import type { SatoriTransformer, VNode } from '../../../../types';
3
- export declare function loadFont(font: string): Promise<any>;
2
+ import type { FontConfig, SatoriTransformer, VNode } from '../../../../types';
3
+ export declare function loadFont(baseURL: string, font: FontConfig): Promise<any>;
4
4
  export declare function walkSatoriTree(url: ParsedURL, node: VNode, plugins: SatoriTransformer[]): Promise<void>;
5
5
  export declare function defineSatoriTransformer(transformer: (url: ParsedURL) => SatoriTransformer): (url: ParsedURL) => SatoriTransformer;
@@ -1,20 +1,32 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import { base64ToArrayBuffer, readPublicAsset } from "../../utils.mjs";
2
3
  import { useStorage } from "#internal/nitro";
3
4
  const cachedFonts = {};
4
- export async function loadFont(font) {
5
- if (cachedFonts[font])
6
- return cachedFonts[font];
5
+ export async function loadFont(baseURL, font) {
6
+ let fontKey = font;
7
+ if (typeof font === "object")
8
+ fontKey = `${font.name}:${font.weight}`;
9
+ if (cachedFonts[fontKey])
10
+ return cachedFonts[fontKey];
11
+ const [name, weight] = fontKey.split(":");
7
12
  let data;
8
- const storageKey = `assets:nuxt-og-imagee:font:${font}`;
9
- if (await useStorage().hasItem(storageKey)) {
13
+ const storageKey = `assets:nuxt-og-imagee:font:${fontKey}`;
14
+ if (await useStorage().hasItem(storageKey))
10
15
  data = base64ToArrayBuffer(await useStorage().getItem(storageKey));
11
- return cachedFonts[font] = { name: font, data, style: "normal" };
16
+ if (!data && name === "Inter" && ["400", "700"].includes(weight)) {
17
+ data = await readPublicAsset(`/inter-latin-ext-${weight}-normal.woff`);
12
18
  }
13
- const [name, weight] = font.split(":");
14
- if (name === "Inter" && ["400", "700"].includes(weight)) {
15
- const data2 = await readPublicAsset(`/inter-latin-ext-${weight}-normal.woff`);
16
- if (data2)
17
- return cachedFonts[font] = { name, weight: Number(weight), data: data2, style: "normal" };
19
+ if (typeof font === "object") {
20
+ data = await readPublicAsset(font.path);
21
+ if (!data) {
22
+ try {
23
+ data = await globalThis.$fetch(font.path, {
24
+ responseType: "arrayBuffer",
25
+ baseURL
26
+ });
27
+ } catch {
28
+ }
29
+ }
18
30
  }
19
31
  if (!data) {
20
32
  const fontUrl = await globalThis.$fetch("/api/og-image-font", {
@@ -24,8 +36,9 @@ export async function loadFont(font) {
24
36
  responseType: "arrayBuffer"
25
37
  });
26
38
  }
39
+ cachedFonts[fontKey] = { name, weight: Number(weight), data, style: "normal" };
27
40
  await useStorage().setItem(storageKey, Buffer.from(data).toString("base64"));
28
- return cachedFonts[font] = { name, weight: Number(weight), data, style: "normal" };
41
+ return cachedFonts[fontKey];
29
42
  }
30
43
  export async function walkSatoriTree(url, node, plugins) {
31
44
  if (!node.props?.children)
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<{
2
+ runtimeConfig: any;
3
+ }>;
4
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler, setHeader } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export default defineEventHandler(async (e) => {
4
+ setHeader(e, "Content-Type", "application/json");
5
+ const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
6
+ return {
7
+ runtimeConfig
8
+ };
9
+ });