nuxt-og-image 3.0.0-beta.12 → 3.0.0-beta.14

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 (99) hide show
  1. package/dist/client/200.html +5 -5
  2. package/dist/client/404.html +5 -5
  3. package/dist/client/_nuxt/{IconCSS.3f011790.js → IconCSS.59f1542b.js} +1 -1
  4. package/dist/client/_nuxt/IconCSS.7e8f1f7b.css +1 -0
  5. package/dist/client/_nuxt/builds/latest.json +1 -1
  6. package/dist/client/_nuxt/builds/meta/4222eee5-f71e-4425-9403-92637db48893.json +1 -0
  7. package/dist/client/_nuxt/entry.03caefbf.js +137 -0
  8. package/dist/client/_nuxt/entry.3a009184.css +1 -0
  9. package/dist/client/_nuxt/{error-404.0d8fb510.js → error-404.8bab7a15.js} +1 -1
  10. package/dist/client/_nuxt/{error-500.cefac4a7.js → error-500.b72200bc.js} +1 -1
  11. package/dist/client/index.html +5 -5
  12. package/dist/module.d.mts +36 -19
  13. package/dist/module.d.ts +36 -19
  14. package/dist/module.json +2 -2
  15. package/dist/module.mjs +54 -41
  16. package/dist/runtime/cache.d.ts +7 -10
  17. package/dist/runtime/cache.mjs +38 -26
  18. package/dist/runtime/components/OgImage/OgImage.d.ts +5 -0
  19. package/dist/runtime/components/OgImage/{index.mjs → OgImage.mjs} +1 -1
  20. package/dist/runtime/components/OgImage/Screenshot.d.ts +3 -4
  21. package/dist/runtime/components/OgImage/Screenshot.mjs +1 -1
  22. package/dist/runtime/components/Templates/Official/SimpleBlog.vue +1 -0
  23. package/dist/runtime/composables/defineOgImage.d.ts +2 -23
  24. package/dist/runtime/composables/defineOgImage.mjs +32 -115
  25. package/dist/runtime/composables/defineOgImageComponent.d.ts +3 -0
  26. package/dist/runtime/composables/defineOgImageComponent.mjs +8 -0
  27. package/dist/runtime/composables/defineOgImagePageScreenshot.d.ts +2 -0
  28. package/dist/runtime/composables/defineOgImagePageScreenshot.mjs +14 -0
  29. package/dist/runtime/core/bindings/css-inline/node.d.ts +2 -5
  30. package/dist/runtime/core/bindings/css-inline/node.mjs +2 -10
  31. package/dist/runtime/core/cache/emojis.d.ts +1 -0
  32. package/dist/runtime/core/cache/emojis.mjs +5 -0
  33. package/dist/runtime/core/cache/htmlPayload.d.ts +5 -0
  34. package/dist/runtime/core/cache/htmlPayload.mjs +6 -0
  35. package/dist/runtime/core/cache/prerender.d.ts +1 -1
  36. package/dist/runtime/core/font/fetch.d.ts +2 -3
  37. package/dist/runtime/core/font/fetch.mjs +10 -4
  38. package/dist/runtime/core/html/applyEmojis.d.ts +3 -0
  39. package/dist/runtime/core/html/applyEmojis.mjs +37 -0
  40. package/dist/runtime/core/html/applyInlineCss.d.ts +3 -0
  41. package/dist/runtime/core/html/applyInlineCss.mjs +32 -0
  42. package/dist/runtime/core/html/devIframeTemplate.d.ts +2 -0
  43. package/dist/runtime/core/html/{fetch.mjs → devIframeTemplate.mjs} +8 -31
  44. package/dist/runtime/core/html/fetchIsland.d.ts +3 -0
  45. package/dist/runtime/core/html/fetchIsland.mjs +17 -0
  46. package/dist/runtime/core/options/fetch.d.ts +1 -1
  47. package/dist/runtime/core/options/fetch.mjs +10 -5
  48. package/dist/runtime/core/options/normalise.d.ts +2 -2
  49. package/dist/runtime/core/renderers/chromium/index.mjs +6 -7
  50. package/dist/runtime/core/renderers/chromium/screenshot.d.ts +2 -3
  51. package/dist/runtime/core/renderers/chromium/screenshot.mjs +5 -4
  52. package/dist/runtime/core/renderers/satori/fonts.d.ts +2 -2
  53. package/dist/runtime/core/renderers/satori/fonts.mjs +2 -2
  54. package/dist/runtime/core/renderers/satori/index.d.ts +2 -3
  55. package/dist/runtime/core/renderers/satori/index.mjs +21 -18
  56. package/dist/runtime/core/renderers/satori/instances.d.ts +3 -0
  57. package/dist/runtime/core/renderers/satori/instances.mjs +15 -0
  58. package/dist/runtime/core/renderers/satori/plugins/emojis.mjs +15 -13
  59. package/dist/runtime/core/renderers/satori/plugins/imageSrc.mjs +8 -4
  60. package/dist/runtime/core/renderers/satori/utils.d.ts +2 -3
  61. package/dist/runtime/core/renderers/satori/vnodes.d.ts +2 -3
  62. package/dist/runtime/core/renderers/satori/vnodes.mjs +14 -6
  63. package/dist/runtime/core/utils/resolveRendererContext.d.ts +2 -6
  64. package/dist/runtime/core/utils/resolveRendererContext.mjs +34 -21
  65. package/dist/runtime/nitro/plugins/nuxt-content.mjs +2 -2
  66. package/dist/runtime/nitro/plugins/prerender.mjs +2 -2
  67. package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +1 -1
  68. package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +14 -47
  69. package/dist/runtime/nuxt/utils.d.ts +2 -0
  70. package/dist/runtime/nuxt/utils.mjs +55 -0
  71. package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +0 -2
  72. package/dist/runtime/server/routes/__og-image__/debug.json.mjs +2 -7
  73. package/dist/runtime/server/routes/__og-image__/image.mjs +86 -0
  74. package/dist/runtime/types.d.ts +57 -24
  75. package/dist/runtime/utils.d.ts +3 -0
  76. package/dist/runtime/utils.mjs +11 -0
  77. package/package.json +17 -32
  78. package/virtual.d.ts +49 -0
  79. package/dist/client/_nuxt/IconCSS.8f429b14.css +0 -1
  80. package/dist/client/_nuxt/builds/meta/f929037e-e674-4060-8070-9dfb30335a28.json +0 -1
  81. package/dist/client/_nuxt/entry.434c2c45.css +0 -1
  82. package/dist/client/_nuxt/entry.6c0001e5.js +0 -137
  83. package/dist/client/grid.png +0 -0
  84. package/dist/runtime/components/OgImage/Cached.d.ts +0 -5
  85. package/dist/runtime/components/OgImage/Cached.mjs +0 -10
  86. package/dist/runtime/components/OgImage/Dynamic.d.ts +0 -8
  87. package/dist/runtime/components/OgImage/Dynamic.mjs +0 -10
  88. package/dist/runtime/components/OgImage/Static.d.ts +0 -8
  89. package/dist/runtime/components/OgImage/Static.mjs +0 -10
  90. package/dist/runtime/components/OgImage/WithoutCache.d.ts +0 -5
  91. package/dist/runtime/components/OgImage/WithoutCache.mjs +0 -10
  92. package/dist/runtime/components/OgImage/index.d.ts +0 -5
  93. package/dist/runtime/core/bindings/css-inline/mock.d.ts +0 -5
  94. package/dist/runtime/core/bindings/css-inline/mock.mjs +0 -3
  95. package/dist/runtime/core/html/fetch.d.ts +0 -3
  96. package/dist/runtime/server/routes/__og-image__/image-[path]-og.[extension].mjs +0 -45
  97. package/dist/runtime/utilts.d.ts +0 -2
  98. package/dist/runtime/utilts.mjs +0 -8
  99. /package/dist/runtime/server/routes/__og-image__/{image-[path]-og.[extension].d.ts → image.d.ts} +0 -0
