nuxt-og-image 2.0.0-beta.3 → 2.0.0-beta.31

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 (80) hide show
  1. package/README.md +229 -44
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.1dce7adc.js +1 -0
  5. package/dist/client/_nuxt/IconCSS.f16db5c2.css +1 -0
  6. package/dist/client/_nuxt/ImageLoader.163d23b3.js +1 -0
  7. package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
  8. package/dist/client/_nuxt/entry.23646169.js +5 -0
  9. package/dist/client/_nuxt/entry.3d2654cc.css +1 -0
  10. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.c0f2dfff.js} +1 -1
  11. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.6a9dcc3d.js} +1 -1
  12. package/dist/client/_nuxt/{error-component.cf7543e5.js → error-component.0db07bb1.js} +2 -2
  13. package/dist/client/_nuxt/index.403133d8.css +1 -0
  14. package/dist/client/_nuxt/index.531c9ed8.js +1 -0
  15. package/dist/client/_nuxt/{options.56a3e5f9.js → options.fd71c26a.js} +1 -1
  16. package/dist/client/_nuxt/{png.37f3e77b.js → png.5b6830ea.js} +1 -1
  17. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.1be71764.js} +1 -1
  18. package/dist/client/_nuxt/{svg.186c6bd1.js → svg.165cf4fc.js} +1 -1
  19. package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.11ff70a7.js} +1 -1
  20. package/dist/client/index.html +2 -2
  21. package/dist/client/options/index.html +2 -2
  22. package/dist/client/png/index.html +2 -2
  23. package/dist/client/svg/index.html +2 -2
  24. package/dist/client/vnodes/index.html +2 -2
  25. package/dist/module.d.ts +21 -7
  26. package/dist/module.json +1 -1
  27. package/dist/module.mjs +252 -117
  28. package/dist/runtime/browserUtil.d.ts +1 -0
  29. package/dist/runtime/browserUtil.mjs +10 -5
  30. package/dist/runtime/composables/defineOgImage.mjs +14 -12
  31. package/dist/runtime/nitro/middleware/og.png.mjs +50 -7
  32. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  33. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  34. package/dist/runtime/nitro/plugins/prerender.mjs +26 -0
  35. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  36. package/dist/runtime/nitro/providers/browser/lambda.mjs +5 -5
  37. package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
  38. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  39. package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
  40. package/dist/runtime/nitro/providers/png/resvg.d.ts +4 -0
  41. package/dist/runtime/nitro/providers/png/resvg.mjs +11 -0
  42. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  43. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  44. package/dist/runtime/nitro/renderers/browser.mjs +12 -6
  45. package/dist/runtime/nitro/renderers/satori/index.mjs +16 -13
  46. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
  47. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +27 -3
  48. package/dist/runtime/nitro/renderers/satori/utils.d.ts +2 -2
  49. package/dist/runtime/nitro/renderers/satori/utils.mjs +20 -11
  50. package/dist/runtime/nitro/routes/debug.d.ts +4 -0
  51. package/dist/runtime/nitro/routes/debug.mjs +9 -0
  52. package/dist/runtime/nitro/routes/html.mjs +52 -14
  53. package/dist/runtime/nitro/routes/options.mjs +19 -15
  54. package/dist/runtime/nitro/routes/svg.mjs +3 -1
  55. package/dist/runtime/nitro/routes/vnode.mjs +3 -1
  56. package/dist/runtime/nitro/util-hostname.d.ts +2 -0
  57. package/dist/runtime/nitro/util-hostname.mjs +15 -0
  58. package/dist/runtime/nitro/utils-pure.d.ts +3 -2
  59. package/dist/runtime/nitro/utils-pure.mjs +16 -13
  60. package/dist/runtime/nitro/utils.d.ts +6 -8
  61. package/dist/runtime/nitro/utils.mjs +47 -47
  62. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  63. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  64. package/dist/types.d.ts +6 -0
  65. package/package.json +28 -19
  66. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  67. package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
  68. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  69. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
  70. package/dist/client/_nuxt/index.3f356409.js +0 -1
  71. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
  72. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  73. /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
  74. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  75. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  76. /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
  77. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  78. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  79. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  80. /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(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(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
171
- }
183
+ if (typeof value === "string")
184
+ obj[key] = decodeHtml(value);
172
185
  });
