nuxt-og-image 3.0.0-beta.10 → 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 (107) hide show
  1. package/dist/client/200.html +5 -5
  2. package/dist/client/404.html +5 -5
  3. package/dist/client/_nuxt/{IconCSS.389cad49.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.91508473.js → error-404.8bab7a15.js} +1 -1
  10. package/dist/client/_nuxt/{error-500.b66f3aae.js → error-500.b72200bc.js} +1 -1
  11. package/dist/client/index.html +5 -5
  12. package/dist/module.d.mts +40 -23
  13. package/dist/module.d.ts +40 -23
  14. package/dist/module.json +2 -2
  15. package/dist/module.mjs +68 -53
  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/bindings/resvg/wasm.mjs +2 -5
  32. package/dist/runtime/core/bindings/satori/wasm.mjs +7 -0
  33. package/dist/runtime/core/cache/emojis.d.ts +1 -0
  34. package/dist/runtime/core/cache/emojis.mjs +5 -0
  35. package/dist/runtime/core/cache/htmlPayload.d.ts +5 -0
  36. package/dist/runtime/core/cache/htmlPayload.mjs +6 -0
  37. package/dist/runtime/core/cache/prerender.d.ts +1 -1
  38. package/dist/runtime/core/font/fetch.d.ts +2 -3
  39. package/dist/runtime/core/font/fetch.mjs +10 -4
  40. package/dist/runtime/core/html/applyEmojis.d.ts +3 -0
  41. package/dist/runtime/core/html/applyEmojis.mjs +37 -0
  42. package/dist/runtime/core/html/applyInlineCss.d.ts +3 -0
  43. package/dist/runtime/core/html/applyInlineCss.mjs +32 -0
  44. package/dist/runtime/core/html/devIframeTemplate.d.ts +2 -0
  45. package/dist/runtime/core/html/{fetch.mjs → devIframeTemplate.mjs} +8 -31
  46. package/dist/runtime/core/html/fetchIsland.d.ts +3 -0
  47. package/dist/runtime/core/html/fetchIsland.mjs +17 -0
  48. package/dist/runtime/core/options/fetch.d.ts +1 -1
  49. package/dist/runtime/core/options/fetch.mjs +10 -5
  50. package/dist/runtime/core/options/normalise.d.ts +2 -2
  51. package/dist/runtime/core/renderers/chromium/index.mjs +6 -7
  52. package/dist/runtime/core/renderers/chromium/screenshot.d.ts +2 -3
  53. package/dist/runtime/core/renderers/chromium/screenshot.mjs +5 -4
  54. package/dist/runtime/core/renderers/satori/fonts.d.ts +2 -2
  55. package/dist/runtime/core/renderers/satori/fonts.mjs +2 -2
  56. package/dist/runtime/core/renderers/satori/index.d.ts +2 -3
  57. package/dist/runtime/core/renderers/satori/index.mjs +21 -18
  58. package/dist/runtime/core/renderers/satori/instances.d.ts +3 -0
  59. package/dist/runtime/core/renderers/satori/instances.mjs +15 -0
  60. package/dist/runtime/core/renderers/satori/plugins/emojis.mjs +15 -13
  61. package/dist/runtime/core/renderers/satori/plugins/imageSrc.mjs +8 -4
  62. package/dist/runtime/core/renderers/satori/utils.d.ts +2 -3
  63. package/dist/runtime/core/renderers/satori/vnodes.d.ts +2 -3
  64. package/dist/runtime/core/renderers/satori/vnodes.mjs +14 -6
  65. package/dist/runtime/core/utils/resolveRendererContext.d.ts +2 -6
  66. package/dist/runtime/core/utils/resolveRendererContext.mjs +34 -21
  67. package/dist/runtime/core/utils/wasm.d.ts +1 -0
  68. package/dist/runtime/core/utils/wasm.mjs +6 -0
  69. package/dist/runtime/nitro/plugins/nuxt-content.mjs +2 -2
  70. package/dist/runtime/nitro/plugins/prerender.mjs +2 -2
  71. package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +1 -1
  72. package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +14 -47
  73. package/dist/runtime/nuxt/utils.d.ts +2 -0
  74. package/dist/runtime/nuxt/utils.mjs +55 -0
  75. package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +0 -2
  76. package/dist/runtime/server/routes/__og-image__/debug.json.mjs +2 -7
  77. package/dist/runtime/server/routes/__og-image__/image.mjs +86 -0
  78. package/dist/runtime/types.d.ts +57 -24
  79. package/dist/runtime/utils.d.ts +3 -0
  80. package/dist/runtime/utils.mjs +11 -0
  81. package/package.json +17 -32
  82. package/virtual.d.ts +49 -0
  83. package/dist/client/_nuxt/IconCSS.8f429b14.css +0 -1
  84. package/dist/client/_nuxt/builds/meta/3185e4a0-6b15-4476-b2f3-85ef31d35fc6.json +0 -1
  85. package/dist/client/_nuxt/entry.434c2c45.css +0 -1
  86. package/dist/client/_nuxt/entry.e62f04d6.js +0 -137
  87. package/dist/client/grid.png +0 -0
  88. package/dist/runtime/components/OgImage/Cached.d.ts +0 -5
  89. package/dist/runtime/components/OgImage/Cached.mjs +0 -10
  90. package/dist/runtime/components/OgImage/Dynamic.d.ts +0 -8
  91. package/dist/runtime/components/OgImage/Dynamic.mjs +0 -10
  92. package/dist/runtime/components/OgImage/Static.d.ts +0 -8
  93. package/dist/runtime/components/OgImage/Static.mjs +0 -10
  94. package/dist/runtime/components/OgImage/WithoutCache.d.ts +0 -5
  95. package/dist/runtime/components/OgImage/WithoutCache.mjs +0 -10
  96. package/dist/runtime/components/OgImage/index.d.ts +0 -5
  97. package/dist/runtime/core/bindings/css-inline/mock.d.ts +0 -5
  98. package/dist/runtime/core/bindings/css-inline/mock.mjs +0 -3
  99. package/dist/runtime/core/bindings/satori/yoga-wasm.mjs +0 -7
  100. package/dist/runtime/core/bindings/sharp/wasm.d.ts +0 -2
  101. package/dist/runtime/core/bindings/sharp/wasm.mjs +0 -2
  102. package/dist/runtime/core/html/fetch.d.ts +0 -3
  103. package/dist/runtime/server/routes/__og-image__/image-[path]-og.[extension].mjs +0 -45
  104. package/dist/runtime/utilts.d.ts +0 -2
  105. package/dist/runtime/utilts.mjs +0 -8
  106. /package/dist/runtime/core/bindings/satori/{yoga-wasm.d.ts → wasm.d.ts} +0 -0
  107. /package/dist/runtime/server/routes/__og-image__/{image-[path]-og.[extension].d.ts → image.d.ts} +0 -0
