nuxt-og-image 2.0.0-beta.5 → 2.0.0-beta.51

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 (95) hide show
  1. package/README.md +265 -53
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.73da91e8.js +1 -0
  5. package/dist/client/_nuxt/IconCSS.8bbd2aa2.css +1 -0
  6. package/dist/client/_nuxt/ImageLoader.1dc2da65.js +1 -0
  7. package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
  8. package/dist/client/_nuxt/entry.9627f7dd.js +5 -0
  9. package/dist/client/_nuxt/entry.f4586a2b.css +1 -0
  10. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.e8b1a738.js} +1 -1
  11. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.34be4bd8.js} +1 -1
  12. package/dist/client/_nuxt/error-component.4733114a.js +3 -0
  13. package/dist/client/_nuxt/index.35b0e13d.js +1 -0
  14. package/dist/client/_nuxt/index.403133d8.css +1 -0
  15. package/dist/client/_nuxt/{options.56a3e5f9.js → options.bd244bf2.js} +1 -1
  16. package/dist/client/_nuxt/{png.37f3e77b.js → png.765461fb.js} +1 -1
  17. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.474ce6f4.js} +1 -1
  18. package/dist/client/_nuxt/{svg.186c6bd1.js → svg.1511e50d.js} +1 -1
  19. package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.4f695971.js} +1 -1
  20. package/dist/client/index.html +2 -2
  21. package/dist/client/options/index.html +2 -2
  22. package/dist/client/png/index.html +2 -2
  23. package/dist/client/svg/index.html +2 -2
  24. package/dist/client/vnodes/index.html +2 -2
  25. package/dist/module.d.ts +22 -7
  26. package/dist/module.json +1 -1
  27. package/dist/module.mjs +312 -114
  28. package/dist/runtime/browserUtil.d.ts +1 -0
  29. package/dist/runtime/browserUtil.mjs +10 -5
  30. package/dist/runtime/components/OgImageBasic.island.vue +5 -0
  31. package/dist/runtime/components/OgImageDynamic.d.ts +1 -1
  32. package/dist/runtime/components/OgImageScreenshot.d.ts +1 -1
  33. package/dist/runtime/components/OgImageStatic.d.ts +1 -1
  34. package/dist/runtime/composables/defineOgImage.mjs +15 -12
  35. package/dist/runtime/nitro/middleware/og.png.mjs +51 -7
  36. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  37. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  38. package/dist/runtime/nitro/plugins/prerender.mjs +26 -0
  39. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  40. package/dist/runtime/nitro/providers/browser/lambda.mjs +3 -3
  41. package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
  42. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  43. package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
  44. package/dist/runtime/nitro/providers/png/resvg-node.d.ts +5 -0
  45. package/dist/runtime/nitro/providers/png/resvg-node.mjs +6 -0
  46. package/dist/runtime/nitro/providers/png/resvg-wasm.d.ts +4 -0
  47. package/dist/runtime/nitro/providers/png/resvg-wasm.mjs +11 -0
  48. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  49. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  50. package/dist/runtime/nitro/renderers/browser.d.ts +2 -2
  51. package/dist/runtime/nitro/renderers/browser.mjs +14 -7
  52. package/dist/runtime/nitro/renderers/satori/index.d.ts +2 -2
  53. package/dist/runtime/nitro/renderers/satori/index.mjs +15 -16
  54. package/dist/runtime/nitro/renderers/satori/plugins/emojis.d.ts +1 -1
  55. package/dist/runtime/nitro/renderers/satori/plugins/emojis.mjs +24 -9
  56. package/dist/runtime/nitro/renderers/satori/plugins/encoding.d.ts +1 -1
  57. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
  58. package/dist/runtime/nitro/renderers/satori/plugins/flex.d.ts +1 -1
  59. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.d.ts +1 -1
  60. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +25 -3
  61. package/dist/runtime/nitro/renderers/satori/plugins/twClasses.d.ts +1 -1
  62. package/dist/runtime/nitro/renderers/satori/utils.d.ts +4 -4
  63. package/dist/runtime/nitro/renderers/satori/utils.mjs +26 -13
  64. package/dist/runtime/nitro/routes/debug.d.ts +4 -0
  65. package/dist/runtime/nitro/routes/debug.mjs +9 -0
  66. package/dist/runtime/nitro/routes/html.mjs +100 -25
  67. package/dist/runtime/nitro/routes/options.mjs +20 -22
  68. package/dist/runtime/nitro/routes/svg.mjs +3 -1
  69. package/dist/runtime/nitro/routes/vnode.mjs +3 -1
  70. package/dist/runtime/nitro/util-hostname.d.ts +2 -0
  71. package/dist/runtime/nitro/util-hostname.mjs +20 -0
  72. package/dist/runtime/nitro/utils-pure.d.ts +3 -2
  73. package/dist/runtime/nitro/utils-pure.mjs +9 -8
  74. package/dist/runtime/nitro/utils.d.ts +6 -8
  75. package/dist/runtime/nitro/utils.mjs +50 -47
  76. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  77. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  78. package/dist/types.d.ts +6 -0
  79. package/package.json +37 -24
  80. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  81. package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
  82. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  83. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
  84. package/dist/client/_nuxt/error-component.cf7543e5.js +0 -3
  85. package/dist/client/_nuxt/index.3f356409.js +0 -1
  86. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
  87. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  88. /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
  89. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  90. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  91. /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
  92. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  93. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  94. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  95. /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