173
186
  return obj;
174
187
  }
@@ -185,27 +198,92 @@ function extractOgImageOptions(html) {
185
198
  console.warn("Failed to parse #nuxt-og-image-options", e, options);
186
199
  }
187
200
  if (options) {
188
- const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
189
- if (description)
190
- options.description = description;
191
- else
192
- options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
193
- return decodeHtmlEntities(options);
201
+ if (!options.description) {
202
+ const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
203
+ if (description)
204
+ options.description = description;
205
+ else
206
+ options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
207
+ }
208
+ return decodeObjectHtmlEntities(options);
194
209
  }
195
210
  return false;
196
211
  }
197
- function stripOgImageOptions(html) {
198
- 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"
239
+ };
240
+ const RuntimeCompatibility = {
241
+ "stackblitz": {
242
+ browser: false,
243
+ satori: "yoga-wasm",
244
+ wasm: "inline"
245
+ },
246
+ "netlify": {
247
+ browser: "lambda",
248
+ wasm: "inline"
249
+ },
250
+ "cloudflare-pages": {
251
+ browser: false,
252
+ wasm: "import"
253
+ },
254
+ "cloudflare": {
255
+ browser: false,
256
+ wasm: "import"
257
+ },
258
+ "vercel-edge": {
259
+ browser: false,
260
+ wasm: "import",
261
+ wasmImportQuery: "?module"
262
+ }
263
+ };
264
+
265
+ function getNitroProviderCompatibility(nuxt) {
266
+ if (nuxt.options.dev || nuxt.options._prepare || nuxt.options._generate) {
267
+ return defu({
268
+ wasm: "fetch",
269
+ browser: "universal"
270
+ }, DefaultRuntimeCompatibility);
271
+ }
272
+ if (provider === "stackblitz")
273
+ return defu(RuntimeCompatibility.stackblitz, DefaultRuntimeCompatibility);
274
+ const target = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
275
+ const compatibility = RuntimeCompatibility[target];
276
+ if (compatibility === false)
277
+ return false;
278
+ return defu(compatibility || {}, DefaultRuntimeCompatibility);
279
+ }
280
+ function ensureDependency(nuxt, dep) {
281
+ return addDependency(dep, { cwd: nuxt.options.rootDir });
199
282
  }
200
283
 
201
284
  const PATH = "/__nuxt_og_image__";
202
285
  const PATH_ENTRY = `${PATH}/entry`;
203
286
  const PATH_PLAYGROUND = `${PATH}/client`;