@@ -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
  }
@@ -0,0 +1 @@
1
+ export declare function importWasm(input: any): Promise<any>;
@@ -0,0 +1,6 @@
1
+ export async function importWasm(input) {
2
+ const _input = await input;
3
+ const _module = _input.default || _input;
4
+ const _instance = typeof _module === "function" ? await _module({}).then((r) => r.instance || r) : await WebAssembly.instantiate(_module, {});
5
+ return _instance.exports;
6
+ }
@@ -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) => {
@@ -2,7 +2,8 @@ import { defu } from "defu";
2
2
  import { parseURL, withoutBase } from "ufo";
3
3
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
4
4
  import { normaliseOptions } from "../../core/options/normalise.mjs";
5
- import { getOgImagePath, isInternalRoute } from "../../utilts.mjs";
5
+ import { getOgImagePath, isInternalRoute } from "../../utils.mjs";
6
+ import { createOgImageMeta } from "../utils.mjs";
6
7
  import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig } from "#imports";
7
8
  export default defineNuxtPlugin((nuxtApp) => {
8
9
  nuxtApp.hooks.hook("app:rendered", async (ctx) => {
@@ -14,59 +15,25 @@ export default defineNuxtPlugin((nuxtApp) => {
14
15
  const _routeRulesMatcher = toRouteMatcher(
15
16
  createRadixRouter({ routes: ssrContext?.runtimeConfig?.nitro?.routeRules })
16
17
  );
17
- const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
18
+ let routeRules = defu({}, ..._routeRulesMatcher.matchAll(
18
19
  withoutBase(path.split("?")[0], ssrContext?.runtimeConfig?.app.baseURL)
19
20
  ).reverse()).ogImage;
20
21
  if (typeof routeRules === "undefined")
21
22
  return;
22
- const payloadIndex = ssrContext.head.headEntries().findIndex((entry) => {
23
- return entry.input?.script?.[0]?.id === "nuxt-og-image-options";
24
- });
25
- if (payloadIndex >= 0 && routeRules === false) {
26
- ssrContext.head.headEntries().splice(payloadIndex, 1);
23
+ const ogImageInstances = nuxtApp.ssrContext._ogImageInstances || [];
24
+ if (routeRules === false) {
25
+ ogImageInstances?.forEach((e2) => {
26
+ e2.dispose();
27
+ });
28
+ nuxtApp.ssrContext._ogImageInstances = void 0;
27
29
  return;
28
30
  }
29
- if (payloadIndex >= 0)
31
+ if (ogImageInstances.length >= 0)
30
32
  return;
31
- const options = normaliseOptions(routeRules);
33
+ routeRules = defu(nuxtApp.ssrContext?.event.context._nitro?.routeRules?.ogImage, routeRules);
32
34
  const { defaults } = useRuntimeConfig()["nuxt-og-image"];
33
- const optionsWithDefault = normaliseOptions(defu(options, defaults));
34
- const src = getOgImagePath(ssrContext.url, optionsWithDefault.extension);
35
- ssrContext?.head.push({
36
- script: [
37
- {
38
- id: "nuxt-og-image-options",
39
- type: "application/json",
40
- processTemplateParams: true,
41
- innerHTML: () => {
42
- const payload = {
43
- title: "%s"
44
- };
45
- Object.entries(options).forEach(([key, val]) => {
46
- payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
47
- });
48
- return payload;
49
- },
50
- // we want this to be last in our head
51
- tagPosition: "bodyClose"
52
- }
53
- ],
54
- meta: [
55
- { property: "og:image", content: src },
56
- { property: "og:image:width", content: optionsWithDefault.width },
57
- { property: "og:image:height", content: optionsWithDefault.height },
58
- { property: "og:image:type", content: `image/${optionsWithDefault.extension}` },
59
- { property: "og:image:alt", content: optionsWithDefault.alt },
60
- // twitter
61
- { name: "twitter:card", content: "summary_large_image" },
62
- { name: "twitter:image:src", content: src },
63
- { name: "twitter:image:width", content: optionsWithDefault.width },
64
- { name: "twitter:image:height", content: optionsWithDefault.height },
65
- { name: "twitter:image:alt", content: optionsWithDefault.alt }
66
- ]
67
- }, {
68
- mode: "server",
69
- tagPriority: 35
70
- });
35
+ const resolvedOptions = normaliseOptions(defu(routeRules, defaults));
36
+ const src = getOgImagePath(ssrContext.url, resolvedOptions);
37
+ createOgImageMeta(src, {}, resolvedOptions, nuxtApp.ssrContext);
71
38
  });
72
39
  });
@@ -0,0 +1,2 @@
1
+ import type { OgImageOptions, OgImagePrebuilt } from '../types';
2
+ export declare function createOgImageMeta(src: string | null, input: OgImageOptions | OgImagePrebuilt, resolvedOptions: OgImageOptions, ssrContext: Record<string, any>): void;
@@ -0,0 +1,55 @@
1
+ import { useServerHead } from "#imports";
2
+ export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
3
+ const url = src || input.url || resolvedOptions.url;
4
+ let urlExtension = (url.split("/").pop() || url).split(".").pop() || resolvedOptions.extension;
5
+ if (urlExtension === "jpg")
6
+ urlExtension = "jpeg";
7
+ const meta = [
8
+ { property: "og:image", content: url },
9
+ { property: "og:image:type", content: `image/${urlExtension}` },
10
+ { name: "twitter:card", content: "summary_large_image" },
11
+ { name: "twitter:image:src", content: url }
12
+ ];
13
+ if (resolvedOptions.width) {
14
+ meta.push({ property: "og:image:width", content: resolvedOptions.width });
15
+ meta.push({ name: "twitter:image:width", content: resolvedOptions.width });
16
+ }
17
+ if (resolvedOptions.height) {
18
+ meta.push({ property: "og:image:height", content: resolvedOptions.height });
19
+ meta.push({ name: "twitter:image:height", content: resolvedOptions.height });
20
+ }
21
+ if (resolvedOptions.alt) {
22
+ meta.push({ property: "og:image:alt", content: resolvedOptions.alt });
23
+ meta.push({ name: "twitter:image:alt", content: resolvedOptions.alt });
24
+ }
25
+ ssrContext._ogImageInstances = ssrContext._ogImageInstances || [];
26
+ const script = [];
27
+ if (src) {
28
+ script.push({
29
+ id: "nuxt-og-image-options",
30
+ type: "application/json",
31
+ processTemplateParams: true,
32
+ innerHTML: () => {
33
+ const payload = {
34
+ title: "%s"
35
+ };
36
+ delete input.url;
37
+ Object.entries({ ...input, url: void 0 }).forEach(([key, val]) => {
38
+ if (typeof val !== "undefined") {
39
+ payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
40
+ }
41
+ });
42
+ return payload;
43
+ },
44
+ // we want this to be last in our head
45
+ tagPosition: "bodyClose"
46
+ });
47
+ }
48
+ const instance = useServerHead({
49
+ script,
50
+ meta
51
+ }, {
52
+ tagPriority: 35
53
+ });
54
+ ssrContext._ogImageInstances.push(instance);
55
+ }
@@ -5,7 +5,5 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
5
5
  };
6
6
  componentNames: any;
7
7
  runtimeConfig: any;
8
- baseCacheKey: string;
9
- cachedKeys: string[];
10
8
  }>>;
