nuxt-og-image 2.0.0-beta.21 → 2.0.0-beta.22

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 (59) hide show
  1. package/README.md +82 -15
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.779331aa.js +1 -0
  5. package/dist/client/_nuxt/{ImageLoader.97400b2b.js → ImageLoader.9ed308b0.js} +1 -1
  6. package/dist/client/_nuxt/{entry.7e98a020.css → entry.acc10163.css} +1 -1
  7. package/dist/client/_nuxt/entry.e7270163.js +5 -0
  8. package/dist/client/_nuxt/{error-404.7660b68a.js → error-404.dcc06a80.js} +1 -1
  9. package/dist/client/_nuxt/{error-500.776f22a1.js → error-500.fb40d400.js} +1 -1
  10. package/dist/client/_nuxt/{error-component.d4668032.js → error-component.9702a511.js} +2 -2
  11. package/dist/client/_nuxt/{index.77081a6c.js → index.6a247c9d.js} +1 -1
  12. package/dist/client/_nuxt/{options.b6164a5b.js → options.481faa9f.js} +1 -1
  13. package/dist/client/_nuxt/{png.b914f6c4.js → png.7e62d84b.js} +1 -1
  14. package/dist/client/_nuxt/{shiki.ba10b978.js → shiki.0c927d45.js} +1 -1
  15. package/dist/client/_nuxt/{svg.1e41877e.js → svg.81bf3f5a.js} +1 -1
  16. package/dist/client/_nuxt/{vnodes.a857bced.js → vnodes.096af306.js} +1 -1
  17. package/dist/client/index.html +2 -2
  18. package/dist/client/options/index.html +2 -2
  19. package/dist/client/png/index.html +2 -2
  20. package/dist/client/svg/index.html +2 -2
  21. package/dist/client/vnodes/index.html +2 -2
  22. package/dist/module.d.ts +15 -5
  23. package/dist/module.json +1 -1
  24. package/dist/module.mjs +194 -84
  25. package/dist/runtime/browserUtil.mjs +5 -3
  26. package/dist/runtime/composables/defineOgImage.mjs +10 -7
  27. package/dist/runtime/nitro/middleware/og.png.mjs +43 -15
  28. package/dist/runtime/nitro/plugins/prerender.mjs +1 -3
  29. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  30. package/dist/runtime/nitro/providers/browser/lambda.mjs +5 -5
  31. package/dist/runtime/nitro/providers/browser/playwright.mjs +22 -0
  32. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  33. package/dist/runtime/nitro/providers/png/resvg.d.ts +4 -0
  34. package/dist/runtime/nitro/providers/png/resvg.mjs +11 -0
  35. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  36. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  37. package/dist/runtime/nitro/renderers/browser.mjs +7 -6
  38. package/dist/runtime/nitro/renderers/satori/index.mjs +3 -3
  39. package/dist/runtime/nitro/routes/debug.d.ts +4 -0
  40. package/dist/runtime/nitro/routes/debug.mjs +9 -0
  41. package/dist/runtime/nitro/routes/options.mjs +3 -3
  42. package/dist/runtime/nitro/utils.d.ts +4 -3
  43. package/dist/runtime/nitro/utils.mjs +37 -18
  44. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  45. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  46. package/package.json +15 -8
  47. package/dist/client/_nuxt/IconCSS.ecebf6a5.js +0 -1
  48. package/dist/client/_nuxt/entry.c8bf4454.js +0 -5
  49. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -12
  50. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  51. /package/dist/runtime/nitro/providers/browser/{node.mjs → universal.mjs} +0 -0
  52. /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
  53. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  54. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  55. /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
  56. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  57. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  58. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  59. /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
package/dist/module.mjs CHANGED
@@ -1,19 +1,21 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent, addServerPlugin } 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
7
  import { withBase, joinURL } from 'ufo';