package/dist/module.mjs CHANGED
@@ -1,29 +1,33 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
2
+ import { defineNuxtModule, useLogger, createResolver, addTemplate, addServerHandler, addImports, addComponent, addServerPlugin } from '@nuxt/kit';
3
3
  import { execa } from 'execa';
4
4
  import chalk from 'chalk';
5
5
  import defu from 'defu';
6
6
  import { toRouteMatcher, createRouter } from 'radix3';
7
- import { joinURL } from 'ufo';
8
- import { resolve, relative } from 'pathe';
7
+ import { withBase, joinURL } from 'ufo';
8
+ import { resolve, relative, dirname } from 'pathe';
9
9
  import { tinyws } from 'tinyws';
10
10
  import sirv from 'sirv';
11
11
  import { pathExists, copy, mkdirp } from 'fs-extra';
12
- import { provider } from 'std-env';
12
+ import { globby } from 'globby';
13
13
  import playwrightCore from 'playwright-core';
14
14
  import { existsSync } from 'node:fs';
15
15
  import { createBirpcGroup } from 'birpc';
16
16
  import { stringify, parse } from 'flatted';
17
+ import { addDependency } from 'nypm';
18
+ import { provider } from 'std-env';
17
19
 
18
20
  async function createBrowser() {
19
- try {
20
- const { Launcher } = await import(String("chrome-launcher"));
21
- const chromePath = Launcher.getFirstInstallation();
22
- return await playwrightCore.chromium.launch({
23
- headless: true,
24
- executablePath: chromePath
25
- });
26
- } catch (e) {
21
+ if (process.dev || process.env.prerender) {
22
+ try {
23
+ const { Launcher } = await import(String("chrome-launcher"));
24
+ const chromePath = Launcher.getFirstInstallation();
25
+ return await playwrightCore.chromium.launch({
26
+ headless: true,
27
+ executablePath: chromePath
28
+ });
29
+ } catch (e) {
30
+ }
27
31
  }
28
32
  try {
29
33
  return await playwrightCore.chromium.launch({
@@ -54,9 +58,9 @@ async function screenshot(browser, options) {
54
58
  width: options.width || 1200,
55
59
  height: options.height || 630
56
60
  });
57
- const isHtml = options.path.startsWith("html:") || options.html;
61
+ const isHtml = options.html || options.path?.startsWith("html:");
58
62
  if (isHtml) {
59
- const html = options.html || options.path.substring(5);
63
+ const html = options.html || options.path?.substring(5);
60
64
  await page.evaluate((html2) => {
61
65
  document.open("text/html");
62
66
  document.write(html2);
@@ -65,10 +69,13 @@ async function screenshot(browser, options) {
65
69
  await page.waitForLoadState("networkidle");
66
70
  } else {
67
71
  await page.goto(`${options.host}${options.path}`, {
68
- timeout: 1e4,
72
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500,
69
73
  waitUntil: "networkidle"
70
74
  });
71
75
  }
76
+ const screenshotOptions = {
77
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500
78
+ };
72
79
  if (options.delay)
73
80
  await page.waitForTimeout(options.delay);
74
81
  if (options.mask) {
@@ -78,8 +85,10 @@ async function screenshot(browser, options) {
78
85
  }, options.mask);
79
86
  }
80
87
  if (options.selector)
81
- return await page.locator(options.selector).screenshot();
82
- return await page.screenshot();
88
+ return await page.locator(options.selector).screenshot(screenshotOptions);
89
+ const screenshot2 = await page.screenshot(screenshotOptions);
90
+ await page.close();
91
+ return screenshot2;
83
92
  }
84
93
 
85
94
  function setupPlaygroundRPC(nuxt, config) {
@@ -113,7 +122,7 @@ function setupPlaygroundRPC(nuxt, config) {
113
122
  const birpc = createBirpcGroup(serverFunctions, []);
114
123
  nuxt.hook("builder:watch", (e, path) => {
115
124
  if (e === "change")
116
- birpc.boardcast.refresh.asEvent(path);
125
+ birpc.broadcast.refresh.asEvent(path);
117
126
  });
118
127
  const middleware = async (req, res) => {
119
128
  if (req.ws) {
@@ -164,11 +173,15 @@ function getBodyJson(req) {
164
173
  });
165
174
  }
166
175
 
167
- function decodeHtmlEntities(obj) {
176
+ function decodeHtml(html) {
177
+ return html.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&cent;/g, "\xA2").replace(/&pound;/g, "\xA3").replace(/&yen;/g, "\xA5").replace(/&euro;/g, "\u20AC").replace(/&copy;/g, "\xA9").replace(/&reg;/g, "\xAE").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/").replace(/&#([0-9]+);/g, (full, int) => {
178
+ return String.fromCharCode(Number.parseInt(int));
179
+ });
180
+ }
181
+ function decodeObjectHtmlEntities(obj) {
168
182
  Object.entries(obj).forEach(([key, value]) => {
169
- if (typeof value === "string") {
170
- obj[key] = value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
171
- }
183
+ if (typeof value === "string")
184
+ obj[key] = decodeHtml(value);
172
185
  });
173
186
  return obj;
174
187
  }
@@ -192,22 +205,120 @@ function extractOgImageOptions(html) {
192
205
  else
193
206
  options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
194
207
  }
195
- return decodeHtmlEntities(options);
208
+ return decodeObjectHtmlEntities(options);
196
209
  }
197
210
  return false;
198
211
  }
199
- function stripOgImageOptions(html) {
200
- return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
212
+
213
+ const SVG2PNGWasmPlaceholder = '"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"';
214
+ const YogaWasmPlaceholder = '"/* NUXT_OG_IMAGE_YOGA_WASM */"';
215
+ const ReSVGWasmPlaceholder = '"/* NUXT_OG_IMAGE_RESVG_WASM */"';
216
+ const Wasms = [
217
+ {
218
+ placeholder: SVG2PNGWasmPlaceholder,
219
+ path: "svg2png/svg2png.wasm",
220
+ file: "svg2png.wasm"
221
+ },
222
+ {
223
+ placeholder: ReSVGWasmPlaceholder,
224
+ path: "resvg/resvg.wasm",
225
+ file: "resvg.wasm"
226
+ },
227
+ {
228
+ placeholder: YogaWasmPlaceholder,
229
+ path: "yoga/yoga.wasm",
230
+ file: "yoga.wasm"
231
+ }
232
+ ];
233
+ const DefaultRuntimeCompatibility = {
234
+ // node-server runtime
235
+ browser: "playwright",
236
+ satori: "default",
237
+ wasm: "fetch",
238
+ png: "resvg-node"
239
+ };
240
+ const RuntimeCompatibility = {
241
+ "nitro-dev": {
242
+ wasm: "fetch",
243
+ browser: "universal"
244
+ },
245
+ "stackblitz": {
246
+ browser: false,
247
+ satori: "yoga-wasm",
248
+ wasm: "inline",
249
+ png: "resvg-wasm"
250
+ },
251
+ "netlify": {
252
+ browser: "lambda",
253
+ wasm: "inline"
254
+ },
255
+ "netlify-edge": {
256
+ wasm: "inline",
257
+ png: "resvg-wasm"
258
+ },
259
+ "vercel": {
260
+ // exceeds 50mb limit
261
+ browser: false
262
+ },
263
+ "vercel-edge": {
264
+ browser: false,
265
+ wasm: "import",
266
+ wasmImportQuery: "?module",
267
+ png: "resvg-wasm"
268
+ },
269
+ "cloudflare-pages": {
270
+ browser: false,
271
+ wasm: "import",
272
+ png: "resvg-wasm"
273
+ },
274
+ "cloudflare": {
275
+ browser: false,
276
+ wasm: "import"
277
+ }
278
+ };
279
+
280
+ const autodetectableProviders = {
281
+ azure_static: "azure",
282
+ cloudflare_pages: "cloudflare-pages",
283
+ netlify: "netlify",
284
+ stormkit: "stormkit",
285
+ vercel: "vercel",
286
+ cleavr: "cleavr"
287
+ };
288
+ const autodetectableStaticProviders = {
289
+ netlify: "netlify-static",
290
+ vercel: "vercel-static"
291
+ };
292
+ function detectTarget(options = {}) {
293
+ return options?.static ? autodetectableStaticProviders[provider] : autodetectableProviders[provider];
294
+ }
295
+ function getNitroPreset(nuxt) {
296
+ return process.env.NITRO_PRESET || nuxt.options.nitro.preset || detectTarget() || "node-server";
297
+ }
298
+ function getNitroProviderCompatibility(nuxt) {
299
+ if (provider === "stackblitz")
300
+ return defu(RuntimeCompatibility.stackblitz, DefaultRuntimeCompatibility);
301
+ if (nuxt.options.dev || nuxt.options._prepare || nuxt.options._generate) {
302
+ return defu({
303
+ wasm: "fetch",
304
+ browser: "universal"
305
+ }, DefaultRuntimeCompatibility);
306
+ }
307
+ const target = getNitroPreset(nuxt);
308
+ const compatibility = RuntimeCompatibility[target];
309
+ if (compatibility === false)
310
+ return false;
311
+ return defu(compatibility || {}, DefaultRuntimeCompatibility);
312
+ }
313
+ function ensureDependencies(nuxt, dep) {
314
+ return Promise.all(dep.map((d) => {
315
+ return addDependency(d, { cwd: nuxt.options.rootDir });
316
+ }));
201
317
  }
202
318
 
203
319
  const PATH = "/__nuxt_og_image__";
204
320
  const PATH_ENTRY = `${PATH}/entry`;
205
321
  const PATH_PLAYGROUND = `${PATH}/client`;
206
- const edgeProvidersSupported = [
207
- "cloudflare",
208
- "vercel-edge",
209
- "netlify-edge"
210
- ];
211
322
  const module = defineNuxtModule({
212
323
  meta: {
213
324
  name: "nuxt-og-image",
@@ -220,26 +331,54 @@ const module = defineNuxtModule({
220
331
  defaults(nuxt) {
221
332
  const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
222
333
  return {
223
- // when we run `nuxi generate` we need to force prerendering
224
- forcePrerender: !nuxt.options.dev && nuxt.options._generate,
225
334
  siteUrl,
226
335
  defaults: {
227
336
  component: "OgImageBasic",
228
337
  width: 1200,
229
- height: 630
338
+ height: 630,
339
+ cacheTtl: 24 * 60 * 60 * 1e3
340
+ // default is to cache the image for 24 hours
230
341
  },
231
- satoriProvider: true,
232
- browserProvider: true,
342
+ runtimeSatori: true,
343
+ runtimeBrowser: nuxt.options.dev,
233
344
  fonts: [],
345
+ runtimeCacheStorage: false,
234
346
  satoriOptions: {},
235
- experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
236
- experimentalRuntimeBrowser: false,
237
- playground: process.env.NODE_ENV === "development" || nuxt.options.dev
347
+ playground: process.env.NODE_ENV === "development" || nuxt.options.dev,
348
+ debug: false
238
349
  };
239
350
  },
240
351
  async setup(config, nuxt) {
352
+ const logger = useLogger("nuxt-og-image");
353
+ logger.level = config.debug ? 4 : 3;
241
354
  const { resolve } = createResolver(import.meta.url);
355
+ logger.debug("Using Nitro preset", getNitroPreset(nuxt));
356
+ const nitroCompatibility = getNitroProviderCompatibility(nuxt);
357
+ logger.debug("Nitro compatibility", nitroCompatibility);
358
+ const nitroTarget = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
359
+ if (!nitroCompatibility) {
360
+ logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\`. Please make an issue. `);
361
+ return;
362
+ }
363
+ if (!nitroCompatibility.browser && config.runtimeBrowser) {
364
+ config.runtimeBrowser = false;
365
+ logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\` with the runtime browser. Set runtimeBrowser: false to stop seeing this.`);
366
+ }
367
+ if (config.runtimeBrowser && nitroCompatibility.browser === "lambda") {
368
+ logger.info(`\`nuxt-og-image\` is deploying to nitro target \`${nitroTarget}\` that installs extra dependencies.`);
369
+ await ensureDependencies(nuxt, ["puppeteer-core@14.1.1", "@sparticuz/chrome-aws-lambda@14.1.1"]);
370
+ }
242
371
  config.siteUrl = config.siteUrl || config.host;
372
+ if (!nuxt.options.dev && nuxt.options._generate && !config.siteUrl)
373
+ logger.warn("Missing `ogImage.siteUrl` and site is being prerendered. This will result in broken og images.");
374
+ nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
375
+ if (nuxt.options._generate) {
376
+ nuxt.options.nitro.storage["og-image"] = {
377
+ driver: "memory"
378
+ };
379
+ } else if (config.runtimeCacheStorage && !nuxt.options.dev && typeof config.runtimeCacheStorage === "object") {
380
+ nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
381
+ }
243
382
  if (!config.fonts.length)
244
383
  config.fonts = ["Inter:400", "Inter:700"];
245
384
  const distResolve = (p) => {
@@ -271,12 +410,14 @@ export {}
271
410
  lazy: true,
272
411
  handler: resolve("./runtime/nitro/middleware/og.png")
273
412
  });
274
- ["html", "options", "svg", "vnode", "font"].forEach((type) => {
275
- addServerHandler({
276
- lazy: true,
277
- route: `/api/og-image-${type}`,
278
- handler: resolve(`./runtime/nitro/routes/${type}`)
279
- });
413
+ ["html", "options", "svg", "vnode", "font", "debug"].forEach((type) => {
414
+ if (type !== "debug" || config.debug) {
415
+ addServerHandler({
416
+ lazy: true,
417
+ route: `/api/og-image-${type}`,
418
+ handler: resolve(`./runtime/nitro/routes/${type}`)
419
+ });
420
+ }
280
421
  });
281
422
  nuxt.hook("devtools:customTabs", (iframeTabs) => {
282
423
  iframeTabs.push({
@@ -325,92 +466,150 @@ export {}
325
466
  });
326
467
  const runtimeDir = resolve("./runtime");
327
468
  nuxt.options.build.transpile.push(runtimeDir);
328
- const moduleAssetDir = resolve("./runtime/public-assets");
329
- const assetDirs = [
330
- resolve(nuxt.options.rootDir, nuxt.options.dir.public),
331
- moduleAssetDir
469
+ addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
470
+ const customAssetDirs = [
471
+ // allows us to show custom error images
472
+ resolve("./runtime/public-assets")
332
473
  ];
474
+ if (config.runtimeSatori) {
475
+ if (config.fonts.includes("Inter:400"))
476
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/inter-font"));
477
+ if (nitroCompatibility.png === "resvg-wasm" && nitroCompatibility.wasm === "fetch")
478
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/resvg"));
479
+ else if (nitroCompatibility.png === "svg2png" && nitroCompatibility.wasm === "fetch")
480
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/svg2png"));
481
+ if (nitroCompatibility.satori === "yoga-wasm")
482
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/yoga"));
483
+ }
333
484
  nuxt.hooks.hook("modules:done", async () => {
334
485
  nuxt.hooks.callHook("og-image:config", config);
335
- nuxt.options.runtimeConfig["nuxt-og-image"] = { ...config, assetDirs };
486
+ nuxt.options.runtimeConfig["nuxt-og-image"] = {
487
+ ...config,
488
+ // avoid adding credentials
489
+ runtimeCacheStorage: Boolean(config.runtimeCacheStorage),
490
+ // convert the fonts to uniform type to fix ts issue
491
+ fonts: config.fonts.map((f) => {
492
+ if (typeof f === "string") {
493
+ const [name, weight] = f.split(":");
494
+ return {
495
+ name,
496
+ weight
497
+ };
498
+ }
499
+ return f;
500
+ }),
501
+ assetDirs: [
502
+ resolve(nuxt.options.srcDir, nuxt.options.dir.public),
503
+ ...customAssetDirs,
504
+ // always add runtime dirs for prerendering to work, these are just used as scan roots
505
+ resolve("./runtime/public-assets-optional/inter-font"),
506
+ resolve("./runtime/public-assets-optional/resvg"),
507
+ resolve("./runtime/public-assets-optional/yoga"),
508
+ resolve("./runtime/public-assets-optional/svg2png")
509
+ ]
510
+ };
336
511
  });
337
- const useSatoriWasm = provider === "stackblitz";
338
512
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
339
513
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
340
514
  inline: [runtimeDir]
341
515
  });
342
- if (config.experimentalRuntimeBrowser) {
516
+ if (config.runtimeBrowser) {
343
517
  nitroConfig.alias = nitroConfig.alias || {};
344
518
  nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
345
519
  nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
346
520
  nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
347
521
  }
348
522
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
349
- nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
523
+ customAssetDirs.forEach((dir) => {
524
+ nitroConfig.publicAssets.push({ dir, maxAge: 31536e3 });
525
+ });
350
526
  const providerPath = `${runtimeDir}/nitro/providers`;
351
- if (config.browserProvider) {
352
- nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || config.experimentalRuntimeBrowser ? `
353
- import node from '${providerPath}/browser/node'
354
-
527
+ if (config.runtimeBrowser) {
528
+ nitroConfig.virtual["#nuxt-og-image/browser"] = `
529
+ let browser
355
530
  export default async function() {
356
- return node
357
- }
358
- ` : `export default async function() {
359
- return () => {}
531
+ browser = browser || await import('${providerPath}/browser/${nitroCompatibility.browser}').then((m) => m.default || m)
532
+ return browser
360
533
  }
361
534
  `;
362
535
  }
363
- if (config.satoriProvider) {
364
- nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${useSatoriWasm ? "webworker" : "node"}'
365
- export default async function() {
536
+ if (config.runtimeSatori) {
537
+ nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${nitroCompatibility.satori}'
538
+ export default function() {
366
539
  return satori
367
540
  }`;
368
- nitroConfig.virtual["#nuxt-og-image/svg2png"] = `
369
- import svg2png from '${providerPath}/svg2png/universal'
370
- export default async function() {
371
- return svg2png
372
- }`;
541
+ nitroConfig.virtual["#nuxt-og-image/png"] = `import png from '${providerPath}/png/${nitroCompatibility.png}'
542
+ export default function() {
543
+ return png
544
+ }
545
+ `;
373
546
  }
374
547
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
375
- import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
376
- import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
548
+ ${config.runtimeSatori ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
549
+ ${config.runtimeBrowser ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
377
550
 
378
551
  export async function useProvider(provider) {
379
552
  if (provider === 'satori')
380
- return satori
553
+ return ${config.runtimeSatori ? "satori" : "null"}
381
554
  if (provider === 'browser')
382
- return browser
555
+ return ${config.runtimeBrowser ? "browser" : "null"}
556
+ return null
383
557
  }
384
558
  `;
385
559
  });
386
560
  nuxt.hooks.hook("nitro:init", async (nitro) => {
387
561
  let screenshotQueue = [];
388
562
  nitro.hooks.hook("compiled", async (_nitro) => {
389
- if (edgeProvidersSupported.includes(_nitro.options.preset)) {
390
- await copy(resolve("./runtime/public-assets/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
391
- await copy(resolve("./runtime/public-assets/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
392
- if (!config.experimentalInlineWasm) {
393
- await copy(resolve("./runtime/public-assets/svg2png.wasm"), resolve(_nitro.options.output.serverDir, "svg2png.wasm"));
394
- if (useSatoriWasm)
395
- await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
563
+ if (!config.runtimeSatori || nuxt.options.dev)
564
+ return;
565
+ if (config.fonts.includes("Inter:400"))
566
+ await copy(resolve("./runtime/public-assets-optional/inter-font/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
567
+ if (config.fonts.includes("Inter:700"))
568
+ await copy(resolve("./runtime/public-assets-optional/inter-font/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
569
+ const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
570
+ const wasmProviderPath = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
571
+ const paths = [wasmProviderPath];
572
+ const chunks = await globby([`${_nitro.options.output.serverDir}/chunks/**/*.mjs`], { absolute: true });
573
+ paths.push(...chunks);
574
+ for (const path of paths) {
575
+ if (!await pathExists(path))
576
+ continue;
577
+ let contents = await readFile(path, "utf-8");
578
+ let updated = false;
579
+ if (_nitro.options.preset.includes("vercel") && path === wasmProviderPath) {
580
+ contents = contents.replace(".cwd(),", '?.cwd || "/",');
581
+ updated = true;
582
+ }
583
+ if (_nitro.options.preset.includes("netlify") && path.endsWith("netlify.mjs")) {
584
+ const match = "// TODO: handle event.isBase64Encoded\n });";
585
+ contents = contents.replace(match, `${match}
586
+
587
+ const headers = normalizeOutgoingHeaders(r.headers);
588
+ // image buffers must be base64 encoded
589
+ if (Buffer.isBuffer(r.body) && headers["content-type"].startsWith("image/")) {
590
+ return {
591
+ statusCode: r.status,
592
+ headers,
593
+ body: r.body.toString("base64"),
594
+ isBase64Encoded: true
595
+ };
596
+ }`);
597
+ updated = true;
396
598
  }
397
- const indexFile = resolve(_nitro.options.output.serverDir, _nitro.options.preset === "netlify-edge" ? "server.mjs" : "index.mjs");
398
- if (await pathExists(indexFile)) {
399
- const indexContents = (await readFile(indexFile, "utf-8")).replace(".cwd(),", '?.cwd || "/",');
400
- if (!config.experimentalInlineWasm) {
401
- await writeFile(
402
- indexFile,
403
- indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', 'import("./svg2png.wasm").then(m => m.default || m)').replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', 'import("./yoga.wasm").then(m => m.default || m)')
404
- );
405
- } else {
406
- const svg2pngWasm = await readFile(resolve("./runtime/public-assets/svg2png.wasm"), "base64");
407
- const yogaWasm = await readFile(resolve("./runtime/public-assets/yoga.wasm"), "base64");
408
- await writeFile(
409
- indexFile,
410
- indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', `Buffer.from("${svg2pngWasm}", "base64")`).replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', `Buffer.from("${yogaWasm}", "base64")`)
411
- );
599
+ for (const wasm of Wasms) {
600
+ if (contents.includes(wasm.placeholder)) {
601
+ if (nitroCompatibility.wasm === "import") {
602
+ contents = contents.replace(wasm.placeholder, `import("./${wasm.file}${nitroCompatibility.wasmImportQuery || ""}").then(m => m.default || m)`);
603
+ await copy(resolve(`./runtime/public-assets-optional/${wasm.path}`), resolve(dirname(path), wasm.file));
604
+ } else if (nitroCompatibility.wasm === "inline") {
605
+ const wasmBuffer = await readFile(resolve(`./runtime/public-assets-optional/${wasm.path}`));
606
+ contents = contents.replace(wasm.placeholder, `Buffer.from("${wasmBuffer}", "base64")`);
607
+ }
608
+ updated = true;
412
609
  }
413
610
  }
611
+ if (updated)
612
+ await writeFile(path, contents, { encoding: "utf-8" });
414
613
  }
415
614
  });
416
615
  const _routeRulesMatcher = toRouteMatcher(
@@ -423,7 +622,6 @@ export async function useProvider(provider) {
423
622
  if (!html)
424
623
  return;
425
624
  const extractedOptions = extractOgImageOptions(html);
426
- ctx.contents = stripOgImageOptions(html);
427
625
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
428
626
  if (!extractedOptions || routeRules.ogImage === false)
429
627
  return;
@@ -442,21 +640,6 @@ export async function useProvider(provider) {
442
640
  await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
443
641
  if (screenshotQueue.length === 0)
444
642
  return;
445
- for (const entry of screenshotQueue) {
446
- if (entry.route && Object.keys(entry).length === 1) {
447
- const html = await $fetch(entry.route);
448
- const extractedOptions = extractOgImageOptions(html);
449
- const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
450
- Object.assign(entry, {
451
- // @ts-expect-error runtime
452
- path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
453
- ...extractedOptions,
454
- ...routeRules.ogImage || {}
455
- });
456
- }
457
- if (entry.component)
458
- entry.html = await $fetch(entry.path);
459
- }
460
643
  nitro.logger.info("Ensuring chromium install for og:image generation...");
461
644
  const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
462
645
  stdio: "inherit"
@@ -483,12 +666,27 @@ export async function useProvider(provider) {
483
666
  browser = await createBrowser();
484
667
  if (browser) {
485
668
  nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
669
+ for (const entry of screenshotQueue) {
670
+ if (entry.route && Object.keys(entry).length === 1) {
671
+ const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
672
+ const extractedOptions = extractOgImageOptions(html);
673
+ const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
674
+ Object.assign(entry, {
675
+ // @ts-expect-error runtime
676
+ path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
677
+ ...extractedOptions,
678
+ ...routeRules.ogImage || {}
679
+ });
680
+ }
681
+ if (entry.component)
682
+ entry.html = await globalThis.$fetch(entry.path);
683
+ }
486
684
  for (const k in screenshotQueue) {
487
685
  const entry = screenshotQueue[k];
488
686
  const start = Date.now();
489
687
  let hasError = false;
490
- const dirname = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
491
- const filename = joinURL(dirname, "/og.png");
688
+ const dirname2 = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
689
+ const filename = joinURL(dirname2, "/og.png");
492
690
  try {
493
691
  const imgBuffer = await screenshot(browser, {
494
692
  ...config.defaults || {},
@@ -496,7 +694,7 @@ export async function useProvider(provider) {
496
694
  host
497
695
  });
498
696
  try {
499
- await mkdirp(dirname);
697
+ await mkdirp(dirname2);
500
698
  } catch (e) {
501
699
  }
502
700
  await writeFile(filename, imgBuffer);
@@ -520,7 +718,7 @@ export async function useProvider(provider) {
520
718
  }
521
719
  screenshotQueue = [];
522
720
  };
523
- if (!nuxt.options._prepare) {
721
+ if (nuxt.options._generate) {
524
722
  nitro.hooks.hook("rollup:before", async () => {
525
723
  await captureScreenshots();
526
724
  });
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ import type { Buffer } from 'node:buffer';
2
3
  import type { Browser } from 'playwright-core';
3
4
  import type { ScreenshotOptions } from '../types';
4
5
  export declare function screenshot(browser: Browser, options: Partial<ScreenshotOptions> & Record<string, any>): Promise<Buffer>;
@@ -6,9 +6,9 @@ export async function screenshot(browser, options) {
6
6
  width: options.width || 1200,
7
7
  height: options.height || 630
8
8
  });
9
- const isHtml = options.path.startsWith("html:") || options.html;
9
+ const isHtml = options.html || options.path?.startsWith("html:");
10
10
  if (isHtml) {
11
- const html = options.html || options.path.substring(5);
11
+ const html = options.html || options.path?.substring(5);
12
12
  await page.evaluate((html2) => {
13
13
  document.open("text/html");
14
14
  document.write(html2);
@@ -17,10 +17,13 @@ export async function screenshot(browser, options) {
17
17
  await page.waitForLoadState("networkidle");
18
18
  } else {
19
19
  await page.goto(`${options.host}${options.path}`, {
20
- timeout: 1e4,
20
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500,
21
21
  waitUntil: "networkidle"
22
22
  });
23
23
  }
24
+ const screenshotOptions = {
25
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500
26
+ };
24
27
  if (options.delay)
25
28
  await page.waitForTimeout(options.delay);
26
29
  if (options.mask) {
@@ -30,6 +33,8 @@ export async function screenshot(browser, options) {
30
33
  }, options.mask);
31
34
  }
32
35
  if (options.selector)
33
- return await page.locator(options.selector).screenshot();
34
- return await page.screenshot();
36
+ return await page.locator(options.selector).screenshot(screenshotOptions);
37
+ const screenshot2 = await page.screenshot(screenshotOptions);
38
+ await page.close();
39
+ return screenshot2;
35
40
  }
@@ -32,6 +32,11 @@ const props = defineProps({
32
32
  },
33
33
  })
34
34
 
35
+ // inherited attrs can mess up the satori parser
36
+ defineOptions({
37
+ inheritAttrs: false,
38
+ })
39
+
35
40
  const containerAttrs = computed(() => {
36
41
  const isBackgroundTw = props.background?.startsWith('bg-')
37
42
  const isColorTw = props.color?.startsWith('text-')