11
9
  export default _default;
@@ -1,21 +1,16 @@
1
1
  import { defineEventHandler, setHeader } from "h3";
2
- import { prefixStorage } from "unstorage";
3
- import { useRuntimeConfig, useSiteConfig, useStorage } from "#imports";
2
+ import { useRuntimeConfig, useSiteConfig } from "#imports";
4
3
  import { componentNames } from "#nuxt-og-image/component-names.mjs";
5
4
  export default defineEventHandler(async (e) => {
6
5
  setHeader(e, "Content-Type", "application/json");
7
6
  const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
8
7
  const siteConfig = await useSiteConfig(e, { debug: true });
9
- const baseCacheKey = runtimeConfig.runtimeCacheStorage === "default" ? `/cache/nuxt-og-image@${runtimeConfig.version}` : `/nuxt-og-image@${runtimeConfig.version}`;
10
- const cache = prefixStorage(useStorage(), `${baseCacheKey}/`);
11
8
  return {
12
9
  siteConfigUrl: {
13
10
  value: siteConfig.url,
14
11
  source: siteConfig._context.url || "unknown"
15
12
  },
16
13
  componentNames,
17
- runtimeConfig,
18
- baseCacheKey,
19
- cachedKeys: await cache.getKeys()
14
+ runtimeConfig
20
15
  };
21
16
  });