204
- const edgeProvidersSupported = [
205
- "cloudflare",
206
- "vercel-edge",
207
- "netlify-edge"
208
- ];
209
287
  const module = defineNuxtModule({
210
288
  meta: {
211
289
  name: "nuxt-og-image",
@@ -218,26 +296,51 @@ const module = defineNuxtModule({
218
296
  defaults(nuxt) {
219
297
  const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
220
298
  return {
221
- // when we run `nuxi generate` we need to force prerendering
222
- forcePrerender: !nuxt.options.dev && nuxt.options._generate,
223
299
  siteUrl,
224
300
  defaults: {
225
301
  component: "OgImageBasic",
226
302
  width: 1200,
227
- height: 630
303
+ height: 630,
304
+ cacheTtl: 24 * 60 * 60 * 1e3
305
+ // default is to cache the image for 24 hours
228
306
  },
229
- satoriProvider: true,
230
- browserProvider: true,
307
+ runtimeSatori: true,
308
+ runtimeBrowser: nuxt.options.dev,
231
309
  fonts: [],
310
+ runtimeCacheStorage: false,
232
311
  satoriOptions: {},
233
- experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
234
- experimentalRuntimeBrowser: false,
235
- playground: process.env.NODE_ENV === "development" || nuxt.options.dev
312
+ playground: process.env.NODE_ENV === "development" || nuxt.options.dev,
313
+ debug: false
236
314
  };
237
315
  },
238
316
  async setup(config, nuxt) {
317
+ const logger = useLogger("nuxt-og-image");
239
318
  const { resolve } = createResolver(import.meta.url);
319
+ const nitroCompatibility = getNitroProviderCompatibility(nuxt);
320
+ if (!nitroCompatibility) {
321
+ const target = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
322
+ logger.warn(`It looks like the nitro target ${target} doesn't support \`nuxt-og-image\`.`);
323
+ return;
324
+ }
325
+ if (!nitroCompatibility.browser && config.runtimeBrowser) {
326
+ config.runtimeBrowser = false;
327
+ logger.warn("It looks like you're using a nitro target that does not support the browser provider, disabling `runtimeBrowser`.");
328
+ }
329
+ if (config.runtimeBrowser && nitroCompatibility.browser === "lambda") {
330
+ logger.info("It looks like you're deploying to an environment which requires `chrome-aws-lambda`, checking for dependency...");
331
+ await ensureDependency(nuxt, "chrome-aws-lambda");
332
+ }
240
333
  config.siteUrl = config.siteUrl || config.host;
334
+ if (!nuxt.options.dev && nuxt.options._generate && !config.siteUrl)
335
+ logger.warn("Missing `ogImage.siteUrl` and site is being prerendered. This will result in broken og images.");
336
+ nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
337
+ if (nuxt.options._generate) {
338
+ nuxt.options.nitro.storage["og-image"] = {
339
+ driver: "memory"
340
+ };
341
+ } else if (config.runtimeCacheStorage && !nuxt.options.dev) {
342
+ nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
343
+ }
241
344
  if (!config.fonts.length)
242
345
  config.fonts = ["Inter:400", "Inter:700"];
243
346
  const distResolve = (p) => {
@@ -269,12 +372,14 @@ export {}
269
372
  lazy: true,
270
373
  handler: resolve("./runtime/nitro/middleware/og.png")
271
374
  });
272
- ["html", "options", "svg", "vnode", "font"].forEach((type) => {
273
- addServerHandler({
274
- lazy: true,
275
- route: `/api/og-image-${type}`,
276
- handler: resolve(`./runtime/nitro/routes/${type}`)
277
- });
375
+ ["html", "options", "svg", "vnode", "font", "debug"].forEach((type) => {
376
+ if (type !== "debug" || config.debug) {
377
+ addServerHandler({
378
+ lazy: true,
379
+ route: `/api/og-image-${type}`,
380
+ handler: resolve(`./runtime/nitro/routes/${type}`)
381
+ });
382
+ }
278
383
  });
279
384
  nuxt.hook("devtools:customTabs", (iframeTabs) => {
280
385
  iframeTabs.push({
@@ -323,92 +428,123 @@ export {}
323
428
  });
324
429
  const runtimeDir = resolve("./runtime");
325
430
  nuxt.options.build.transpile.push(runtimeDir);
326
- const moduleAssetDir = resolve("./runtime/public-assets");
327
- const assetDirs = [
328
- resolve(nuxt.options.rootDir, nuxt.options.dir.public),
329
- moduleAssetDir
431
+ addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
432
+ const customAssetDirs = [
433
+ // allows us to show custom error images
434
+ resolve("./runtime/public-assets")
330
435
  ];
436
+ if (config.runtimeSatori) {
437
+ if (config.fonts.includes("Inter:400"))
438
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/inter-font"));
439
+ if (nitroCompatibility.png === "resvg" && nitroCompatibility.wasm === "fetch")
440
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/resvg"));
441
+ else if (nitroCompatibility.png === "svg2png" && nitroCompatibility.wasm === "fetch")
442
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/svg2png"));
443
+ if (nitroCompatibility.satori === "yoga-wasm")
444
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/yoga"));
445
+ }
331
446
  nuxt.hooks.hook("modules:done", async () => {
332
447
  nuxt.hooks.callHook("og-image:config", config);
333
- nuxt.options.runtimeConfig["nuxt-og-image"] = { ...config, assetDirs };
448
+ nuxt.options.runtimeConfig["nuxt-og-image"] = {
449
+ ...config,
450
+ // avoid adding credentials
451
+ runtimeCacheStorage: Boolean(config.runtimeCacheStorage),
452
+ assetDirs: [
453
+ resolve(nuxt.options.rootDir, nuxt.options.dir.public),
454
+ ...customAssetDirs,
455
+ // always add runtime dirs for prerendering to work, these are just used as scan roots
456
+ resolve("./runtime/public-assets-optional/inter-font"),
457
+ resolve("./runtime/public-assets-optional/resvg"),
458
+ resolve("./runtime/public-assets-optional/yoga"),
459
+ resolve("./runtime/public-assets-optional/svg2png")
460
+ ]
461
+ };
334
462
  });
335
- const useSatoriWasm = provider === "stackblitz";
336
463
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
337
464
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
338
465
  inline: [runtimeDir]
339
466
  });
340
- if (config.experimentalRuntimeBrowser) {
467
+ if (config.runtimeBrowser) {
341
468
  nitroConfig.alias = nitroConfig.alias || {};
342
469
  nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
343
470
  nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
344
471
  nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
345
472
  }
346
473
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
347
- nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
474
+ customAssetDirs.forEach((dir) => {
475
+ nitroConfig.publicAssets.push({ dir, maxAge: 31536e3 });
476
+ });
348
477
  const providerPath = `${runtimeDir}/nitro/providers`;
349
- if (config.browserProvider) {
350
- nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || config.experimentalRuntimeBrowser ? `
351
- import node from '${providerPath}/browser/node'
352
-
478
+ if (config.runtimeBrowser) {
479
+ nitroConfig.virtual["#nuxt-og-image/browser"] = `
480
+ let browser
353
481
  export default async function() {
354
- return node
355
- }
356
- ` : `export default async function() {
357
- return () => {}
482
+ browser = browser || await import('${providerPath}/browser/${nitroCompatibility.browser}').then((m) => m.default || m)
483
+ return browser
358
484
  }
359
485
  `;
360
486
  }
361
- if (config.satoriProvider) {
362
- nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${useSatoriWasm ? "webworker" : "node"}'
487
+ if (config.runtimeSatori) {
488
+ nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${nitroCompatibility.satori}'
363
489
  export default async function() {
364
490
  return satori
365
491
  }`;
366
- nitroConfig.virtual["#nuxt-og-image/svg2png"] = `
367
- import svg2png from '${providerPath}/svg2png/universal'
492
+ nitroConfig.virtual["#nuxt-og-image/png"] = `import png from '${providerPath}/png/${nitroCompatibility.png}'
368
493
  export default async function() {
369
- return svg2png
370
- }`;
494
+ return png
495
+ }
496
+ `;
371
497
  }
372
498
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
373
- import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
374
- import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
499
+ ${config.runtimeSatori ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
500
+ ${config.runtimeBrowser ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
375
501
 
376
502
  export async function useProvider(provider) {
377
503
  if (provider === 'satori')
378
- return satori
504
+ return ${config.runtimeSatori ? "satori" : "null"}
379
505
  if (provider === 'browser')
380
- return browser
506
+ return ${config.runtimeBrowser ? "browser" : "null"}
507
+ return null
381
508
  }
382
509
  `;
383
510
  });
384
511
  nuxt.hooks.hook("nitro:init", async (nitro) => {
385
512
  let screenshotQueue = [];
386
513
  nitro.hooks.hook("compiled", async (_nitro) => {
387
- if (edgeProvidersSupported.includes(_nitro.options.preset)) {
388
- await copy(resolve("./runtime/public-assets/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
389
- await copy(resolve("./runtime/public-assets/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
390
- if (!config.experimentalInlineWasm) {
391
- await copy(resolve("./runtime/public-assets/svg2png.wasm"), resolve(_nitro.options.output.serverDir, "svg2png.wasm"));
392
- if (useSatoriWasm)
393
- await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
514
+ if (!config.runtimeSatori || nuxt.options.dev)
515
+ return;
516
+ if (config.fonts.includes("Inter:400"))
517
+ 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"));
518
+ if (config.fonts.includes("Inter:700"))
519
+ 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"));
520
+ const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
521
+ const wasmProviderPath = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
522
+ const paths = [wasmProviderPath];
523
+ const chunks = await globby([`${_nitro.options.output.serverDir}/chunks/**/*.mjs`], { absolute: true });
524
+ paths.push(...chunks);
525
+ for (const path of paths) {
526
+ if (!await pathExists(path))
527
+ continue;
528
+ let contents = await readFile(path, "utf-8");
529
+ let updated = false;
530
+ if (_nitro.options.preset.includes("vercel") && path === wasmProviderPath) {
531
+ contents = contents.replace(".cwd(),", '?.cwd || "/",');
532
+ updated = true;
394
533
  }
395
- const indexFile = resolve(_nitro.options.output.serverDir, _nitro.options.preset === "netlify-edge" ? "server.mjs" : "index.mjs");
396
- if (await pathExists(indexFile)) {
397
- const indexContents = (await readFile(indexFile, "utf-8")).replace(".cwd(),", '?.cwd || "/",');
398
- if (!config.experimentalInlineWasm) {
399
- await writeFile(
400
- indexFile,
401
- 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)')
402
- );
403
- } else {
404
- const svg2pngWasm = await readFile(resolve("./runtime/public-assets/svg2png.wasm"), "base64");
405
- const yogaWasm = await readFile(resolve("./runtime/public-assets/yoga.wasm"), "base64");
406
- await writeFile(
407
- indexFile,
408
- indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', `Buffer.from("${svg2pngWasm}", "base64")`).replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', `Buffer.from("${yogaWasm}", "base64")`)
409
- );
534
+ for (const wasm of Wasms) {
535
+ if (contents.includes(wasm.placeholder)) {
536
+ if (nitroCompatibility.wasm === "import") {
537
+ contents = contents.replace(wasm.placeholder, `import("./${wasm.file}${nitroCompatibility.wasmImportQuery || ""}").then(m => m.default || m)`);
538
+ await copy(resolve(`./runtime/public-assets-optional/${wasm.path}`), resolve(dirname(path), wasm.file));
539
+ } else if (nitroCompatibility.wasm === "inline") {
540
+ const wasmBuffer = await readFile(resolve(`./runtime/public-assets-optional/${wasm.path}`), "base64");
541
+ contents = contents.replace(wasm.placeholder, `Buffer.from("${wasmBuffer}", "base64")`);
542
+ }
543
+ updated = true;
410
544
  }
411
545
  }
546
+ if (updated)
547
+ await writeFile(path, contents, { encoding: "utf-8" });
412
548
  }
413
549
  });
414
550
  const _routeRulesMatcher = toRouteMatcher(
@@ -421,7 +557,6 @@ export async function useProvider(provider) {
421
557
  if (!html)
422
558
  return;
423
559
  const extractedOptions = extractOgImageOptions(html);
424
- ctx.contents = stripOgImageOptions(html);
425
560
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
426
561
  if (!extractedOptions || routeRules.ogImage === false)
427
562
  return;
@@ -440,21 +575,6 @@ export async function useProvider(provider) {
440
575
  await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
441
576
  if (screenshotQueue.length === 0)
442
577
  return;
443
- for (const entry of screenshotQueue) {
444
- if (entry.route && Object.keys(entry).length === 1) {
445
- const html = await $fetch(entry.route);
446
- const extractedOptions = extractOgImageOptions(html);
447
- const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
448
- Object.assign(entry, {
449
- // @ts-expect-error runtime
450
- path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
451
- ...extractedOptions,
452
- ...routeRules.ogImage || {}
453
- });
454
- }
455
- if (entry.component)
456
- entry.html = await $fetch(entry.path);
457
- }
458
578
  nitro.logger.info("Ensuring chromium install for og:image generation...");
459
579
  const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
460
580
  stdio: "inherit"
@@ -481,12 +601,27 @@ export async function useProvider(provider) {
481
601
  browser = await createBrowser();
482
602
  if (browser) {
483
603
  nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
604
+ for (const entry of screenshotQueue) {
605
+ if (entry.route && Object.keys(entry).length === 1) {
606
+ const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
607
+ const extractedOptions = extractOgImageOptions(html);
608
+ const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
609
+ Object.assign(entry, {
610
+ // @ts-expect-error runtime
611
+ path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
612
+ ...extractedOptions,
613
+ ...routeRules.ogImage || {}
614
+ });
615
+ }
616
+ if (entry.component)
617
+ entry.html = await globalThis.$fetch(entry.path);
618
+ }
484
619
  for (const k in screenshotQueue) {
485
620
  const entry = screenshotQueue[k];
486
621
  const start = Date.now();
487
622
  let hasError = false;
488
- const dirname = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
489
- const filename = joinURL(dirname, "/og.png");
623
+ const dirname2 = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
624
+ const filename = joinURL(dirname2, "/og.png");
490
625
  try {
491
626
  const imgBuffer = await screenshot(browser, {
492
627
  ...config.defaults || {},
@@ -494,7 +629,7 @@ export async function useProvider(provider) {
494
629
  host
495
630
  });
496
631
  try {
497
- await mkdirp(dirname);
632
+ await mkdirp(dirname2);
498
633
  } catch (e) {
499
634
  }
500
635
  await writeFile(filename, imgBuffer);
@@ -518,7 +653,7 @@ export async function useProvider(provider) {
518
653
  }
519
654
  screenshotQueue = [];
520
655
  };
521
- if (!nuxt.options._prepare) {
656
+ if (nuxt.options._generate) {
522
657
  nitro.hooks.hook("rollup:before", async () => {
523
658
  await captureScreenshots();
524
659
  });
@@ -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
  }
@@ -1,7 +1,7 @@
1
- import { useServerHead } from "@vueuse/head";
2
1
  import { withBase } from "ufo";
3
2
  import { useRequestEvent } from "#app";
4
- import { useRouter, useRuntimeConfig } from "#imports";
3
+ import { useHostname } from "../nitro/util-hostname.mjs";
4
+ import { useRouter, useRuntimeConfig, useServerHead } from "#imports";
5
5
  export function defineOgImageScreenshot(options = {}) {
6
6
  const router = useRouter();
7
7
  const route = router?.currentRoute?.value?.path || "";
@@ -14,29 +14,31 @@ export function defineOgImageScreenshot(options = {}) {
14
14
  });
15
15
  }
16
16
  export function defineOgImageDynamic(options = {}) {
17
- const { satoriProvider, forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
17
+ const { runtimeSatori } = useRuntimeConfig()["nuxt-og-image"];
18
18
  defineOgImage({
19
- provider: satoriProvider ? "satori" : "browser",
20
- static: !!forcePrerender,
19
+ provider: runtimeSatori ? "satori" : "browser",
20
+ static: false,
21
21
  ...options
22
22
  });
23
23
  }
24
24
  export function defineOgImageStatic(options = {}) {
25
- const { satoriProvider } = useRuntimeConfig()["nuxt-og-image"];
25
+ const { runtimeSatori } = useRuntimeConfig()["nuxt-og-image"];
26
26
  defineOgImage({
27
- provider: satoriProvider ? "satori" : "browser",
27
+ provider: runtimeSatori ? "satori" : "browser",
28
28
  static: true,
29
29
  ...options
30
30
  });
31
31
  }
32
32
  export function defineOgImage(options = {}) {
33
33
  if (process.server) {
34
- const { forcePrerender, defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
34
+ let resolveSrc = function() {
35
+ return withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl);
36
+ };
37
+ const { defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
35
38
  const router = useRouter();
36
39
  const route = router?.currentRoute?.value?.path || "";
37
40
  const e = useRequestEvent();
38
- if ((forcePrerender || options.static) && options.provider === "satori")
39
- e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
41
+ const baseUrl = process.env.prerender ? siteUrl : useHostname(e);
40
42
  const meta = [
41
43
  {
42
44
  name: "twitter:card",
@@ -44,11 +46,11 @@ export function defineOgImage(options = {}) {
44
46
  },
45
47
  {
46
48
  name: "twitter:image:src",
47
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, siteUrl)
49
+ content: resolveSrc
48
50
  },
49
51
  {
50
52
  property: "og:image",
51
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, siteUrl)
53
+ content: resolveSrc
52
54
  },
53
55
  {
54
56
  property: "og:image:width",