nuxt-og-image 3.0.0-beta.9 → 3.0.0-rc.1

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 (143) hide show
  1. package/README.md +4 -4
  2. package/dist/client/200.html +6 -5
  3. package/dist/client/404.html +6 -5
  4. package/dist/client/_nuxt/IconCSS.bca1abaf.js +1 -0
  5. package/dist/client/_nuxt/IconCSS.f0b56d3e.css +1 -0
  6. package/dist/client/_nuxt/builds/latest.json +1 -1
  7. package/dist/client/_nuxt/builds/meta/c430c582-423d-48d2-8592-39b7bfd61658.json +1 -0
  8. package/dist/client/_nuxt/entry.a30f63d0.css +1 -0
  9. package/dist/client/_nuxt/entry.f2e056ce.js +108 -0
  10. package/dist/client/_nuxt/{error-404.18456c20.js → error-404.1b7ec865.js} +1 -1
  11. package/dist/client/_nuxt/{error-500.a3e12514.js → error-500.1f097e6f.js} +1 -1
  12. package/dist/client/_nuxt/vanilla-picker-NKbIFE8h.23409a58.js +8 -0
  13. package/dist/client/index.html +6 -5
  14. package/dist/module.d.mts +63 -48
  15. package/dist/module.d.ts +63 -48
  16. package/dist/module.json +2 -2
  17. package/dist/module.mjs +346 -166
  18. package/dist/runtime/cache.d.ts +7 -10
  19. package/dist/runtime/cache.mjs +40 -27
  20. package/dist/runtime/components/OgImage/OgImage.d.ts +5 -0
  21. package/dist/runtime/components/OgImage/{index.mjs → OgImage.mjs} +1 -1
  22. package/dist/runtime/components/OgImage/OgImageScreenshot.d.ts +5 -0
  23. package/dist/runtime/components/OgImage/{Screenshot.mjs → OgImageScreenshot.mjs} +1 -1
  24. package/dist/runtime/components/Templates/{Official → Community}/BrandedLogo.vue +3 -2
  25. package/dist/runtime/components/Templates/Community/Nuxt.vue +6 -5
  26. package/dist/runtime/components/Templates/Community/NuxtSeo.vue +137 -0
  27. package/dist/runtime/components/Templates/Community/Pergel.vue +104 -0
  28. package/dist/runtime/components/Templates/{Official → Community}/SimpleBlog.vue +7 -5
  29. package/dist/runtime/components/Templates/Community/UnJs.vue +108 -0
  30. package/dist/runtime/components/Templates/{Official → Community}/Wave.vue +3 -2
  31. package/dist/runtime/components/Templates/{Official → Community}/WithEmoji.vue +3 -2
  32. package/dist/runtime/composables/defineOgImage.d.ts +2 -23
  33. package/dist/runtime/composables/defineOgImage.mjs +33 -117
  34. package/dist/runtime/composables/defineOgImageComponent.d.ts +3 -0
  35. package/dist/runtime/composables/defineOgImageComponent.mjs +8 -0
  36. package/dist/runtime/composables/defineOgImageScreenshot.d.ts +2 -0
  37. package/dist/runtime/composables/defineOgImageScreenshot.mjs +13 -0
  38. package/dist/runtime/core/bindings/css-inline/node.d.ts +2 -5
  39. package/dist/runtime/core/bindings/css-inline/node.mjs +2 -10
  40. package/dist/runtime/core/bindings/resvg/wasm-fs.d.ts +40 -0
  41. package/dist/runtime/core/bindings/resvg/wasm-fs.mjs +6 -0
  42. package/dist/runtime/core/bindings/resvg/wasm.mjs +2 -5
  43. package/dist/runtime/core/bindings/satori/wasm-fs.mjs +13 -0
  44. package/dist/runtime/core/bindings/satori/wasm.d.ts +6 -0
  45. package/dist/runtime/core/bindings/satori/wasm.mjs +14 -0
  46. package/dist/runtime/core/cache/emojis.d.ts +1 -0
  47. package/dist/runtime/core/cache/emojis.mjs +5 -0
  48. package/dist/runtime/core/cache/fonts.d.ts +3 -0
  49. package/dist/runtime/core/cache/fonts.mjs +6 -0
  50. package/dist/runtime/core/cache/htmlPayload.d.ts +5 -0
  51. package/dist/runtime/core/cache/htmlPayload.mjs +6 -0
  52. package/dist/runtime/core/cache/prerender.d.ts +1 -5
  53. package/dist/runtime/core/cache/prerender.mjs +1 -2
  54. package/dist/runtime/core/env/assets.d.ts +0 -1
  55. package/dist/runtime/core/env/assets.mjs +0 -4
  56. package/dist/runtime/core/font/fetch.d.ts +2 -3
  57. package/dist/runtime/core/font/fetch.mjs +26 -19
  58. package/dist/runtime/core/html/applyEmojis.d.ts +3 -0
  59. package/dist/runtime/core/html/applyEmojis.mjs +37 -0
  60. package/dist/runtime/core/html/applyInlineCss.d.ts +3 -0
  61. package/dist/runtime/core/html/applyInlineCss.mjs +32 -0
  62. package/dist/runtime/core/html/devIframeTemplate.d.ts +2 -0
  63. package/dist/runtime/core/html/{fetch.mjs → devIframeTemplate.mjs} +33 -42
  64. package/dist/runtime/core/html/fetchIsland.d.ts +3 -0
  65. package/dist/runtime/core/html/fetchIsland.mjs +17 -0
  66. package/dist/runtime/core/options/fetch.d.ts +1 -1
  67. package/dist/runtime/core/options/fetch.mjs +10 -5
  68. package/dist/runtime/core/renderers/chromium/index.mjs +12 -15
  69. package/dist/runtime/core/renderers/chromium/screenshot.d.ts +2 -3
  70. package/dist/runtime/core/renderers/chromium/screenshot.mjs +20 -15
  71. package/dist/runtime/core/renderers/satori/index.d.ts +2 -3
  72. package/dist/runtime/core/renderers/satori/index.mjs +51 -25
  73. package/dist/runtime/core/renderers/satori/instances.d.ts +3 -0
  74. package/dist/runtime/core/renderers/satori/instances.mjs +15 -0
  75. package/dist/runtime/core/renderers/satori/plugins/emojis.mjs +15 -13
  76. package/dist/runtime/core/renderers/satori/plugins/imageSrc.mjs +60 -30
  77. package/dist/runtime/core/renderers/satori/plugins/unocss.d.ts +2 -0
  78. package/dist/runtime/core/renderers/satori/plugins/unocss.mjs +45 -0
  79. package/dist/runtime/core/renderers/satori/utils.d.ts +2 -3
  80. package/dist/runtime/core/renderers/satori/vnodes.d.ts +2 -3
  81. package/dist/runtime/core/renderers/satori/vnodes.mjs +16 -6
  82. package/dist/runtime/core/utils/resolveRendererContext.d.ts +2 -6
  83. package/dist/runtime/core/utils/resolveRendererContext.mjs +47 -29
  84. package/dist/runtime/core/utils/wasm.d.ts +3 -0
  85. package/dist/runtime/core/utils/wasm.mjs +16 -0
  86. package/dist/runtime/nitro/plugins/nuxt-content.mjs +7 -6
  87. package/dist/runtime/nitro/plugins/prerender.d.ts +1 -1
  88. package/dist/runtime/nitro/plugins/prerender.mjs +20 -18
  89. package/dist/runtime/nitro/utils.d.ts +2 -0
  90. package/dist/runtime/nitro/utils.mjs +17 -0
  91. package/dist/runtime/nuxt/plugins/og-image-canonical-urls.server.mjs +43 -0
  92. package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +16 -51
  93. package/dist/runtime/nuxt/utils.d.ts +3 -0
  94. package/dist/runtime/nuxt/utils.mjs +69 -0
  95. package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +2 -3
  96. package/dist/runtime/server/routes/__og-image__/debug.json.mjs +5 -7
  97. package/dist/runtime/server/routes/__og-image__/image.mjs +88 -0
  98. package/dist/runtime/types.d.ts +96 -27
  99. package/dist/runtime/utils.d.ts +4 -0
  100. package/dist/runtime/utils.mjs +11 -0
  101. package/dist/runtime/utils.pure.d.ts +6 -0
  102. package/dist/runtime/utils.pure.mjs +63 -0
  103. package/package.json +33 -38
  104. package/virtual.d.ts +49 -0
  105. package/dist/client/_nuxt/IconCSS.05a4ab6a.js +0 -1
  106. package/dist/client/_nuxt/IconCSS.8f429b14.css +0 -1
  107. package/dist/client/_nuxt/builds/meta/ce33a6eb-5cc2-4c46-8618-9befaa3f226c.json +0 -1
  108. package/dist/client/_nuxt/entry.434c2c45.css +0 -1
  109. package/dist/client/_nuxt/entry.d927023c.js +0 -137
  110. package/dist/client/grid.png +0 -0
  111. package/dist/runtime/components/OgImage/Cached.d.ts +0 -5
  112. package/dist/runtime/components/OgImage/Cached.mjs +0 -10
  113. package/dist/runtime/components/OgImage/Dynamic.d.ts +0 -8
  114. package/dist/runtime/components/OgImage/Dynamic.mjs +0 -10
  115. package/dist/runtime/components/OgImage/Screenshot.d.ts +0 -6
  116. package/dist/runtime/components/OgImage/Static.d.ts +0 -8
  117. package/dist/runtime/components/OgImage/Static.mjs +0 -10
  118. package/dist/runtime/components/OgImage/WithoutCache.d.ts +0 -5
  119. package/dist/runtime/components/OgImage/WithoutCache.mjs +0 -10
  120. package/dist/runtime/components/OgImage/index.d.ts +0 -5
  121. package/dist/runtime/components/Templates/Official/Fallback.vue +0 -147
  122. package/dist/runtime/core/bindings/css-inline/mock.d.ts +0 -5
  123. package/dist/runtime/core/bindings/css-inline/mock.mjs +0 -3
  124. package/dist/runtime/core/bindings/satori/yoga-wasm.mjs +0 -7
  125. package/dist/runtime/core/bindings/sharp/wasm.d.ts +0 -2
  126. package/dist/runtime/core/bindings/sharp/wasm.mjs +0 -2
  127. package/dist/runtime/core/font/cache.d.ts +0 -1
  128. package/dist/runtime/core/font/cache.mjs +0 -1
  129. package/dist/runtime/core/html/fetch.d.ts +0 -3
  130. package/dist/runtime/core/options/normalise.d.ts +0 -2
  131. package/dist/runtime/core/options/normalise.mjs +0 -26
  132. package/dist/runtime/core/renderers/satori/fonts.d.ts +0 -3
  133. package/dist/runtime/core/renderers/satori/fonts.mjs +0 -8
  134. package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +0 -29
  135. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  136. package/dist/runtime/server/routes/__og-image__/image-[path]-og.[extension].mjs +0 -45
  137. package/dist/runtime/utilts.d.ts +0 -2
  138. package/dist/runtime/utilts.mjs +0 -8
  139. /package/dist/runtime/core/bindings/satori/{yoga-wasm.d.ts → wasm-fs.d.ts} +0 -0
  140. /package/dist/runtime/nuxt/plugins/{nuxt-content-canonical-urls.d.ts → og-image-canonical-urls.server.d.ts} +0 -0
  141. /package/dist/runtime/{public-assets-optional/inter-font → server/assets}/inter-latin-ext-400-normal.woff +0 -0
  142. /package/dist/runtime/{public-assets-optional/inter-font → server/assets}/inter-latin-ext-700-normal.woff +0 -0
  143. /package/dist/runtime/server/routes/__og-image__/{image-[path]-og.[extension].d.ts → image.d.ts} +0 -0