@@ -0,0 +1,86 @@
1
+ import { H3Error, createError, defineEventHandler, getQuery, setHeader } from "h3";
2
+ import { resolveRendererContext } from "../../../core/utils/resolveRendererContext.mjs";
3
+ import { fetchIsland } from "../../../core/html/fetchIsland.mjs";
4
+ import { devIframeTemplate } from "../../../core/html/devIframeTemplate.mjs";
5
+ import { applyInlineCss } from "../../../core/html/applyInlineCss.mjs";
6
+ import { useOgImageBufferCache } from "../../../cache.mjs";
7
+ import { useRuntimeConfig, useSiteConfig } from "#imports";
8
+ export default defineEventHandler(async (e) => {
9
+ const ctx = await resolveRendererContext(e);
10
+ if (ctx instanceof H3Error)
11
+ return ctx;
12
+ const { isDebugJsonPayload, extension, options, renderer } = ctx;
13
+ const { debug, baseCacheKey } = useRuntimeConfig()["nuxt-og-image"];
14
+ const compatibility = [];
15
+ if (isDebugJsonPayload) {
16
+ const queryExtension = getQuery(e).extension || ctx.options.extension;
17
+ if (["jpeg", "jpg"].includes(queryExtension) && options.renderer === "satori")
18
+ compatibility.push("Converting PNGs to JPEGs requires Sharp which only runs on Node based systems.");
19
+ if (options.renderer === "chromium")
20
+ compatibility.push("Using Chromium to generate images is only supported in Node based environments. It's recommended to only use this if you're prerendering");
21
+ if (await applyInlineCss(ctx, await fetchIsland(ctx)))
22
+ compatibility.push("Inlining CSS is only supported in Node based environments.");
23
+ setHeader(e, "Content-Type", "application/json");
24
+ return {
25
+ compatibility,
26
+ ...ctx,
27
+ siteConfig: useSiteConfig(e),
28
+ ...options.renderer === "satori" ? await renderer.debug(ctx) : void 0
29
+ };
30
+ }
31
+ switch (extension) {
32
+ case "html":
33
+ setHeader(e, "Content-Type", `text/html`);
34
+ return devIframeTemplate(ctx);
35
+ case "svg":
36
+ if (!debug && !import.meta.dev) {
37
+ return createError({
38
+ statusCode: 404
39
+ });
40
+ }
41
+ if (ctx.renderer.name !== "satori") {
42
+ return createError({
43
+ statusCode: 400,
44
+ statusMessage: `Generating ${extension}'s with ${renderer.name} is not supported.`
45
+ });
46
+ }
47
+ setHeader(e, "Content-Type", `image/svg+xml`);
48
+ return (await ctx.renderer.debug(ctx)).svg;
49
+ case "png":
50
+ case "jpeg":
51
+ case "jpg":
52
+ if (!renderer.supportedFormats.includes(extension)) {
53
+ return createError({
54
+ statusCode: 400,
55
+ statusMessage: `Generating ${extension}'s with ${renderer.name} is not supported.`
56
+ });
57
+ }
58
+ setHeader(e, "Content-Type", `image/${extension === "jpg" ? "jpeg" : extension}`);
59
+ break;
60
+ default:
61
+ return createError({
62
+ statusCode: 400,
63
+ statusMessage: `Invalid request for og.${extension}.`
64
+ });
65
+ }
66
+ const cacheApi = await useOgImageBufferCache(ctx, {
67
+ cacheMaxAgeSeconds: ctx.options.cacheMaxAgeSeconds,
68
+ baseCacheKey
69
+ });
70
+ if (typeof cacheApi === "undefined")
71
+ return;
72
+ let image = cacheApi.cachedItem;
73
+ if (!image) {
74
+ image = await renderer.createImage(ctx);
75
+ if (image instanceof H3Error)
76
+ return image;
77
+ if (!image) {
78
+ return createError({
79
+ statusCode: 500,
80
+ statusMessage: `Failed to generate og.${extension}.`
81
+ });
82
+ }
83
+ await cacheApi.update(image);
84
+ }
85
+ return image;
86
+ });
@@ -1,9 +1,19 @@
1
- /// <reference types="node" />
2
- import type { Buffer } from 'node:buffer';
3
1
  import type { html } from 'satori-html';