@@ -1,37 +1,20 @@
1
- import { createError } from "h3";
2
- import { hash } from "ohash";
3
1
  import { createHeadCore } from "@unhead/vue";
4
- import twemoji from "twemoji";
5
2
  import { renderSSRHead } from "@unhead/ssr";
3
+ import { applyEmojis } from "./applyEmojis.mjs";
4
+ import { fetchIsland } from "./fetchIsland.mjs";
6
5
  import { useRuntimeConfig } from "#imports";
7
- export async function fetchHTML(e, options) {
6
+ export async function devIframeTemplate(ctx) {
7
+ const { options } = ctx;
8
8
  const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
9
- if (!options.component) {
10
- throw createError({
11
- statusCode: 500,
12
- statusMessage: `Nuxt OG Image trying to render an invalid component. Received options ${JSON.stringify(options)}`
13
- });
14
- }
15
- const hashId = hash([options.component, options, Math.random() * 100]);
16
- const island = await e.$fetch(`/__nuxt_island/${options.component}_${hashId}.json`, {
17
- params: {
18
- props: JSON.stringify(options)
19
- }
20
- });
9
+ const island = await fetchIsland(ctx);
21
10
  const head = createHeadCore();
22
11
  head.push(island.head);
23
12
  let defaultFontFamily = "sans-serif";
24
13
  const firstFont = fonts[0];
25
14
  if (firstFont)
26
15
  defaultFontFamily = firstFont.name;
16
+ await applyEmojis(ctx, island);
27
17
  let html = island.html;
28
- try {
29
- html = twemoji.parse(html, {
30
- folder: "svg",
31
- ext: ".svg"
32
- });
33
- } catch (e2) {
34
- }
35
18
  const googleFonts = {};
36
19
  fonts.filter((font) => !font.path).forEach((font) => {
37
20
  if (!googleFonts[font.name])
@@ -54,12 +37,6 @@ export async function fetchHTML(e, options) {
54
37
  height: ${options.height}px;
55
38
  overflow: hidden;
56
39
  background-color: ${options.mode === "dark" ? "#1b1b1b" : "#fff"};
57
- }
58
- img.emoji {
59
- height: 1em;
60
- width: 1em;
61
- margin: 0 .05em 0 .1em;
62
- vertical-align: -0.1em;
63
40
  }`
64
41
  },
65
42
  ...fonts.filter((font) => font.path).map((font) => {
@@ -98,6 +75,7 @@ img.emoji {
98
75
  rel: "stylesheet"
99
76
  },
100
77
  // have to add each weight as their own stylesheet
78
+ // we should use the local font file no?
101
79
  ...Object.entries(googleFonts).map(([name, fonts2]) => {
102
80
  return {
103
81
  href: `https://fonts.googleapis.com/css2?family=${name}:wght@${fonts2.map((f) => f.weight).join(";")}&display=swap`,
@@ -108,10 +86,9 @@ img.emoji {
108
86
  });
109
87
  html = html.replaceAll(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
110
88
  const headChunk = await renderSSRHead(head);
111
- const htmlTemplate = `<!DOCTYPE html>
89
+ return `<!DOCTYPE html>
112
90
  <html ${headChunk.htmlAttrs}>
113
91
  <head>${headChunk.headTags}</head>
114
92
  <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;">${html}</div>${headChunk.bodyTags}</body>
115
93
  </html>`;
116
- return htmlTemplate;
117
94
  }
@@ -0,0 +1,3 @@
1
+ import type { NuxtIslandResponse } from 'nuxt/dist/core/runtime/nitro/renderer';
2
+ import type { H3EventOgImageRender } from '../../types';
3
+ export declare function fetchIsland({ options, e }: H3EventOgImageRender): Promise<NuxtIslandResponse>;
@@ -0,0 +1,17 @@
1
+ import { createError } from "h3";
2
+ import { hash } from "ohash";
3
+ export function fetchIsland({ options, e }) {
4
+ if (!options.component) {
5
+ throw createError({
6
+ statusCode: 500,
7
+ statusMessage: `Nuxt OG Image trying to render an invalid component. Received options ${JSON.stringify(options)}`
8
+ });
9
+ }
10
+ const hashId = hash([options.component, options]);
11
+ const props = typeof options.props !== "undefined" ? options.props : options;
12
+ return e.$fetch(`/__nuxt_island/${options.component}_${hashId}.json`, {
13
+ params: {
14
+ props: JSON.stringify(props)
15
+ }
16
+ });
17
+ }
@@ -1,3 +1,3 @@
1
1
  import { type H3Error, type H3Event } from 'h3';
2
2
  import type { OgImageOptions } from '../../types';
3
- export declare function fetchPathHtmlAndExtractOptions(e: H3Event, path: string): Promise<H3Error | OgImageOptions>;
3
+ export declare function fetchPathHtmlAndExtractOptions(e: H3Event, path: string, key: string): Promise<H3Error | OgImageOptions>;
@@ -1,6 +1,10 @@
1
1
  import { createError } from "h3";
2
+ import { htmlPayloadCache } from "../cache/htmlPayload.mjs";
2
3
  import { extractAndNormaliseOgImageOptions } from "./extract.mjs";
3
- export async function fetchPathHtmlAndExtractOptions(e, path) {
4
+ export async function fetchPathHtmlAndExtractOptions(e, path, key) {
5
+ const cachedHtmlPayload = await htmlPayloadCache.getItem(key);
6
+ if (cachedHtmlPayload && cachedHtmlPayload.expiresAt < Date.now())
7
+ return cachedHtmlPayload.value;
4
8
  let html;
5
9
  try {
6
10
  html = await e.$fetch(path);
@@ -11,10 +15,11 @@ export async function fetchPathHtmlAndExtractOptions(e, path) {
11
15
  });
12
16
  }
13
17
  const payload = extractAndNormaliseOgImageOptions(html);
14
- if (!payload) {
15
- return createError({
16
- statusCode: 400,
17
- statusMessage: `The path is missing the Nuxt OG Image payload. Did you forget to use defineOgImage()?`
18
+ if (payload) {
19
+ await htmlPayloadCache.setItem(key, {
20
+ // 60 minutes for prerender, 10 seconds for runtime
21
+ expiresAt: Date.now() + 1e3 * (import.meta.prerender ? 60 * 60 : 10),
22
+ value: payload
18
23
  });
19
24
  }
20
25
  return payload;
@@ -1,2 +1,2 @@
1
- import type { OgImageOptions } from '../../types';
2
- export declare function normaliseOptions(_options: OgImageOptions): any;
1
+ import type { DefineOgImageInput, OgImageOptions, OgImagePrebuilt } from '../../types';
2
+ export declare function normaliseOptions(_options: DefineOgImageInput): OgImageOptions | OgImagePrebuilt;
@@ -1,11 +1,13 @@
1
- import { getOgImagePath } from "../../../utilts.mjs";
2
1
  import { prerenderChromiumContext } from "../../cache/prerender.mjs";
3
2
  import { createScreenshot } from "./screenshot.mjs";
4
3
  import { createBrowser } from "#nuxt-og-image/bindings/chromium";
5
4
  const ChromiumRenderer = {
6
5
  name: "chromium",
7
- supportedFormats: ["png", "jpeg"],
8
- async createImage(e, options) {
6
+ supportedFormats: ["png", "jpeg", "jpg"],
7
+ async debug() {
8
+ return {};
9
+ },
10
+ async createImage(ctx) {
9
11
  const browser = (import.meta.prerender ? prerenderChromiumContext.browser : null) || await createBrowser();
10
12
  if (!browser) {
11
13
  return createError({
@@ -15,10 +17,7 @@ const ChromiumRenderer = {
15
17
  }
16
18
  if (import.meta.prerender)
17
19
  prerenderChromiumContext.browser = browser;
18
- return createScreenshot(e, browser, {
19
- ...options,
20
- path: options.component === "PageScreenshot" ? options.path : getOgImagePath(options.path, "html")
21
- }).finally(async () => {
20
+ return createScreenshot(ctx, browser).finally(async () => {
22
21
  await browser.close();
23
22
  });
24
23
  }
@@ -1,6 +1,5 @@
1
1
  /// <reference types="node" />
2
2
  import type { Buffer } from 'node:buffer';
3
3
  import type { Browser } from 'playwright-core';
4
- import type { H3Event } from 'h3';
5
- import type { RendererOptions } from '../../../types';
6
- export declare function createScreenshot(e: H3Event, browser: Browser, options: RendererOptions): Promise<Buffer>;
4
+ import type { H3EventOgImageRender } from '../../../types';
5
+ export declare function createScreenshot({ e, options, extension }: H3EventOgImageRender, browser: Browser): Promise<Buffer>;
@@ -1,6 +1,7 @@
1
- import { withQuery } from "ufo";
1
+ import { joinURL, withQuery } from "ufo";
2
2
  import { useNitroOrigin } from "#imports";
3
- export async function createScreenshot(e, browser, options) {
3
+ export async function createScreenshot({ e, options, extension }, browser) {
4
+ const path = options.component === "PageScreenshot" ? options.path : joinURL("/__og-image__/image", options.path, `og.html`);
4
5
  const page = await browser.newPage({
5
6
  colorScheme: options.colorScheme,
6
7
  baseURL: useNitroOrigin(e)
@@ -23,7 +24,7 @@ export async function createScreenshot(e, browser, options) {
23
24
  }, html);
24
25
  await page.waitForLoadState("networkidle");
25
26
  } else {
26
- await page.goto(withQuery(options.path, options), {
27
+ await page.goto(withQuery(path, options.props), {
27
28
  timeout: 1e4,
28
29
  waitUntil: "networkidle"
29
30
  });
@@ -31,7 +32,7 @@ export async function createScreenshot(e, browser, options) {
31
32
  const screenshotOptions = {
32
33
  timeout: 1e4,
33
34
  animations: "disabled",
34
- type: options.extension
35
+ type: extension === "png" ? "png" : "jpeg"
35
36
  };
36
37
  if (options.mask) {
37
38
  await page.evaluate((mask) => {
@@ -1,3 +1,3 @@
1
- import type { FontConfig } from '../../../types';
1
+ import type { FontConfig, H3EventOgImageRender } from '../../../types';
2
2
  export declare const satoriFonts: any[];
3
- export declare function loadFonts(baseURL: string, fonts: FontConfig[]): Promise<any>;
3
+ export declare function loadFonts(e: H3EventOgImageRender, fonts: FontConfig[]): Promise<any>;
@@ -1,8 +1,8 @@
1
1
  import { loadFont } from "../../font/fetch.mjs";
2
2
  export const satoriFonts = [];
3
3
  let fontLoadPromise = null;
4
- export function loadFonts(baseURL, fonts) {
4
+ export function loadFonts(e, fonts) {
5
5
  if (fontLoadPromise)
6
6
  return fontLoadPromise;
7
- return fontLoadPromise = Promise.all(fonts.map((font) => loadFont(baseURL, font)));
7
+ return fontLoadPromise = Promise.all(fonts.map((font) => loadFont(e, font)));
8
8
  }
@@ -1,5 +1,4 @@
1
- import type { H3Event } from 'h3';
2
- import type { Renderer, RendererOptions } from '../../../types';
3
- export declare function createSvg(e: H3Event, options: RendererOptions): Promise<string>;
1
+ import type { H3EventOgImageRender, Renderer } from '../../../types';
2
+ export declare function createSvg(event: H3EventOgImageRender): Promise<string>;
4
3
  declare const SatoriRenderer: Renderer;
5
4
  export default SatoriRenderer;
@@ -3,11 +3,12 @@ import { createVNodes } from "./vnodes.mjs";
3
3
  import { loadFonts, satoriFonts } from "./fonts.mjs";
4
4
  import { useResvg, useSatori, useSharp } from "./instances.mjs";
5
5
  import { useRuntimeConfig } from "#imports";
6
- export async function createSvg(e, options) {
6
+ export async function createSvg(event) {
7
+ const { options } = event;
7
8
  const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
8
- const vnodes = await createVNodes(e, options);
9
+ const vnodes = await createVNodes(event);
9
10
  if (!satoriFonts.length)
10
- satoriFonts.push(...await loadFonts(e, fonts));
11
+ satoriFonts.push(...await loadFonts(event, fonts));
11
12
  const satori = await useSatori();
12
13
  return satori(vnodes, defu(options.satori, satoriOptions, {
13
14
  fonts: satoriFonts,
@@ -16,38 +17,40 @@ export async function createSvg(e, options) {
16
17
  height: options.height
17
18
  }));
18
19
  }
19
- async function createPng(e, options) {
20
+ async function createPng(event) {
20
21
  const { resvgOptions } = useRuntimeConfig()["nuxt-og-image"];
21
- const svg = await createSvg(e, options);
22
+ const svg = await createSvg(event);
22
23
  const Resvg = await useResvg();
23
24
  const resvg = new Resvg(svg, defu(
24
- options.resvg,
25
+ event.options.resvg,
25
26
  resvgOptions
26
27
  ));
27
28
  const pngData = resvg.render();
28
29
  return pngData.asPng();
29
30
  }
30
- async function createJpeg(e, options) {
31
+ async function createJpeg(event) {
31
32
  const { sharpOptions } = useRuntimeConfig()["nuxt-og-image"];
32
- const png = await createPng(e, options);
33
+ const png = await createPng(event);
33
34
  const sharp = await useSharp();
34
- return sharp(png, defu(options.sharp, sharpOptions)).jpeg(defu(options.sharp, sharpOptions)).toBuffer();
35
+ return sharp(png, defu(event.options.sharp, sharpOptions)).jpeg().toBuffer();
35
36
  }
36
37
  const SatoriRenderer = {
37
38
  name: "satori",
38
- supportedFormats: ["svg", "png", "jpeg", "jpg", "json"],
39
- async createImage(e, options) {
40
- switch (options.extension) {
41
- case "json":
42
- return createVNodes(e, options);
43
- case "svg":
44
- return createSvg(e, options);
39
+ supportedFormats: ["png", "jpeg", "jpg", "json"],
40
+ async createImage(e) {
41
+ switch (e.extension) {
45
42
  case "png":
46
- return createPng(e, options);
43
+ return createPng(e);
47
44
  case "jpeg":
48
45
  case "jpg":
49
- return createJpeg(e, options);
46
+ return createJpeg(e);
50
47
  }
48
+ },
49
+ async debug(e) {
50
+ return {
51
+ vnodes: await createVNodes(e),
52
+ svg: await createSvg(e)
53
+ };
51
54
  }
52
55
  };
53
56
  export default SatoriRenderer;
@@ -1,4 +1,6 @@
1
1
  import type _satori from 'satori';
2
+ export declare function useSatoriRenderer(): Promise<import("../../../types").Renderer>;
3
+ export declare function useChromiumRenderer(): Promise<import("../../../types").Renderer>;
2
4
  export declare function useResvg(): Promise<new (svg: string | Uint8Array, options?: import("@resvg/resvg-wasm").ResvgRenderOptions | undefined) => {
3
5
  free(): void;
4
6
  render(): {
@@ -37,3 +39,4 @@ export declare function useResvg(): Promise<new (svg: string | Uint8Array, optio
37
39
  }>;
38
40
  export declare function useSatori(): Promise<typeof _satori>;
39
41
  export declare function useSharp(): Promise<any>;
42
+ export declare function useCssInline(): Promise<any>;
@@ -1,6 +1,17 @@
1
+ const cssInlineInstance = { instance: void 0 };
1
2
  const sharpInstance = { instance: void 0 };
2
3
  const resvgInstance = { instance: void 0 };
3
4
  const satoriInstance = { instance: void 0 };
5
+ const satoriRendererInstance = { instance: void 0 };
6
+ const chromiumRendererInstance = { instance: void 0 };
7
+ export async function useSatoriRenderer() {
8
+ satoriRendererInstance.instance = satoriRendererInstance.instance || await import("#nuxt-og-image/renderers/satori").then((m) => m.default);
9
+ return satoriRendererInstance.instance;
10
+ }
11
+ export async function useChromiumRenderer() {
12
+ chromiumRendererInstance.instance = chromiumRendererInstance.instance || await import("#nuxt-og-image/renderers/chromium").then((m) => m.default);
13
+ return chromiumRendererInstance.instance;
14
+ }
4
15
  export async function useResvg() {
5
16
  resvgInstance.instance = resvgInstance.instance || await import("#nuxt-og-image/bindings/resvg").then((m) => m.default);
6
17
  await resvgInstance.instance.initWasmPromise;
@@ -15,3 +26,7 @@ export async function useSharp() {
15
26
  sharpInstance.instance = sharpInstance.instance || await import("#nuxt-og-image/bindings/sharp").then((m) => m.default);
16
27
  return sharpInstance.instance;
17
28
  }
29
+ export async function useCssInline() {
30
+ cssInlineInstance.instance = cssInlineInstance.instance || await import("#nuxt-og-image/bindings/css-inline").then((m) => m.default);
31
+ return cssInlineInstance.instance;
32
+ }
@@ -1,26 +1,28 @@
1
1
  import { defineSatoriTransformer } from "../utils.mjs";
2
2
  function isEmojiFilter(node) {
3
- return node.type === "img" && node.props?.class?.includes("emoji");
3
+ return node.type === "svg" && typeof node.props?.["data-emoji"] !== "undefined";
4
4
  }
5
5
  export default defineSatoriTransformer([
6
6
  // need to make sure parent div has flex for the emoji to render inline
7
7
  {
8
- filter: (node) => node.type === "div" && Array.isArray(node.props?.children) && node.props.children.some(isEmojiFilter),
8
+ filter: (node) => ["div", "p"].includes(node.type) && Array.isArray(node.props?.children) && node.props.children.some(isEmojiFilter),
9
9
  transform: async (node) => {
10
10
  node.props.style = node.props.style || {};
11
11
  node.props.style.display = "flex";
12
12
  node.props.style.alignItems = "center";
13
- }
14
- },
15
- {
16
- filter: isEmojiFilter,
17
- transform: async (node) => {
18
- node.props.style = node.props.style || {};
19
- node.props.style.height = "1em";
20
- node.props.style.width = "1em";
21
- node.props.style.margin = "0 .3em 0 .3em";
22
- node.props.style.verticalAlign = "0.1em";
23
- node.props.class = "";
13
+ node.props.children = node.props.children.map((child) => {
14
+ if (typeof child === "string") {
15
+ return {
16
+ type: "div",
17
+ props: {
18
+ children: child
19
+ }
20
+ };
21
+ }
22
+ if (child.props.class?.includes("emoji"))
23
+ delete child.props.class;
24
+ return child;
25
+ });
24
26
  }
25
27
  }
26
28
  ]);
@@ -6,8 +6,9 @@ import { toBase64Image } from "../../../env/assets.mjs";
6
6
  import { useNitroOrigin } from "#imports";
7
7
  export default defineSatoriTransformer({
8
8
  filter: (node) => node.type === "img",
9
- transform: async (node, e) => {
9
+ transform: async (node, { e }) => {
10
10
  const src = node.props?.src;
11
+ const isRelative = src?.startsWith("/");
11
12
  if (src) {
12
13
  let updated = false;
13
14
  let dimensions;
@@ -21,8 +22,11 @@ export default defineSatoriTransformer({
21
22
  });
22
23
  if (valid) {
23
24
  node.props.src = toBase64Image(src, response);
24
- const imageSize = sizeOf(Buffer.from(response));
25
- dimensions = { width: imageSize.width, height: imageSize.height };
25
+ try {
26
+ const imageSize = sizeOf(Buffer.from(response));
27
+ dimensions = { width: imageSize.width, height: imageSize.height };
28
+ } catch (e2) {
29
+ }
26
30
  updated = true;
27
31
  }
28
32
  }
@@ -37,7 +41,7 @@ export default defineSatoriTransformer({
37
41
  node.props.height = dimensions.height;
38
42
  }
39
43
  }
40
- if (!updated) {
44
+ if (!updated && isRelative) {
41
45
  node.props.src = `${withBase(src, `${useNitroOrigin(e)}`)}?${Date.now()}`;
42
46
  }
43
47
  }
@@ -1,4 +1,3 @@
1
- import type { H3Event } from 'h3';
2
- import type { SatoriTransformer, VNode } from '../../../types';
3
- export declare function walkSatoriTree(e: H3Event, node: VNode, plugins: (SatoriTransformer | SatoriTransformer[])[]): Promise<void>;
1
+ import type { H3EventOgImageRender, SatoriTransformer, VNode } from '../../../types';
2
+ export declare function walkSatoriTree(e: H3EventOgImageRender, node: VNode, plugins: (SatoriTransformer | SatoriTransformer[])[]): Promise<void>;
4
3
  export declare function defineSatoriTransformer(transformer: SatoriTransformer | SatoriTransformer[]): SatoriTransformer | SatoriTransformer[];
@@ -1,3 +1,2 @@
1
- import type { H3Event } from 'h3';
2
- import type { RendererOptions, VNode } from '../../../types';
3
- export declare function createVNodes(e: H3Event, options: RendererOptions): Promise<VNode>;
1
+ import type { H3EventOgImageRender, VNode } from '../../../types';
2
+ export declare function createVNodes(ctx: H3EventOgImageRender): Promise<VNode>;
@@ -1,16 +1,24 @@
1
1
  import { html as convertHtmlToSatori } from "satori-html";
2
- import { fetchHTML } from "../../html/fetch.mjs";
2
+ import { fetchIsland } from "../../html/fetchIsland.mjs";
3
+ import { applyInlineCss } from "../../html/applyInlineCss.mjs";
4
+ import { applyEmojis } from "../../html/applyEmojis.mjs";
3
5
  import { walkSatoriTree } from "./utils.mjs";
4
6
  import emojis from "./plugins/emojis.mjs";
5
7
  import twClasses from "./plugins/twClasses.mjs";
6
8
  import imageSrc from "./plugins/imageSrc.mjs";
7
9
  import flex from "./plugins/flex.mjs";
8
10
  import encoding from "./plugins/encoding.mjs";
9
- export async function createVNodes(e, options) {
10
- const html = options.html || await fetchHTML(e, options);
11
- const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1] || html;
12
- const satoriTree = convertHtmlToSatori(body);
13
- await walkSatoriTree(e, satoriTree, [
11
+ export async function createVNodes(ctx) {
12
+ let html = ctx.options.html;
13
+ if (!html) {
14
+ const island = await fetchIsland(ctx);
15
+ await applyInlineCss(ctx, island);
16
+ await applyEmojis(ctx, island);
17
+ html = island.html;
18
+ }
19
+ const template = `<div data-v-inspector-ignore="true" style="position: relative; display: flex; margin: 0 auto; width: ${ctx.options.width}px; height: ${ctx.options.height}px; overflow: hidden;">${html}</div>`;
20
+ const satoriTree = convertHtmlToSatori(template);
21
+ await walkSatoriTree(ctx, satoriTree, [
14
22
  emojis,
15
23
  twClasses,
16
24
  imageSrc,
@@ -1,7 +1,3 @@
1
1
  import type { H3Error, H3Event } from 'h3';
2
- import type { Renderer, RuntimeOgImageOptions } from '../../types';
3
- export declare function resolveRendererContext(e: H3Event): Promise<H3Error | {
4
- extension: RuntimeOgImageOptions['extension'];
5
- renderer: Renderer;
6
- options: RuntimeOgImageOptions;
7
- }>;
2
+ import type { H3EventOgImageRender } from '../../types';
3
+ export declare function resolveRendererContext(e: H3Event): Promise<H3Error | H3EventOgImageRender>;
@@ -2,11 +2,11 @@ import { parseURL, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from
2
2
  import { createError, getQuery } from "h3";
3
3
  import { defu } from "defu";
4
4
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
5
+ import { hash } from "ohash";
5
6
  import { fetchPathHtmlAndExtractOptions } from "../options/fetch.mjs";
6
7
  import { prerenderCache } from "../cache/prerender.mjs";
7
- import { useRuntimeConfig } from "#imports";
8
- const satoriRendererInstance = { instance: void 0 };
9
- const chromiumRendererInstance = { instance: void 0 };
8
+ import { useChromiumRenderer, useSatoriRenderer } from "../renderers/satori/instances.mjs";
9
+ import { useRuntimeConfig, useSiteConfig } from "#imports";
10
10
  export async function resolveRendererContext(e) {
11
11
  const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
12
12
  const path = parseURL(e.path).pathname;
@@ -17,22 +17,35 @@ export async function resolveRendererContext(e) {
17
17
  statusMessage: `Missing OG Image type.`
18
18
  });
19
19
  }
20
+ if (!["png", "jpeg", "jpg", "svg", "html", "json"].includes(extension)) {
21
+ return createError({
22
+ statusCode: 400,
23
+ statusMessage: `Unknown OG Image type ${extension}.`
24
+ });
25
+ }
20
26
  const basePath = withoutTrailingSlash(
21
- path.replace("/__og-image__/image", "").replace(`/og.${extension}`, "")
27
+ path.replace(`/__og-image__/image`, "").replace(`/og.${extension}`, "")
22
28
  );
23
29
  const queryParams = { ...getQuery(e) };
30
+ const isDebugJsonPayload = extension === "json" && runtimeConfig.debug;
31
+ const siteConfig = useSiteConfig(e);
32
+ const key = [
33
+ withoutLeadingSlash(basePath === "/" || !basePath ? "index" : basePath).replaceAll("/", "-"),
34
+ hash([
35
+ basePath,
36
+ siteConfig.url,
37
+ hash(queryParams)
38
+ ])
39
+ ].join(":");
24
40
  let options = queryParams.options;
25
41
  if (!options) {
26
42
  if (import.meta.prerender) {
27
- const key = [
28
- withoutLeadingSlash(basePath === "/" || !basePath ? "index" : basePath).replaceAll("/", "-")
29
- ].join(":");
30
43
  options = await prerenderCache?.getItem(key);
31
44
  } else {
32
- const payloadOptions = await fetchPathHtmlAndExtractOptions(e, basePath);
33
- if (payloadOptions instanceof Error)
34
- return payloadOptions;
35
- options = payloadOptions;
45
+ const payload = await fetchPathHtmlAndExtractOptions(e, basePath, key);
46
+ if (payload instanceof Error)
47
+ return payload;
48
+ options = payload;
36
49
  }
37
50
  }
38
51
  delete queryParams.options;
@@ -41,8 +54,8 @@ export async function resolveRendererContext(e) {
41
54
  );
42
55
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
43
56
  withoutBase(basePath.split("?")[0], useRuntimeConfig().app.baseURL)
44
- ).reverse()).ogImage;
45
- options = defu(queryParams, routeRules, options, runtimeConfig.defaults);
57
+ ).reverse());
58
+ options = defu(queryParams, routeRules.ogImage, options, runtimeConfig.defaults);
46
59
  if (!options) {
47
60
  return createError({
48
61
  statusCode: 404,
@@ -52,25 +65,25 @@ export async function resolveRendererContext(e) {
52
65
  let renderer;
53
66
  switch (options.renderer) {
54
67
  case "satori":
55
- renderer = satoriRendererInstance.instance = satoriRendererInstance.instance || await import("#nuxt-og-image/renderers/satori").then((m) => Object.keys(m.default).length ? m.default : false);
68
+ renderer = await useSatoriRenderer();
56
69
  break;
57
70
  case "chromium":
58
- renderer = chromiumRendererInstance.instance = chromiumRendererInstance.instance || await import("#nuxt-og-image/renderers/chromium").then((m) => Object.keys(m.default).length ? m.default : false);
71
+ renderer = await useChromiumRenderer();
59
72
  break;
60
73
  }
61
- if (!renderer) {
74
+ if (!renderer || renderer.__unenv__) {
62
75
  throw createError({
63
76
  statusCode: 400,
64
77
  statusMessage: `Renderer ${options.renderer} is missing.`
65
78
  });
66
79
  }
67
80
  return {
81
+ e,
82
+ key,
68
83
  renderer,
84
+ isDebugJsonPayload,
69
85
  extension,
70
- options: {
71
- ...options,
72
- extension: extension === "json" ? options.extension : extension,
73
- path: basePath
74
- }
86
+ basePath,
87
+ options
75
88
  };
76
89
  }
@@ -1,6 +1,6 @@
1
1
  import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
2
2
  import { defu } from "defu";
3
- import { getOgImagePath } from "../../utilts.mjs";
3
+ import { getOgImagePath } from "../../utils.mjs";
4
4
  import { useRuntimeConfig } from "#imports";
5
5
  export default defineNitroPlugin((nitroApp) => {
6
6
  nitroApp.hooks.hook("content:file:afterParse", async (content) => {
@@ -10,7 +10,7 @@ export default defineNitroPlugin((nitroApp) => {
10
10
  const ogImageConfig = typeof content.ogImage === "object" ? content.ogImage : {};
11
11
  const { defaults } = useRuntimeConfig()["nuxt-og-image"];
12
12
  const optionsWithDefault = defu(ogImageConfig, defaults);
13
- const src = getOgImagePath(content.path, optionsWithDefault.extension);
13
+ const src = getOgImagePath(content.path, optionsWithDefault);
14
14
  const payload = {
15
15
  title: content.title,
16
16
  excerpt: content.description || content.excerpt,
@@ -1,9 +1,9 @@
1
1
  import { parseURL, withoutLeadingSlash } from "ufo";
2
2
  import { getRouteRules } from "nitropack/dist/runtime/route-rules";
3
+ import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
3
4
  import { extractAndNormaliseOgImageOptions } from "../../core/options/extract.mjs";
4
5
  import { prerenderCache, prerenderChromiumContext } from "../../core/cache/prerender.mjs";
5
- import { isInternalRoute } from "../../utilts.mjs";
6
- import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
6
+ import { isInternalRoute } from "../../utils.mjs";
7
7
  export default defineNitroPlugin(async (nitro) => {
8
8
  if (!import.meta.prerender)
9
9
  return;
@@ -1,6 +1,6 @@
1
1
  import { parseURL } from "ufo";
2
2
  import { toValue } from "vue";
3
- import { isInternalRoute } from "../../utilts.mjs";
3
+ import { isInternalRoute } from "../../utils.mjs";
4
4
  import { useRequestEvent, withSiteUrl } from "#imports";
5
5
  export default defineNuxtPlugin((nuxtApp) => {
6
6
  nuxtApp.hooks.hook("app:rendered", async (ctx) => {