8
- import { resolve, relative } from 'pathe';
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
21
  if (process.dev || process.env.prerender) {
@@ -67,12 +69,12 @@ async function screenshot(browser, options) {
67
69
  await page.waitForLoadState("networkidle");
68
70
  } else {
69
71
  await page.goto(`${options.host}${options.path}`, {
70
- timeout: 1e4,
72
+ timeout: process.env.prerender || process.dev ? 1e4 : 2e3,
71
73
  waitUntil: "networkidle"
72
74
  });
73
75
  }
74
76
  const screenshotOptions = {
75
- timeout: 1e4
77
+ timeout: process.env.prerender || process.dev ? 1e4 : 2e3
76
78
  };
77
79
  if (options.delay)
78
80
  await page.waitForTimeout(options.delay);
@@ -84,7 +86,9 @@ async function screenshot(browser, options) {
84
86
  }
85
87
  if (options.selector)
86
88
  return await page.locator(options.selector).screenshot(screenshotOptions);
87
- return await page.screenshot(screenshotOptions);
89
+ const screenshot2 = await page.screenshot(screenshotOptions);
90
+ await page.close();
91
+ return screenshot2;
88
92
  }
89
93
 
90
94
  function setupPlaygroundRPC(nuxt, config) {
@@ -206,15 +210,76 @@ function extractOgImageOptions(html) {
206
210
  return false;
207
211
  }
208
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
+ browser: "playwright",
235
+ satori: "default",
236
+ wasm: "import",
237
+ png: "resvg"
238
+ };
239
+ const RuntimeCompatibility = {
240
+ "stackblitz": {
241
+ browser: false,
242
+ satori: "yoga-wasm",
243
+ wasm: "inline"
244
+ },
245
+ "netlify": {
246
+ browser: "lambda",
247
+ wasm: "inline"
248
+ },
249
+ "cloudflare-pages": {
250
+ browser: false
251
+ },
252
+ "cloudflare": {
253
+ browser: false
254
+ },
255
+ "vercel-edge": {
256
+ browser: false,
257
+ wasmImportQuery: "?module"
258
+ }
259
+ };
260
+
261
+ function getNitroProviderCompatibility(nuxt) {
262
+ if (nuxt.options.dev || nuxt.options._prepare) {
263
+ return defu({
264
+ wasm: "fetch",
265
+ browser: "universal"
266
+ }, DefaultRuntimeCompatibility);
267
+ }
268
+ if (provider === "stackblitz")
269
+ return defu(RuntimeCompatibility.stackblitz, DefaultRuntimeCompatibility);
270
+ const target = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
271
+ const compatibility = RuntimeCompatibility[target];
272
+ if (compatibility === false)
273
+ return false;
274
+ return defu(compatibility || {}, DefaultRuntimeCompatibility);
275
+ }
276
+ function ensureDependency(nuxt, dep) {
277
+ return addDependency(dep, { cwd: nuxt.options.rootDir });
278
+ }
279
+
209
280
  const PATH = "/__nuxt_og_image__";
210
281
  const PATH_ENTRY = `${PATH}/entry`;
211
282
  const PATH_PLAYGROUND = `${PATH}/client`;