4
2
  import type { H3Error, H3Event } from 'h3';
5
3
  import type { ResvgRenderOptions } from '@resvg/resvg-js';
6
4
  import type { SatoriOptions } from 'satori';
5
+ import type { AllowedComponentProps, Component, ComponentCustomProps, VNodeProps } from '@vue/runtime-core';
6
+ import type { OgImageComponents } from '#nuxt-og-image/components';
7
+ export interface H3EventOgImageRender {
8
+ e: H3Event;
9
+ extension: 'png' | 'jpeg' | 'jpg' | 'svg' | 'html' | 'json';
10
+ key: string;
11
+ basePath: string;
12
+ renderer: Renderer;
13
+ options: OgImageOptions;
14
+ isDebugJsonPayload: boolean;
15
+ }
16
+ export type IconifyEmojiIconSets = 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'streamline-emojis' | 'openmoji';
7
17
  export interface OgImageComponent {
8
18
  path?: string;
9
19
  pascalName: string;
@@ -33,31 +43,52 @@ export interface ScreenshotOptions {
33
43
  */
34
44
  delay?: number;
35
45
  }
36
- export interface OgImageOptions extends Partial<ScreenshotOptions> {
37
- component?: string;
38
- renderer?: 'chromium' | 'satori';
39
- extension?: 'png' | 'jpeg' | 'jpg';
46
+ export type OgImagePrebuilt = {
47
+ url: string;
48
+ } & Pick<OgImageOptions, 'width' | 'height' | 'alt'>;
49
+ export type DefineOgImageInput = OgImageOptions | OgImagePrebuilt | false;
50
+ export interface OgImageOptions<T extends keyof OgImageComponents = 'Fallback'> {
51
+ /**
52
+ * The width of the screenshot.
53
+ *
54
+ * @default 1200
55
+ */
56
+ width?: number;
57
+ /**
58
+ * The height of the screenshot.
59
+ *
60
+ * @default 630
61
+ */
62
+ height?: number;
40
63
  /**
41
- * @deprecated use renderer. Replace `browser` with `chromium`
64
+ * The alt text for the image.
42
65
  */
43
- provider?: 'browser' | 'satori';
66
+ alt?: string;
67
+ /**
68
+ * Use a prebuilt image instead of generating one.
69
+ *
70
+ * Should be an absolute URL.
71
+ */
72
+ url?: string;
73
+ /**
74
+ * The name of the component to render.
75
+ */
76
+ component?: T | string;
77
+ /**
78
+ * Props to pass to the component.
79
+ */
80
+ props?: OgImageComponents[T] | Record<string, any>;
81
+ renderer?: 'chromium' | 'satori';
82
+ extension?: 'png' | 'jpeg' | 'jpg';
83
+ emojis?: IconifyEmojiIconSets;
44
84
  /**
45
85
  * Provide a static HTML template to render the OG Image instead of a component.
46
86
  */
47
87
  html?: string;
48
- cache?: boolean;
49
- cacheKey?: string;
50
88
  resvg?: ResvgRenderOptions;
51
89
  satori?: SatoriOptions;
52
- /**
53
- * The time to live of the cache in milliseconds.
54
- */
55
- cacheTtl?: number;
56
- [key: string]: any;
57
- }
58
- export interface RuntimeOgImageOptions extends Omit<OgImageOptions, 'extension'> {
59
- extension: 'png' | 'jpeg' | 'jpg' | 'svg' | 'json' | 'html';
60
- path: string;
90
+ screenshot?: Partial<ScreenshotOptions>;
91
+ cacheMaxAgeSeconds?: number;
61
92
  }
62
93
  export interface FontConfig {
63
94
  name: string;
@@ -65,17 +96,19 @@ export interface FontConfig {
65
96
  path?: string;
66
97
  }
67
98
  export type InputFontConfig = (`${string}:${number}` | FontConfig);
68
- export type RendererOptions = Omit<RuntimeOgImageOptions, 'extension'> & {
69
- extension: Omit<RuntimeOgImageOptions['extension'], 'html'>;
99
+ export type RendererOptions = Omit<OgImageOptions, 'extension'> & {
100
+ extension: Omit<OgImageOptions['extension'], 'html'>;
70
101
  };
71
102
  export interface Renderer {
72
103
  name: 'chromium' | 'satori';
73
104
  supportedFormats: Partial<RendererOptions['extension']>[];
74
- createImage: (e: H3Event, options: RendererOptions) => Promise<H3Error | Buffer>;
105
+ createImage: (e: H3EventOgImageRender) => Promise<H3Error | BufferSource | void>;
106
+ debug: (e: H3EventOgImageRender) => Promise<Record<string, any>>;
75
107
  }
76
- export type OgImageScreenshotOptions = Omit<OgImageOptions, 'component'>;
108
+ export type ExtractComponentProps<T extends Component> = T extends new (...args: any) => any ? Omit<InstanceType<T>['$props'], keyof ComponentCustomProps | keyof VNodeProps | keyof AllowedComponentProps> : never;
109
+ export type OgImagePageScreenshotOptions = Omit<OgImageOptions, 'html' | 'renderer' | 'component' | 'satori' | 'resvg' | 'sharp'>;
77
110
  export type VNode = ReturnType<typeof html>;
78
111
  export interface SatoriTransformer {
79
112
  filter: (node: VNode) => boolean;
80
- transform: (node: VNode, e: H3Event) => Promise<void>;
113
+ transform: (node: VNode, e: H3EventOgImageRender) => Promise<void>;
81
114
  }
@@ -0,0 +1,3 @@
1
+ import type { OgImageOptions } from './types';
2
+ export declare function getOgImagePath(pagePath: string, _options?: Partial<OgImageOptions>): string;
3
+ export declare function isInternalRoute(path: string): boolean;
@@ -0,0 +1,11 @@
1
+ import { joinURL } from "ufo";
2
+ import { defu } from "defu";
3
+ import { useRuntimeConfig } from "#imports";
4
+ export function getOgImagePath(pagePath, _options) {
5
+ const options = defu(_options, useRuntimeConfig()["nuxt-og-image"].defaults);
6
+ return joinURL("/__og-image__/image", pagePath, `og.${options.extension}`);
7
+ }
8
+ export function isInternalRoute(path) {
9
+ const lastSegment = path.split("/").pop() || path;
10
+ return lastSegment.includes(".") || path.startsWith("/__") || path.startsWith("@");
11
+ }