@@ -1,37 +1,21 @@
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";
6
- import { useRuntimeConfig } from "#imports";
7
- export async function fetchHTML(e, options) {
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
- });
3
+ import { useOgImageRuntimeConfig } from "../../utils.mjs";
4
+ import { applyEmojis } from "./applyEmojis.mjs";
5
+ import { fetchIsland } from "./fetchIsland.mjs";
6
+ import { theme } from "#nuxt-og-image/unocss-config.mjs";
7
+ export async function devIframeTemplate(ctx) {
8
+ const { options } = ctx;
9
+ const { fonts } = useOgImageRuntimeConfig();
10
+ const island = await fetchIsland(ctx);
21
11
  const head = createHeadCore();
22
12
  head.push(island.head);
23
13
  let defaultFontFamily = "sans-serif";
24
14
  const firstFont = fonts[0];
25
15
  if (firstFont)
26
16
  defaultFontFamily = firstFont.name;
17
+ await applyEmojis(ctx, island);
27
18
  let html = island.html;
28
- try {
29
- html = twemoji.parse(html, {
30
- folder: "svg",
31
- ext: ".svg"
32
- });
33
- } catch (e2) {
34
- }
35
19
  const googleFonts = {};
36
20
  fonts.filter((font) => !font.path).forEach((font) => {
37
21
  if (!googleFonts[font.name])
@@ -46,21 +30,23 @@ export async function fetchHTML(e, options) {
46
30
  },
47
31
  {
48
32
  innerHTML: `body {
49
- transform: scale(${options.scale || 1});
33
+ transform: scale(${options.props.scale || 1});
50
34
  transform-origin: top left;
51
35
  max-height: 100vh;
52
36
  position: relative;
53
37
  width: ${options.width}px;
54
38
  height: ${options.height}px;
55
39
  overflow: hidden;
56
- background-color: ${options.mode === "dark" ? "#1b1b1b" : "#fff"};
40
+ background-color: ${options.props.colorMode === "dark" ? "#1b1b1b" : "#fff"};
41
+ }
42
+ div {
43
+ display: flex;
44
+ flex-direction: column;
45
+ }
46
+ svg[data-emoji] {
47
+ display: inline-block;
57
48
  }
58
- img.emoji {
59
- height: 1em;
60
- width: 1em;
61
- margin: 0 .05em 0 .1em;
62
- vertical-align: -0.1em;
63
- }`
49
+ `
64
50
  },
65
51
  ...fonts.filter((font) => font.path).map((font) => {
66
52
  return `
@@ -80,15 +66,20 @@ img.emoji {
80
66
  ],
81
67
  script: [
82
68
  {
83
- src: "https://cdn.tailwindcss.com"
69
+ src: "https://cdn.jsdelivr.net/npm/@unocss/runtime/preset-wind.global.js"
70
+ },
71
+ {
72
+ innerHTML: `
73
+ window.__unocss = {
74
+ theme: ${JSON.stringify(theme)},
75
+ presets: [
76
+ () => window.__unocss_runtime.presets.presetWind(),
77
+ ],
78
+ }
79
+ `
84
80
  },
85
81
  {
86
- innerHTML: `tailwind.config = {
87
- corePlugins: {
88
- preflight: false,
89
- },
90
- theme: ${JSON.stringify(satoriOptions?.tailwindConfig?.theme || {})}
91
- }`
82
+ src: "https://cdn.jsdelivr.net/npm/@unocss/runtime/core.global.js"
92
83
  }
93
84
  ],
94
85
  link: [
@@ -98,6 +89,7 @@ img.emoji {
98
89
  rel: "stylesheet"
99
90
  },
100
91
  // have to add each weight as their own stylesheet
92
+ // we should use the local font file no?
101
93
  ...Object.entries(googleFonts).map(([name, fonts2]) => {
102
94
  return {
103
95
  href: `https://fonts.googleapis.com/css2?family=${name}:wght@${fonts2.map((f) => f.weight).join(";")}&display=swap`,
@@ -108,10 +100,9 @@ img.emoji {
108
100
  });
109
101
  html = html.replaceAll(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
110
102
  const headChunk = await renderSSRHead(head);
111
- const htmlTemplate = `<!DOCTYPE html>
103
+ return `<!DOCTYPE html>
112
104
  <html ${headChunk.htmlAttrs}>
113
105
  <head>${headChunk.headTags}</head>
114
106
  <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
107
  </html>`;
116
- return htmlTemplate;
117
108
  }
@@ -0,0 +1,3 @@
1
+ import type { NuxtIslandResponse } from 'nuxt/dist/core/runtime/nitro/renderer';
2
+ import type { OgImageRenderEventContext } from '../../types';
3
+ export declare function fetchIsland({ options, e }: OgImageRenderEventContext): 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,26 +1,23 @@
1
- import { getOgImagePath } from "../../../utilts.mjs";
2
- import { prerenderChromiumContext } from "../../cache/prerender.mjs";
1
+ import { createError } from "h3";
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) {
9
- const browser = (import.meta.prerender ? prerenderChromiumContext.browser : null) || await createBrowser();
10
- if (!browser) {
6
+ supportedFormats: ["png", "jpeg", "jpg"],
7
+ async debug() {
8
+ return {};
9
+ },
10
+ async createImage(ctx) {
11
+ const browser = await createBrowser();
12
+ const screenshot = await createScreenshot(ctx, browser).catch((e) => e);
13
+ await browser.close();
14
+ if (screenshot instanceof Error) {
11
15
  return createError({
12
16
  statusCode: 400,
13
- statusMessage: "Failed to create Local Chromium Browser."
17
+ statusMessage: `Failed to create screenshot ${screenshot.message}.`
14
18
  });
15
19
  }
16
- if (import.meta.prerender)
17
- prerenderChromiumContext.browser = browser;
18
- return createScreenshot(e, browser, {
19
- ...options,
20
- path: options.component === "PageScreenshot" ? options.path : getOgImagePath(options.path, "html")
21
- }).finally(async () => {
22
- await browser.close();
23
- });
20
+ return screenshot;
24
21
  }
25
22
  };
26
23
  export default ChromiumRenderer;
@@ -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 { OgImageRenderEventContext } from '../../../types';
5
+ export declare function createScreenshot({ basePath, e, options, extension }: OgImageRenderEventContext, browser: Browser): Promise<Buffer>;
@@ -1,21 +1,23 @@
1
- import { withQuery } from "ufo";
1
+ import { joinURL, withQuery } from "ufo";
2
+ import { useOgImageRuntimeConfig } from "../../../utils.mjs";
2
3
  import { useNitroOrigin } from "#imports";
3
- export async function createScreenshot(e, browser, options) {
4
+ export async function createScreenshot({ basePath, e, options, extension }, browser) {
5
+ const { colorPreference } = useOgImageRuntimeConfig();
6
+ const path = options.component === "PageScreenshot" ? basePath : joinURL("/__og-image__/image", basePath, `og.html`);
4
7
  const page = await browser.newPage({
5
- colorScheme: options.colorScheme,
8
+ colorScheme: colorPreference || "no-preference",
6
9
  baseURL: useNitroOrigin(e)
7
10
  });
8
- if (import.meta.prerender && !options.html) {
9
- options.html = await e.$fetch(options.path);
10
- }
11
11
  try {
12
+ if (import.meta.prerender && !options.html) {
13
+ options.html = await e.$fetch(path).catch(() => void 0);
14
+ }
12
15
  await page.setViewportSize({
13
16
  width: options.width || 1200,
14
17
  height: options.height || 630
15
18
  });
16
- const isHtml = options.html || options.path?.startsWith("html:");
17
- if (isHtml) {
18
- const html = options.html || options.path?.substring(5);
19
+ if (options.html) {
20
+ const html = options.html;
19
21
  await page.evaluate((html2) => {
20
22
  document.open("text/html");
21
23
  document.write(html2);
@@ -23,7 +25,7 @@ export async function createScreenshot(e, browser, options) {
23
25
  }, html);
24
26
  await page.waitForLoadState("networkidle");
25
27
  } else {
26
- await page.goto(withQuery(options.path, options), {
28
+ await page.goto(withQuery(path, options.props), {
27
29
  timeout: 1e4,
28
30
  waitUntil: "networkidle"
29
31
  });
@@ -31,16 +33,19 @@ export async function createScreenshot(e, browser, options) {
31
33
  const screenshotOptions = {
32
34
  timeout: 1e4,
33
35
  animations: "disabled",
34
- type: options.extension
36
+ type: extension === "png" ? "png" : "jpeg"
35
37
  };
36
- if (options.mask) {
38
+ const _options = options.screenshot || {};
39
+ if (_options.delay)
40
+ await page.waitForTimeout(_options.delay);
41
+ if (_options.mask) {
37
42
  await page.evaluate((mask) => {
38
43
  for (const el of document.querySelectorAll(mask))
39
44
  el.style.display = "none";
40
- }, options.mask);
45
+ }, _options.mask);
41
46
  }
42
- if (options.selector)
43
- return await page.locator(options.selector).screenshot(screenshotOptions);
47
+ if (_options.selector)
48
+ return await page.locator(_options.selector).screenshot(screenshotOptions);
44
49
  return await page.screenshot(screenshotOptions);
45
50
  } finally {
46
51
  await page.close();
@@ -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 { OgImageRenderEventContext, Renderer } from '../../../types';
2
+ export declare function createSvg(event: OgImageRenderEventContext): Promise<string>;
4
3
  declare const SatoriRenderer: Renderer;
5
4
  export default SatoriRenderer;
@@ -1,53 +1,79 @@
1
1
  import { defu } from "defu";
2
+ import { normaliseFontInput, useOgImageRuntimeConfig } from "../../../utils.mjs";
3
+ import { loadFont } from "../../font/fetch.mjs";
4
+ import { fontCache, fontPromises } from "../../cache/fonts.mjs";
2
5
  import { createVNodes } from "./vnodes.mjs";
3
- import { loadFonts, satoriFonts } from "./fonts.mjs";
4
6
  import { useResvg, useSatori, useSharp } from "./instances.mjs";
5
- import { useRuntimeConfig } from "#imports";
6
- export async function createSvg(e, options) {
7
- const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
8
- const vnodes = await createVNodes(e, options);
9
- if (!satoriFonts.length)
10
- satoriFonts.push(...await loadFonts(e, fonts));
7
+ import { theme } from "#nuxt-og-image/unocss-config.mjs";
8
+ export async function createSvg(event) {
9
+ const { options } = event;
10
+ const { fonts, satoriOptions } = useOgImageRuntimeConfig();
11
+ const vnodes = await createVNodes(event);
12
+ await event._nitro.hooks.callHook("nuxt-og-image:satori:vnodes", vnodes, event);
13
+ const normalisedFonts = normaliseFontInput([...fonts, ...event.options.fonts || []]);
14
+ const localFontPromises = [];
15
+ const preloadedFonts = [];
16
+ for (const font of normalisedFonts) {
17
+ if (await fontCache.hasItem(font.cacheKey)) {
18
+ font.data = await fontCache.getItemRaw(font.cacheKey);
19
+ preloadedFonts.push(font);
20
+ } else {
21
+ if (!fontPromises[font.cacheKey]) {
22
+ fontPromises[font.cacheKey] = loadFont(event, font).then(async (_font) => {
23
+ if (_font?.data)
24
+ await fontCache.setItemRaw(_font.cacheKey, _font.data);
25
+ return _font;
26
+ });
27
+ }
28
+ localFontPromises.push(fontPromises[font.cacheKey]);
29
+ }
30
+ }
31
+ const awaitedFonts = await Promise.all(localFontPromises);
11
32
  const satori = await useSatori();
12
33
  return satori(vnodes, defu(options.satori, satoriOptions, {
13
- fonts: satoriFonts,
34
+ fonts: [...preloadedFonts, ...awaitedFonts].map((_f) => {
35
+ return { ..._f, weight: Number(_f.weight) };
36
+ }),
37
+ tailwindConfig: { theme },
14
38
  embedFont: true,
15
39
  width: options.width,
16
40
  height: options.height
17
41
  }));
18
42
  }
19
- async function createPng(e, options) {
20
- const { resvgOptions } = useRuntimeConfig()["nuxt-og-image"];
21
- const svg = await createSvg(e, options);
43
+ async function createPng(event) {
44
+ const { resvgOptions } = useOgImageRuntimeConfig();
45
+ const svg = await createSvg(event);
22
46
  const Resvg = await useResvg();
23
47
  const resvg = new Resvg(svg, defu(
24
- options.resvg,
48
+ event.options.resvg,
25
49
  resvgOptions
26
50
  ));
27
51
  const pngData = resvg.render();
28
52
  return pngData.asPng();
29
53
  }
30
- async function createJpeg(e, options) {
31
- const { sharpOptions } = useRuntimeConfig()["nuxt-og-image"];
32
- const png = await createPng(e, options);
54
+ async function createJpeg(event) {
55
+ const { sharpOptions } = useOgImageRuntimeConfig();
56
+ const png = await createPng(event);
33
57
  const sharp = await useSharp();
34
- return sharp(png, defu(options.sharp, sharpOptions)).jpeg(defu(options.sharp, sharpOptions)).toBuffer();
58
+ return sharp(png, defu(event.options.sharp, sharpOptions)).jpeg().toBuffer();
35
59
  }
36
60
  const SatoriRenderer = {
37
61
  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);
62
+ supportedFormats: ["png", "jpeg", "jpg", "json"],
63
+ async createImage(e) {
64
+ switch (e.extension) {
45
65
  case "png":
46
- return createPng(e, options);
66
+ return createPng(e);
47
67
  case "jpeg":
48
68
  case "jpg":
49
- return createJpeg(e, options);
69
+ return createJpeg(e);
50
70
  }
71
+ },
72
+ async debug(e) {
73
+ return {
74
+ vnodes: await createVNodes(e),
75
+ svg: await createSvg(e)
76
+ };
51
77
  }
52
78
  };
53
79
  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
  ]);
@@ -3,43 +3,73 @@ import { withBase } from "ufo";
3
3
  import sizeOf from "image-size";
4
4
  import { defineSatoriTransformer } from "../utils.mjs";
5
5
  import { toBase64Image } from "../../../env/assets.mjs";
6
+ import { useStorage } from "#internal/nitro";
6
7
  import { useNitroOrigin } from "#imports";
7
- export default defineSatoriTransformer({
8
- filter: (node) => node.type === "img",
9
- transform: async (node, e) => {
10
- const src = node.props?.src;
11
- if (src) {
12
- let updated = false;
13
- let dimensions;
14
- if (!updated) {
8
+ export default defineSatoriTransformer([
9
+ // fix <img src="">
10
+ {
11
+ filter: (node) => node.type === "img",
12
+ transform: async (node, { e }) => {
13
+ const src = node.props?.src;
14
+ const isRelative = src?.startsWith("/");
15
+ if (src) {
16
+ let updated = false;
17
+ let dimensions;
18
+ let imageBuffer;
15
19
  let valid = true;
16
- const response = await e.$fetch(src, {
17
- baseURL: useNitroOrigin(e),
18
- responseType: "arrayBuffer"
19
- }).catch(() => {
20
- valid = false;
21
- });
20
+ if (import.meta.prerender || import.meta.dev) {
21
+ const key = `root:public${src.replace("./", ":").replace("/", ":")}`;
22
+ if (await useStorage().hasItem(key)) {
23
+ imageBuffer = await useStorage().getItemRaw(key);
24
+ updated = !!imageBuffer;
25
+ }
26
+ }
27
+ if (!import.meta.prerender && !updated) {
28
+ imageBuffer = await e.$fetch(src, {
29
+ baseURL: useNitroOrigin(e),
30
+ responseType: "arrayBuffer"
31
+ }).catch(() => {
32
+ valid = false;
33
+ });
34
+ valid = !!imageBuffer;
35
+ }
22
36
  if (valid) {
23
- node.props.src = toBase64Image(src, response);
24
- const imageSize = sizeOf(Buffer.from(response));
25
- dimensions = { width: imageSize.width, height: imageSize.height };
37
+ node.props.src = toBase64Image(src, imageBuffer);
38
+ try {
39
+ const imageSize = sizeOf(Buffer.from(imageBuffer));
40
+ dimensions = { width: imageSize.width, height: imageSize.height };
41
+ } catch (e2) {
42
+ }
26
43
  updated = true;
27
44
  }
28
- }
29
- if (dimensions?.width && dimensions?.height) {
30
- const naturalAspectRatio = dimensions.width / dimensions.height;
31
- if (node.props.width && !node.props.height) {
32
- node.props.height = Math.round(node.props.width / naturalAspectRatio);
33
- } else if (node.props.height && !node.props.width) {
34
- node.props.width = Math.round(node.props.height * naturalAspectRatio);
35
- } else if (!node.props.width && !node.props.height) {
36
- node.props.width = dimensions.width;
37
- node.props.height = dimensions.height;
45
+ if (dimensions?.width && dimensions?.height) {
46
+ const naturalAspectRatio = dimensions.width / dimensions.height;
47
+ if (node.props.width && !node.props.height) {
48
+ node.props.height = Math.round(node.props.width / naturalAspectRatio);
49
+ } else if (node.props.height && !node.props.width) {
50
+ node.props.width = Math.round(node.props.height * naturalAspectRatio);
51
+ } else if (!node.props.width && !node.props.height) {
52
+ node.props.width = dimensions.width;
53
+ node.props.height = dimensions.height;
54
+ }
55
+ }
56
+ if (!updated && isRelative) {
57
+ node.props.src = `${withBase(src, `${useNitroOrigin(e)}`)}?${Date.now()}`;
38
58
  }
39
59
  }
40
- if (!updated) {
41
- node.props.src = `${withBase(src, `${useNitroOrigin(e)}`)}?${Date.now()}`;
60
+ }
61
+ },
62
+ // fix style="background-image: url('')"
63
+ {
64
+ filter: (node) => node.props?.style?.backgroundImage?.includes("url("),
65
+ transform: async (node, { e }) => {
66
+ const backgroundImage = node.props.style.backgroundImage;
67
+ if (backgroundImage) {
68
+ const src = backgroundImage.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
69
+ const isRelative = src?.startsWith("/");
70
+ if (isRelative)
71
+ node.props.style.backgroundImage = `url(${withBase(src, `${useNitroOrigin(e)}`)}?${Date.now()})`;
42
72
  }
43
73
  }
44
74
  }
45
- });
75
+ ]);
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../../../types").SatoriTransformer | import("../../../../types").SatoriTransformer[];
2
+ export default _default;
@@ -0,0 +1,45 @@
1
+ import { createGenerator } from "@unocss/core";
2
+ import presetWind from "@unocss/preset-wind";
3
+ import { defineSatoriTransformer } from "../utils.mjs";
4
+ import { theme } from "#nuxt-og-image/unocss-config.mjs";
5
+ const uno = createGenerator({ theme }, {
6
+ presets: [
7
+ presetWind()
8
+ ]
9
+ });
10
+ export default defineSatoriTransformer({
11
+ filter: (node) => !!node.props?.class,
12
+ transform: async (node) => {
13
+ const classes = node.props.class || "";
14
+ const styles = node.props.style || {};
15
+ const replacedClasses = /* @__PURE__ */ new Set();
16
+ for (const token of classes.split(" ").filter((c) => c.trim())) {
17
+ const parsedToken = await uno.parseToken(token);
18
+ if (parsedToken) {
19
+ const inlineStyles = parsedToken[0][2].split(";").filter((s) => !!s?.trim());
20
+ const vars = {};
21
+ inlineStyles.filter((s) => s.startsWith("--")).forEach((s) => {
22
+ const [key, value] = s.split(":");
23
+ vars[key] = value;
24
+ });
25
+ inlineStyles.filter((s) => !s.startsWith("--")).forEach((s) => {
26
+ const [key, value] = s.split(":");
27
+ const camelCasedKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
28
+ if (!styles[camelCasedKey])
29
+ styles[camelCasedKey] = value.replace(/var\((.*?)\)/g, (_, k) => vars[k.trim()]);
30
+ if (styles[camelCasedKey] && styles[camelCasedKey].includes("/")) {
31
+ const [rgb, opacity] = styles[camelCasedKey].split("/");
32
+ if (opacity.trim() === "1)")
33
+ styles[camelCasedKey] = rgb.replace(/(\d+) (\d+) (\d+).*/, (_, r, g, b) => `${r}, ${g}, ${b})`);
34
+ else
35
+ styles[camelCasedKey] = `${rgb.replace("rgb", "rgba").replaceAll(" ", ", ")}${opacity.trim()}`;
36
+ }
37
+ });
38
+ replacedClasses.add(token);
39
+ }
40
+ }
41
+ node.props.class = classes.split(" ").filter((c) => !replacedClasses.has(c)).join(" ");
42
+ node.props.tw = classes.split(" ").filter((c) => !replacedClasses.has(c)).join(" ");
43
+ node.props.style = styles;
44
+ }
45
+ });