212
- const edgeProvidersSupported = [
213
- "cloudflare",
214
- "cloudflare-pages",
215
- "vercel-edge",
216
- "netlify-edge"
217
- ];
218
283
  const module = defineNuxtModule({
219
284
  meta: {
220
285
  name: "nuxt-og-image",
@@ -226,28 +291,48 @@ const module = defineNuxtModule({
226
291
  },
227
292
  defaults(nuxt) {
228
293
  const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
229
- const isEdgeProvider = edgeProvidersSupported.includes(process.env.NITRO_PRESET || "") || edgeProvidersSupported.includes(nuxt.options.nitro.preset);
230
294
  return {
231
- // when we run `nuxi generate` we need to force prerendering
232
- forcePrerender: !nuxt.options.dev && nuxt.options._generate,
233
295
  siteUrl,
234
296
  defaults: {
235
297
  component: "OgImageBasic",
236
298
  width: 1200,
237
- height: 630
299
+ height: 630,
300
+ cacheTtl: 24 * 60 * 60 * 1e3
301
+ // default is to cache the image for 24 hours
238
302
  },
239
- satoriProvider: true,
240
- // disable browser in edge environments
241
- browserProvider: !isEdgeProvider,
303
+ runtimeSatori: true,
304
+ runtimeBrowser: nuxt.options.dev,
242
305
  fonts: [],
306
+ runtimeCacheStorage: false,
243
307
  satoriOptions: {},
244
- experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
245
- playground: process.env.NODE_ENV === "development" || nuxt.options.dev
308
+ playground: process.env.NODE_ENV === "development" || nuxt.options.dev,
309
+ debug: false
246
310
  };
247
311
  },
248
312
  async setup(config, nuxt) {
313
+ const logger = useLogger("nuxt-og-image");
249
314
  const { resolve } = createResolver(import.meta.url);
315
+ const nitroCompatibility = getNitroProviderCompatibility(nuxt);
316
+ if (!nitroCompatibility) {
317
+ const target = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
318
+ logger.warn(`It looks like the nitro target ${target} doesn't support \`nuxt-og-image\`.`);
319
+ return;
320
+ }
321
+ if (!nitroCompatibility.browser && config.runtimeBrowser) {
322
+ config.runtimeBrowser = false;
323
+ logger.warn("It looks like you're using a nitro target that does not support the browser provider, disabling `runtimeBrowser`.");
324
+ }
325
+ if (nitroCompatibility.browser === "lambda") {
326
+ logger.info("It looks like you're deploying to an environment which requires `chrome-aws-lambda`, checking for dependency...");
327
+ await ensureDependency(nuxt, "chrome-aws-lambda");
328
+ }
250
329
  config.siteUrl = config.siteUrl || config.host;
330
+ if (!nuxt.options.dev && nuxt.options._generate && !config.siteUrl)
331
+ logger.warn("Nuxt is being prerendered but no siteUrl is set. This will result in broken images.");
332
+ if (config.runtimeCacheStorage && !nuxt.options.dev) {
333
+ nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
334
+ nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
335
+ }
251
336
  if (!config.fonts.length)
252
337
  config.fonts = ["Inter:400", "Inter:700"];
253
338
  const distResolve = (p) => {
@@ -279,12 +364,14 @@ export {}
279
364
  lazy: true,
280
365
  handler: resolve("./runtime/nitro/middleware/og.png")
281
366
  });
282
- ["html", "options", "svg", "vnode", "font"].forEach((type) => {
283
- addServerHandler({
284
- lazy: true,
285
- route: `/api/og-image-${type}`,
286
- handler: resolve(`./runtime/nitro/routes/${type}`)
287
- });
367
+ ["html", "options", "svg", "vnode", "font", "debug"].forEach((type) => {
368
+ if (type !== "debug" || config.debug) {
369
+ addServerHandler({
370
+ lazy: true,
371
+ route: `/api/og-image-${type}`,
372
+ handler: resolve(`./runtime/nitro/routes/${type}`)
373
+ });
374
+ }
288
375
  });
289
376
  nuxt.hook("devtools:customTabs", (iframeTabs) => {
290
377
  iframeTabs.push({
@@ -334,57 +421,76 @@ export {}
334
421
  const runtimeDir = resolve("./runtime");
335
422
  nuxt.options.build.transpile.push(runtimeDir);
336
423
  addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
337
- const moduleAssetDir = resolve("./runtime/public-assets");
338
- const assetDirs = [
339
- resolve(nuxt.options.rootDir, nuxt.options.dir.public),
340
- moduleAssetDir
424
+ const customAssetDirs = [
425
+ // allows us to show custom error images
426
+ resolve("./runtime/public-assets")
341
427
  ];
428
+ if (config.runtimeSatori) {
429
+ if (config.fonts.includes("Inter:400"))
430
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/inter-font"));
431
+ if (nitroCompatibility.png === "resvg" && nitroCompatibility.wasm === "fetch")
432
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/resvg"));
433
+ else if (nitroCompatibility.png === "svg2png" && nitroCompatibility.wasm === "fetch")
434
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/svg2png"));
435
+ if (nitroCompatibility.satori === "yoga-wasm")
436
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/yoga"));
437
+ }
342
438
  nuxt.hooks.hook("modules:done", async () => {
343
439
  nuxt.hooks.callHook("og-image:config", config);
344
- nuxt.options.runtimeConfig["nuxt-og-image"] = { ...config, assetDirs };
440
+ nuxt.options.runtimeConfig["nuxt-og-image"] = {
441
+ ...config,
442
+ // avoid adding credentials
443
+ runtimeCacheStorage: Boolean(config.runtimeCacheStorage),
444
+ assetDirs: [
445
+ resolve(nuxt.options.rootDir, nuxt.options.dir.public),
446
+ ...customAssetDirs
447
+ ]
448
+ };
345
449
  });
346
- const useSatoriWasm = provider === "stackblitz";
347
450
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
348
451
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
349
452
  inline: [runtimeDir]
350
453
  });
454
+ if (config.runtimeBrowser) {
455
+ nitroConfig.alias = nitroConfig.alias || {};
456
+ nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
457
+ nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
458
+ nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
459
+ }
351
460
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
352
- nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
461
+ customAssetDirs.forEach((dir) => {
462
+ nitroConfig.publicAssets.push({ dir, maxAge: 31536e3 });
463
+ });
353
464
  const providerPath = `${runtimeDir}/nitro/providers`;
354
- const nitroPreset = nuxt.options.nitro.preset || process.env.NITRO_PRESET;
355
- const isNodeNitroServer = !nitroPreset || nitroPreset === "node";
356
- if (config.browserProvider) {
357
- nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || process.env.prerender || isNodeNitroServer ? `
358
- import node from '${providerPath}/browser/node'
359
-
465
+ if (config.runtimeBrowser) {
466
+ nitroConfig.virtual["#nuxt-og-image/browser"] = `
467
+ let browser
360
468
  export default async function() {
361
- return node
362
- }
363
- ` : `export default async function() {
364
- return () => {}
469
+ browser = browser || await import('${providerPath}/browser/${nitroCompatibility.browser}').then((m) => m.default || m)
470
+ return browser
365
471
  }
366
472
  `;
367
473
  }
368
- if (config.satoriProvider) {
369
- nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${useSatoriWasm ? "webworker" : "node"}'
474
+ if (config.runtimeSatori) {
475
+ nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${nitroCompatibility.satori}'
370
476
  export default async function() {
371
477
  return satori
372
478
  }`;
373
- nitroConfig.virtual["#nuxt-og-image/svg2png"] = `
374
- import svg2png from '${providerPath}/svg2png/universal'
479
+ nitroConfig.virtual["#nuxt-og-image/png"] = `import png from '${providerPath}/png/${nitroCompatibility.png}'
375
480
  export default async function() {
376
- return svg2png
377
- }`;
481
+ return png
482
+ }
483
+ `;
378
484
  }
379
485
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
380
- ${config.satoriProvider ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
381
- ${config.browserProvider ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
486
+ ${config.runtimeSatori ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
487
+ ${config.runtimeBrowser ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
382
488
 
383
489
  export async function useProvider(provider) {
384
490
  if (provider === 'satori')
385
- return ${config.satoriProvider ? "satori" : "null"}
491
+ return ${config.runtimeSatori ? "satori" : "null"}
386
492
  if (provider === 'browser')
387
- return ${config.browserProvider ? "browser" : "null"}
493
+ return ${config.runtimeBrowser ? "browser" : "null"}
388
494
  return null
389
495
  }
390
496
  `;
@@ -392,36 +498,40 @@ export async function useProvider(provider) {
392
498
  nuxt.hooks.hook("nitro:init", async (nitro) => {
393
499
  let screenshotQueue = [];
394
500
  nitro.hooks.hook("compiled", async (_nitro) => {
395
- if (edgeProvidersSupported.includes(_nitro.options.preset)) {
396
- await copy(resolve("./runtime/public-assets/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
397
- await copy(resolve("./runtime/public-assets/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
398
- if (!config.experimentalInlineWasm) {
399
- await copy(resolve("./runtime/public-assets/svg2png.wasm"), resolve(_nitro.options.output.serverDir, "svg2png.wasm"));
400
- if (useSatoriWasm)
401
- await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
501
+ if (!config.runtimeSatori || nuxt.options.dev)
502
+ return;
503
+ if (config.fonts.includes("Inter:400"))
504
+ 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"));
505
+ if (config.fonts.includes("Inter:700"))
506
+ 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"));
507
+ const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
508
+ const wasmProviderPath = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
509
+ const paths = [wasmProviderPath];
510
+ const chunks = await globby([`${_nitro.options.output.serverDir}/chunks/**/*.mjs`], { absolute: true });
511
+ paths.push(...chunks);
512
+ for (const path of paths) {
513
+ if (!await pathExists(path))
514
+ continue;
515
+ let contents = await readFile(path, "utf-8");
516
+ let updated = false;
517
+ if (_nitro.options.preset.includes("vercel") && path === wasmProviderPath) {
518
+ contents = contents.replace(".cwd(),", '?.cwd || "/",');
519
+ updated = true;
402
520
  }
403
- const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
404
- const entryFile = typeof configuredEntry === "string" ? configuredEntry : "index.mjs";
405
- const indexFile = resolve(_nitro.options.output.serverDir, entryFile);
406
- if (await pathExists(indexFile)) {
407
- let indexContents = await readFile(indexFile, "utf-8");
408
- if (_nitro.options.preset.includes("vercel")) {
409
- indexContents = indexContents.replace(".cwd(),", '?.cwd || "/",');
410
- }
411
- if (!config.experimentalInlineWasm) {
412
- await writeFile(
413
- indexFile,
414
- 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)')
415
- );
416
- } else {
417
- const svg2pngWasm = await readFile(resolve("./runtime/public-assets/svg2png.wasm"), "base64");
418
- const yogaWasm = await readFile(resolve("./runtime/public-assets/yoga.wasm"), "base64");
419
- await writeFile(
420
- indexFile,
421
- indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', `Buffer.from("${svg2pngWasm}", "base64")`).replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', `Buffer.from("${yogaWasm}", "base64")`)
422
- );
521
+ for (const wasm of Wasms) {
522
+ if (contents.includes(wasm.placeholder)) {
523
+ if (nitroCompatibility.wasm === "import") {
524
+ contents = contents.replace(wasm.placeholder, `import("./${wasm.file}${nitroCompatibility.wasmImportQuery || ""}").then(m => m.default || m)`);
525
+ await copy(resolve(`./runtime/public-assets-optional/${wasm.path}`), resolve(dirname(path), wasm.file));
526
+ } else if (nitroCompatibility.wasm === "inline") {
527
+ const wasmBuffer = await readFile(resolve(`./runtime/public-assets-optional/${wasm.path}`), "base64");
528
+ contents = contents.replace(wasm.placeholder, `Buffer.from("${wasmBuffer}", "base64")`);
529
+ }
530
+ updated = true;
423
531
  }
424
532
  }
533
+ if (updated)
534
+ await writeFile(path, contents, { encoding: "utf-8" });
425
535
  }
426
536
  });
427
537
  const _routeRulesMatcher = toRouteMatcher(
@@ -497,8 +607,8 @@ export async function useProvider(provider) {
497
607
  const entry = screenshotQueue[k];
498
608
  const start = Date.now();
499
609
  let hasError = false;
500
- const dirname = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
501
- const filename = joinURL(dirname, "/og.png");
610
+ const dirname2 = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
611
+ const filename = joinURL(dirname2, "/og.png");
502
612
  try {
503
613
  const imgBuffer = await screenshot(browser, {
504
614
  ...config.defaults || {},
@@ -506,7 +616,7 @@ export async function useProvider(provider) {
506
616
  host
507
617
  });
508
618
  try {
509
- await mkdirp(dirname);
619
+ await mkdirp(dirname2);
510
620
  } catch (e) {
511
621
  }
512
622
  await writeFile(filename, imgBuffer);
@@ -17,12 +17,12 @@ 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 : 2e3,
21
21
  waitUntil: "networkidle"
22
22
  });
23
23
  }
24
24
  const screenshotOptions = {
25
- timeout: 1e4
25
+ timeout: process.env.prerender || process.dev ? 1e4 : 2e3
26
26
  };
27
27
  if (options.delay)
28
28
  await page.waitForTimeout(options.delay);
@@ -34,5 +34,7 @@ export async function screenshot(browser, options) {
34
34
  }
35
35
  if (options.selector)
36
36
  return await page.locator(options.selector).screenshot(screenshotOptions);
37
- return await page.screenshot(screenshotOptions);
37
+ const screenshot2 = await page.screenshot(screenshotOptions);
38
+ await page.close();
39
+ return screenshot2;
38
40
  }
@@ -14,23 +14,26 @@ 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
+ let resolveSrc = function() {
35
+ return withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl);
36
+ };
34
37
  const { defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
35
38
  const router = useRouter();
36
39
  const route = router?.currentRoute?.value?.path || "";
@@ -43,11 +46,11 @@ export function defineOgImage(options = {}) {
43
46
  },
44
47
  {
45
48
  name: "twitter:image:src",
46
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
49
+ content: resolveSrc
47
50
  },
48
51
  {
49
52
  property: "og:image",
50
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
53
+ content: resolveSrc
51
54
  },
52
55
  {
53
56
  property: "og:image:width",
@@ -1,18 +1,21 @@
1
- import { createError, defineEventHandler, setHeader } from "h3";
2
- import { parseURL, withBase, withoutTrailingSlash } from "ufo";
1
+ import { Buffer } from "node:buffer";
2
+ import { createError, defineEventHandler, sendRedirect, setHeader } from "h3";
3
+ import { joinURL, parseURL, withBase, withoutTrailingSlash } from "ufo";
4
+ import { prefixStorage } from "unstorage";
3
5
  import { fetchOptions, useHostname } from "../utils.mjs";
4
6
  import { useProvider } from "#nuxt-og-image/provider";
7
+ import { useRuntimeConfig, useStorage } from "#imports";
5
8
  export default defineEventHandler(async (e) => {
9
+ const { runtimeBrowser, runtimeCacheStorage } = useRuntimeConfig()["nuxt-og-image"];
6
10
  const path = parseURL(e.path).pathname;
7
11
  if (!path.endsWith("__og_image__/og.png"))
8
12
  return;
9
13
  const basePath = withoutTrailingSlash(
10
14
  path.replace("__og_image__/og.png", "")
11
15
  );
12
- setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
13
- setHeader(e, "Pragma", "no-cache");
14
- setHeader(e, "Expires", "0");
15
16
  const options = await fetchOptions(e, basePath);
17
+ if (process.env.NODE_ENV === "production" && !process.env.prerender && !runtimeBrowser && options.provider === "browser")
18
+ return sendRedirect(e, joinURL(useHostname(e), "__nuxt_og_image__/browser-provider-not-supported.png"));
16
19
  const provider = await useProvider(options.provider);
17
20
  if (!provider) {
18
21
  throw createError({
@@ -20,21 +23,46 @@ export default defineEventHandler(async (e) => {
20
23
  statusMessage: `Provider ${options.provider} is missing.`
21
24
  });
22
25
  }
23
- try {
24
- const png = await provider.createPng(withBase(basePath, useHostname(e)), options);
25
- if (png) {
26
+ const cache = prefixStorage(useStorage(), "og-image-cache:images");
27
+ const key = options.cacheKey || e.node.req.url;
28
+ let png;
29
+ if (runtimeCacheStorage && await cache.hasItem(key)) {
30
+ const { value, expiresAt } = await cache.getItem(key);
31
+ if (expiresAt > Date.now()) {
32
+ setHeader(e, "Cache-Control", "public, max-age=31536000");
26
33
  setHeader(e, "Content-Type", "image/png");
27
- return png;
34
+ png = Buffer.from(value, "base64");
35
+ } else {
36
+ await cache.removeItem(key);
28
37
  }
29
- } catch (err) {
30
- throw createError({
31
- statusCode: 500,
32
- statusMessage: `Failed to create og image: ${err.message}`
33
- });
38
+ }
39
+ if (!png) {
40
+ try {
41
+ png = await provider.createPng(withBase(basePath, useHostname(e)), options);
42
+ if (png && runtimeCacheStorage && options.static) {
43
+ const base64png = Buffer.from(png).toString("base64");
44
+ await cache.setItem(key, { value: base64png, expiresAt: Date.now() + (options.cacheTtl || 0) });
45
+ }
46
+ } catch (err) {
47
+ throw createError({
48
+ statusCode: 500,
49
+ statusMessage: `Failed to create og image: ${err.message}`
50
+ });
51
+ }
52
+ }
53
+ if (png) {
54
+ if (!process.dev && options.static) {
55
+ setHeader(e, "Cache-Control", "public, max-age=31536000");
56
+ } else {
57
+ setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
58
+ setHeader(e, "Pragma", "no-cache");
59
+ setHeader(e, "Expires", "0");
60
+ }
61
+ setHeader(e, "Content-Type", "image/png");
62
+ return png;
34
63
  }
35
64
  throw createError({
36
65
  statusCode: 500,
37
66
  statusMessage: "Failed to create og image, unknown error."
38
67
  });
39
- return false;
40
68
  });
@@ -1,11 +1,9 @@
1
1
  import { appendHeader } from "h3";
2
2
  import { joinURL } from "ufo";
3
3
  import { extractOgImageOptions } from "../utils-pure.mjs";
4
- import { useRuntimeConfig } from "#imports";
5
4
  const OgImagePrenderNitroPlugin = (nitroApp) => {
6
5
  if (!process.env.prerender)
7
6
  return;
8
- const { forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
9
7
  nitroApp.hooks.hook("render:html", async (ctx, { event }) => {
10
8
  const url = event.node.req.url;
11
9
  if (url.includes(".") || url.startsWith("/__nuxt_island/"))
@@ -13,7 +11,7 @@ const OgImagePrenderNitroPlugin = (nitroApp) => {
13
11
  const options = extractOgImageOptions(ctx.head.join("\n"));
14
12
  if (!options)
15
13
  return;
16
- if ((forcePrerender || options.static) && options.provider === "satori")
14
+ if (options.static && options.provider === "satori")
17
15
  appendHeader(event, "x-nitro-prerender", joinURL(url, "/__og_image__/og.png"));
18
16
  });
19
17
  };
@@ -1 +1 @@
1
- export default function createBrowser(): Promise<import("puppeteer-core").Browser>;
1
+ export default function createBrowser(): Promise<import("playwright-core").Browser>;
@@ -1,9 +1,9 @@
1
- import edgeChromium from "chrome-aws-lambda";
2
- import puppeteer from "puppeteer-core";
1
+ import awsChromium from "chrome-aws-lambda";
2
+ import { chromium } from "playwright-core";
3
3
  export default async function createBrowser() {
4
- return puppeteer.launch({
5
- args: edgeChromium.args,
6
- executablePath: await edgeChromium.executablePath,
4
+ return await chromium.launch({
5
+ args: awsChromium.args,
6
+ executablePath: await awsChromium.executablePath,
7
7
  headless: true
8
8
  });
9
9
  }
@@ -0,0 +1,22 @@
1
+ import playwrightCore from "playwright-core";
2
+ export default async function createBrowser() {
3
+ try {
4
+ return await playwrightCore.chromium.launch({
5
+ headless: true
6
+ });
7
+ } catch (e) {
8
+ }
9
+ try {
10
+ const playwright = await import(String("playwright"));
11
+ return await playwright.chromium.launch({
12
+ headless: true
13
+ });
14
+ } catch (e) {
15
+ if (process.dev) {
16
+ console.warn("Failed to load chromium instance. Ensure you have chrome installed, otherwise add the dependency: `npm add -D playwright`.");
17
+ } else {
18
+ console.error("Failed to load browser instance. Please open an issue with the exception: https://github.com/harlan-zw/nuxt-og-image/issues.");
19
+ throw e;
20
+ }
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export default function createBrowser(): Promise<any>;
@@ -0,0 +1,4 @@
1
+ import type { ResvgRenderOptions } from '@resvg/resvg-wasm';
2
+ export default function (svg: string, options: ResvgRenderOptions & {
3
+ baseUrl: string;
4
+ }): Promise<Uint8Array>;
@@ -0,0 +1,11 @@
1
+ import { Resvg, initWasm } from "@resvg/resvg-wasm";
2
+ import { wasmLoader } from "../../utils.mjs";
3
+ const ReSvgLoader = wasmLoader("/* NUXT_OG_IMAGE_RESVG_WASM */", "/resvg.wasm");
4
+ export default async function(svg, options) {
5
+ const ReSvgWasm = await ReSvgLoader.load({ baseUrl: options.baseUrl });
6
+ await initWasm(ReSvgWasm).catch(() => {
7
+ });
8
+ const resvgJS = new Resvg(svg, {});
9
+ const pngData = resvgJS.render();
10
+ return pngData.asPng();
11
+ }
@@ -0,0 +1,11 @@
1
+ import { initialize, svg2png } from "svg2png-wasm";
2
+ import { wasmLoader } from "../../utils.mjs";
3
+ const Svg2PngLoader = wasmLoader("/* NUXT_OG_IMAGE_SVG2PNG_WASM */", "/svg2png.wasm");
4
+ export default async function(svg, options) {
5
+ const Svg2PngWasm = await Svg2PngLoader.load({ baseUrl: options.baseUrl });
6
+ await initialize(Svg2PngWasm).catch((e) => {
7
+ if (!e.message.trim().endsWith("function can be used only once."))
8
+ throw e;
9
+ });
10
+ return await svg2png(svg, options);
11
+ }