nuxt-og-image 2.0.0-beta.7 → 2.0.0-beta.70

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 (117) hide show
  1. package/README.md +312 -97
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.78433ecd.js +1 -0
  5. package/dist/client/_nuxt/IconCSS.b41b9663.css +1 -0
  6. package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
  7. package/dist/client/_nuxt/ImageLoader.b930015f.js +1 -0
  8. package/dist/client/_nuxt/entry.1311cc29.css +1 -0
  9. package/dist/client/_nuxt/entry.212e85d9.js +143 -0
  10. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.0875e3f6.js} +1 -1
  11. package/dist/client/_nuxt/error-404.f3dd5020.css +1 -0
  12. package/dist/client/_nuxt/error-500.06915589.css +1 -0
  13. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.f06aa28f.js} +1 -1
  14. package/dist/client/_nuxt/index.9dc826bf.js +1 -0
  15. package/dist/client/_nuxt/index.ffbea0a9.css +1 -0
  16. package/dist/client/_nuxt/options.f2b6402e.js +1 -0
  17. package/dist/client/_nuxt/png.04e14eef.js +1 -0
  18. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.a8b6534b.js} +1 -1
  19. package/dist/client/_nuxt/svg.9ac05c0d.js +1 -0
  20. package/dist/client/_nuxt/vnodes.c25d5717.js +1 -0
  21. package/dist/client/index.html +2 -2
  22. package/dist/client/options/index.html +2 -2
  23. package/dist/client/png/index.html +2 -2
  24. package/dist/client/svg/index.html +2 -2
  25. package/dist/client/vnodes/index.html +2 -2
  26. package/dist/module.d.ts +101 -11
  27. package/dist/module.json +2 -2
  28. package/dist/module.mjs +394 -134
  29. package/dist/runtime/browserUtil.d.ts +1 -0
  30. package/dist/runtime/browserUtil.mjs +10 -5
  31. package/dist/runtime/components/{OgImageDynamic.d.ts → OgImage/Cached.d.ts} +2 -2
  32. package/dist/runtime/components/OgImage/Cached.mjs +10 -0
  33. package/dist/runtime/components/{OgImageScreenshot.d.ts → OgImage/Screenshot.d.ts} +2 -2
  34. package/dist/runtime/components/{OgImageScreenshot.mjs → OgImage/Screenshot.mjs} +2 -2
  35. package/dist/runtime/components/OgImage/WithoutCache.d.ts +4 -0
  36. package/dist/runtime/components/OgImage/WithoutCache.mjs +10 -0
  37. package/dist/runtime/components/OgImage/_OgImageDynamic.d.ts +8 -0
  38. package/dist/runtime/components/{OgImageDynamic.mjs → OgImage/_OgImageDynamic.mjs} +3 -3
  39. package/dist/runtime/components/OgImage/_OgImageStatic.d.ts +8 -0
  40. package/dist/runtime/components/{OgImageStatic.mjs → OgImage/_OgImageStatic.mjs} +3 -3
  41. package/dist/runtime/components/{OgImageStatic.d.ts → OgImage/index.d.ts} +2 -2
  42. package/dist/runtime/components/OgImage/index.mjs +10 -0
  43. package/dist/runtime/components/OgImageTemplate/Fallback.vue +170 -0
  44. package/dist/runtime/composables/defineOgImage.d.ts +12 -4
  45. package/dist/runtime/composables/defineOgImage.mjs +31 -49
  46. package/dist/runtime/composables/util.d.ts +2 -0
  47. package/dist/runtime/composables/util.mjs +26 -0
  48. package/dist/runtime/nitro/middleware/og.png.mjs +54 -8
  49. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  50. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  51. package/dist/runtime/nitro/plugins/prerender.mjs +28 -0
  52. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  53. package/dist/runtime/nitro/providers/browser/lambda.mjs +3 -3
  54. package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
  55. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  56. package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
  57. package/dist/runtime/nitro/providers/png/resvg-node.d.ts +4 -0
  58. package/dist/runtime/nitro/providers/png/resvg-node.mjs +6 -0
  59. package/dist/runtime/nitro/providers/png/resvg-wasm.d.ts +3 -0
  60. package/dist/runtime/nitro/providers/png/resvg-wasm.mjs +11 -0
  61. package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +2 -3
  62. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  63. package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +2 -3
  64. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  65. package/dist/runtime/nitro/renderers/browser.d.ts +2 -2
  66. package/dist/runtime/nitro/renderers/browser.mjs +14 -10
  67. package/dist/runtime/nitro/renderers/satori/index.d.ts +2 -2
  68. package/dist/runtime/nitro/renderers/satori/index.mjs +27 -32
  69. package/dist/runtime/nitro/renderers/satori/plugins/emojis.d.ts +1 -1
  70. package/dist/runtime/nitro/renderers/satori/plugins/emojis.mjs +19 -6
  71. package/dist/runtime/nitro/renderers/satori/plugins/encoding.d.ts +1 -1
  72. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +5 -7
  73. package/dist/runtime/nitro/renderers/satori/plugins/flex.d.ts +1 -1
  74. package/dist/runtime/nitro/renderers/satori/plugins/flex.mjs +8 -10
  75. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.d.ts +1 -1
  76. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +45 -13
  77. package/dist/runtime/nitro/renderers/satori/plugins/twClasses.d.ts +1 -1
  78. package/dist/runtime/nitro/renderers/satori/plugins/twClasses.mjs +5 -7
  79. package/dist/runtime/nitro/renderers/satori/utils.d.ts +4 -5
  80. package/dist/runtime/nitro/renderers/satori/utils.mjs +28 -17
  81. package/dist/runtime/nitro/routes/debug.d.ts +8 -0
  82. package/dist/runtime/nitro/routes/debug.mjs +14 -0
  83. package/dist/runtime/nitro/routes/font.mjs +2 -2
  84. package/dist/runtime/nitro/routes/html.mjs +102 -27
  85. package/dist/runtime/nitro/routes/options.d.ts +2 -2
  86. package/dist/runtime/nitro/routes/options.mjs +25 -22
  87. package/dist/runtime/nitro/routes/svg.mjs +5 -3
  88. package/dist/runtime/nitro/routes/vnode.mjs +5 -3
  89. package/dist/runtime/nitro/utils-pure.d.ts +2 -2
  90. package/dist/runtime/nitro/utils-pure.mjs +1 -4
  91. package/dist/runtime/nitro/utils.d.ts +11 -11
  92. package/dist/runtime/nitro/utils.mjs +69 -54
  93. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  94. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  95. package/dist/types.d.ts +6 -0
  96. package/package.json +39 -27
  97. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  98. package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
  99. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  100. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
  101. package/dist/client/_nuxt/error-404.1469f10f.css +0 -1
  102. package/dist/client/_nuxt/error-500.92b94fae.css +0 -1
  103. package/dist/client/_nuxt/error-component.cf7543e5.js +0 -3
  104. package/dist/client/_nuxt/index.3f356409.js +0 -1
  105. package/dist/client/_nuxt/options.56a3e5f9.js +0 -1
  106. package/dist/client/_nuxt/png.37f3e77b.js +0 -1
  107. package/dist/client/_nuxt/svg.186c6bd1.js +0 -1
  108. package/dist/client/_nuxt/vnodes.a799f183.js +0 -1
  109. package/dist/runtime/components/OgImageBasic.island.vue +0 -92
  110. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
  111. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  112. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  113. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  114. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  115. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  116. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  117. /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
package/dist/module.mjs CHANGED
@@ -1,29 +1,34 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
2
+ import { useNuxt, addTemplate, defineNuxtModule, useLogger, createResolver, addServerHandler, addImports, addComponent, hasNuxtModule, 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
+ import { installNuxtSiteConfig, updateSiteConfig, requireSiteConfig } from 'nuxt-site-config-kit';
13
14
  import playwrightCore from 'playwright-core';
14
15
  import { existsSync } from 'node:fs';
15
16
  import { createBirpcGroup } from 'birpc';
16
17
  import { stringify, parse } from 'flatted';
18
+ import { addDependency } from 'nypm';
19
+ import { provider } from 'std-env';
17
20
 
18
21
  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) {
22
+ if (process.dev || process.env.prerender) {
23
+ try {
24
+ const { Launcher } = await import(String("chrome-launcher"));
25
+ const chromePath = Launcher.getFirstInstallation();
26
+ return await playwrightCore.chromium.launch({
27
+ headless: true,
28
+ executablePath: chromePath
29
+ });
30
+ } catch (e) {
31
+ }
27
32
  }
28
33
  try {
29
34
  return await playwrightCore.chromium.launch({
@@ -54,9 +59,9 @@ async function screenshot(browser, options) {
54
59
  width: options.width || 1200,
55
60
  height: options.height || 630
56
61
  });
57
- const isHtml = options.path.startsWith("html:") || options.html;
62
+ const isHtml = options.html || options.path?.startsWith("html:");
58
63
  if (isHtml) {
59
- const html = options.html || options.path.substring(5);
64
+ const html = options.html || options.path?.substring(5);
60
65
  await page.evaluate((html2) => {
61
66
  document.open("text/html");
62
67
  document.write(html2);
@@ -65,10 +70,13 @@ async function screenshot(browser, options) {
65
70
  await page.waitForLoadState("networkidle");
66
71
  } else {
67
72
  await page.goto(`${options.host}${options.path}`, {
68
- timeout: 1e4,
73
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500,
69
74
  waitUntil: "networkidle"
70
75
  });
71
76
  }
77
+ const screenshotOptions = {
78
+ timeout: process.env.prerender || process.dev ? 1e4 : 3500
79
+ };
72
80
  if (options.delay)
73
81
  await page.waitForTimeout(options.delay);
74
82
  if (options.mask) {
@@ -78,8 +86,10 @@ async function screenshot(browser, options) {
78
86
  }, options.mask);
79
87
  }
80
88
  if (options.selector)
81
- return await page.locator(options.selector).screenshot();
82
- return await page.screenshot();
89
+ return await page.locator(options.selector).screenshot(screenshotOptions);
90
+ const screenshot2 = await page.screenshot(screenshotOptions);
91
+ await page.close();
92
+ return screenshot2;
83
93
  }
84
94
 
85
95
  function setupPlaygroundRPC(nuxt, config) {
@@ -113,7 +123,7 @@ function setupPlaygroundRPC(nuxt, config) {
113
123
  const birpc = createBirpcGroup(serverFunctions, []);
114
124
  nuxt.hook("builder:watch", (e, path) => {
115
125
  if (e === "change")
116
- birpc.boardcast.refresh.asEvent(path);
126
+ birpc.broadcast.refresh.asEvent(path);
117
127
  });
118
128
  const middleware = async (req, res) => {
119
129
  if (req.ws) {
@@ -166,7 +176,7 @@ function getBodyJson(req) {
166
176
 
167
177
  function decodeHtml(html) {
168
178
  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) => {
169
- return String.fromCharCode(parseInt(int));
179
+ return String.fromCharCode(Number.parseInt(int));
170
180
  });
171
181
  }
172
182
  function decodeObjectHtmlEntities(obj) {
@@ -200,50 +210,204 @@ function extractOgImageOptions(html) {
200
210
  }
201
211
  return false;
202
212
  }
203
- function stripOgImageOptions(html) {
204
- return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
213
+
214
+ const SVG2PNGWasmPlaceholder = '"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"';
215
+ const YogaWasmPlaceholder = '"/* NUXT_OG_IMAGE_YOGA_WASM */"';
216
+ const ReSVGWasmPlaceholder = '"/* NUXT_OG_IMAGE_RESVG_WASM */"';
217
+ const Wasms = [
218
+ {
219
+ placeholder: SVG2PNGWasmPlaceholder,
220
+ path: "svg2png/svg2png.wasm",
221
+ file: "svg2png.wasm"
222
+ },
223
+ {
224
+ placeholder: ReSVGWasmPlaceholder,
225
+ path: "resvg/resvg.wasm",
226
+ file: "resvg.wasm"
227
+ },
228
+ {
229
+ placeholder: YogaWasmPlaceholder,
230
+ path: "yoga/yoga.wasm",
231
+ file: "yoga.wasm"
232
+ }
233
+ ];
234
+ const DefaultRuntimeCompatibility = {
235
+ // node-server runtime
236
+ browser: "playwright",
237
+ satori: "default",
238
+ wasm: "fetch",
239
+ png: "resvg-node"
240
+ };
241
+ const RuntimeCompatibility = {
242
+ "nitro-dev": {
243
+ wasm: "fetch",
244
+ browser: "universal"
245
+ },
246
+ "stackblitz": {
247
+ browser: false,
248
+ satori: "yoga-wasm",
249
+ wasm: "inline",
250
+ png: "resvg-wasm"
251
+ },
252
+ "netlify": {
253
+ browser: "lambda",
254
+ wasm: "inline"
255
+ },
256
+ "netlify-edge": {
257
+ wasm: "inline",
258
+ png: "resvg-wasm"
259
+ },
260
+ "vercel": {
261
+ // exceeds 50mb limit
262
+ browser: false
263
+ },
264
+ "vercel-edge": {
265
+ browser: false,
266
+ wasm: "import",
267
+ wasmImportQuery: "?module",
268
+ png: "resvg-wasm"
269
+ },
270
+ "cloudflare-pages": {
271
+ browser: false,
272
+ wasm: "import",
273
+ png: "resvg-wasm"
274
+ },
275
+ "cloudflare": {
276
+ browser: false,
277
+ wasm: "import"
278
+ }
279
+ };
280
+
281
+ const autodetectableProviders = {
282
+ azure_static: "azure",
283
+ cloudflare_pages: "cloudflare-pages",
284
+ netlify: "netlify",
285
+ stormkit: "stormkit",
286
+ vercel: "vercel",
287
+ cleavr: "cleavr",
288
+ stackblitz: "stackblitz"
289
+ };
290
+ const autodetectableStaticProviders = {
291
+ netlify: "netlify-static",
292
+ vercel: "vercel-static"
293
+ };
294
+ function detectTarget(options = {}) {
295
+ return options?.static ? autodetectableStaticProviders[provider] : autodetectableProviders[provider];
296
+ }
297
+ function getNitroPreset(nuxt) {
298
+ return process.env.NITRO_PRESET || nuxt.options.nitro.preset || detectTarget() || "node-server";
299
+ }
300
+ function getNitroProviderCompatibility(nuxt) {
301
+ if (provider === "stackblitz")
302
+ return defu(RuntimeCompatibility.stackblitz, DefaultRuntimeCompatibility);
303
+ if (nuxt.options.dev || nuxt.options._prepare || nuxt.options._generate) {
304
+ return defu({
305
+ wasm: "fetch",
306
+ browser: "universal"
307
+ }, DefaultRuntimeCompatibility);
308
+ }
309
+ const target = getNitroPreset(nuxt);
310
+ const compatibility = RuntimeCompatibility[target];
311
+ if (compatibility === false)
312
+ return false;
313
+ return defu(compatibility || {}, DefaultRuntimeCompatibility);
314
+ }
315
+ function ensureDependencies(nuxt, dep) {
316
+ return Promise.all(dep.map((d) => {
317
+ return addDependency(d, { cwd: nuxt.options.rootDir });
318
+ }));
319
+ }
320
+
321
+ function extendTypes(module, template) {
322
+ const nuxt = useNuxt();
323
+ addTemplate({
324
+ filename: `${module}.d.ts`,
325
+ getContents: () => {
326
+ const s = template();
327
+ return `// Generated by ${module}
328
+ ${s}
329
+ export {}
330
+ `;
331
+ }
332
+ });
333
+ nuxt.hooks.hook("prepare:types", ({ references }) => {
334
+ references.push({ path: resolve(nuxt.options.buildDir, `${module}.d.ts`) });
335
+ });
205
336
  }
206
337
 
207
338
  const PATH = "/__nuxt_og_image__";
208
339
  const PATH_ENTRY = `${PATH}/entry`;
209
340
  const PATH_PLAYGROUND = `${PATH}/client`;
210
- const edgeProvidersSupported = [
211
- "cloudflare",
212
- "vercel-edge",
213
- "netlify-edge"
214
- ];
215
341
  const module = defineNuxtModule({
216
342
  meta: {
217
343
  name: "nuxt-og-image",
218
344
  compatibility: {
219
- nuxt: "^3.3.1",
345
+ nuxt: "^3.6.1",
220
346
  bridge: false
221
347
  },
222
348
  configKey: "ogImage"
223
349
  },
224
350
  defaults(nuxt) {
225
- const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
226
351
  return {
227
- // when we run `nuxi generate` we need to force prerendering
228
- forcePrerender: !nuxt.options.dev && nuxt.options._generate,
229
- siteUrl,
352
+ enabled: true,
230
353
  defaults: {
231
- component: "OgImageBasic",
354
+ component: "OgImageTemplateFallback",
232
355
  width: 1200,
233
- height: 630
356
+ height: 630,
357
+ // default is to cache the image for 24 hours
358
+ cache: true,
359
+ cacheTtl: 24 * 60 * 60 * 1e3
234
360
  },
235
- satoriProvider: true,
236
- browserProvider: true,
361
+ componentDirs: ["OgImage", "OgImageTemplate"],
362
+ runtimeSatori: true,
363
+ runtimeBrowser: nuxt.options.dev,
237
364
  fonts: [],
365
+ runtimeCacheStorage: true,
238
366
  satoriOptions: {},
239
- experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
240
- experimentalRuntimeBrowser: false,
241
- playground: process.env.NODE_ENV === "development" || nuxt.options.dev
367
+ playground: process.env.NODE_ENV === "development" || nuxt.options.dev,
368
+ debug: false
242
369
  };
243
370
  },
244
371
  async setup(config, nuxt) {
372
+ const logger = useLogger("nuxt-og-image");
373
+ logger.level = config.debug || nuxt.options.debug ? 4 : 3;
374
+ if (config.enabled === false) {
375
+ logger.debug("The module is disabled, skipping setup.");
376
+ return;
377
+ }
245
378
  const { resolve } = createResolver(import.meta.url);
246
- config.siteUrl = config.siteUrl || config.host;
379
+ logger.debug("Using Nitro preset", getNitroPreset(nuxt));
380
+ const nitroCompatibility = getNitroProviderCompatibility(nuxt);
381
+ logger.debug("Nitro compatibility", nitroCompatibility);
382
+ const nitroTarget = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
383
+ if (!nitroCompatibility) {
384
+ logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\`. Please make an issue. `);
385
+ return;
386
+ }
387
+ if (!nitroCompatibility.browser && config.runtimeBrowser) {
388
+ config.runtimeBrowser = false;
389
+ logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\` with the runtime browser. Set runtimeBrowser: false to stop seeing this.`);
390
+ }
391
+ if (config.runtimeBrowser && nitroCompatibility.browser === "lambda") {
392
+ logger.info(`\`nuxt-og-image\` is deploying to nitro target \`${nitroTarget}\` that installs extra dependencies.`);
393
+ await ensureDependencies(nuxt, ["puppeteer-core@14.1.1", "@sparticuz/chrome-aws-lambda@14.1.1"]);
394
+ }
395
+ await installNuxtSiteConfig();
396
+ await updateSiteConfig({
397
+ _context: "nuxt-og-image:config",
398
+ url: config.siteUrl || config.host
399
+ });
400
+ requireSiteConfig("nuxt-og-image", {
401
+ url: "Required to generate absolute URLs for the og:image."
402
+ }, { prerender: true });
403
+ nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
404
+ if (nuxt.options._generate) {
405
+ nuxt.options.nitro.storage["og-image"] = {
406
+ driver: "memory"
407
+ };
408
+ } else if (config.runtimeCacheStorage && !nuxt.options.dev && typeof config.runtimeCacheStorage === "object") {
409
+ nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
410
+ }
247
411
  if (!config.fonts.length)
248
412
  config.fonts = ["Inter:400", "Inter:700"];
249
413
  const distResolve = (p) => {
@@ -253,34 +417,27 @@ const module = defineNuxtModule({
253
417
  return resolve(`../dist/${p}`);
254
418
  };
255
419
  nuxt.options.experimental.componentIslands = true;
256
- addTemplate({
257
- filename: "nuxt-og-image.d.ts",
258
- getContents: () => {
259
- return `// Generated by nuxt-og-image
260
- interface NuxtOgImageNitroRules {
420
+ extendTypes("nuxt-og-image", () => {
421
+ return `interface NuxtOgImageNitroRules {
261
422
  ogImage?: false | Record<string, any>
262
423
  }
263
424
  declare module 'nitropack' {
264
425
  interface NitroRouteRules extends NuxtOgImageNitroRules {}
265
426
  interface NitroRouteConfig extends NuxtOgImageNitroRules {}
266
- }
267
- export {}
268
- `;
269
- }
270
- });
271
- nuxt.hooks.hook("prepare:types", ({ references }) => {
272
- references.push({ path: resolve(nuxt.options.buildDir, "nuxt-og-image.d.ts") });
427
+ }`;
273
428
  });
274
429
  addServerHandler({
275
430
  lazy: true,
276
431
  handler: resolve("./runtime/nitro/middleware/og.png")
277
432
  });
278
- ["html", "options", "svg", "vnode", "font"].forEach((type) => {
279
- addServerHandler({
280
- lazy: true,
281
- route: `/api/og-image-${type}`,
282
- handler: resolve(`./runtime/nitro/routes/${type}`)
283
- });
433
+ ["html", "options", "svg", "vnode", "font", "debug"].forEach((type) => {
434
+ if (type !== "debug" || config.debug) {
435
+ addServerHandler({
436
+ lazy: true,
437
+ route: `/api/og-image-${type}`,
438
+ handler: resolve(`./runtime/nitro/routes/${type}`)
439
+ });
440
+ }
284
441
  });
285
442
  nuxt.hook("devtools:customTabs", (iframeTabs) => {
286
443
  iframeTabs.push({
@@ -309,7 +466,17 @@ export {}
309
466
  });
310
467
  }
311
468
  nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = [];
312
- ["defineOgImageDynamic", "defineOgImageStatic", "defineOgImageScreenshot"].forEach((name) => {
469
+ [
470
+ // deprecated
471
+ "Dynamic",
472
+ "Static",
473
+ // new
474
+ "index",
475
+ "Cached",
476
+ "WithoutCache",
477
+ "Screenshot"
478
+ ].forEach((name) => {
479
+ name = name === "index" ? "defineOgImage" : `defineOgImage${name}`;
313
480
  addImports({
314
481
  name,
315
482
  from: resolve("./runtime/composables/defineOgImage")
@@ -317,104 +484,198 @@ export {}
317
484
  nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"].push(name);
318
485
  });
319
486
  await addComponent({
320
- name: "OgImageBasic",
321
- filePath: resolve("./runtime/components/OgImageBasic.island.vue"),
487
+ name: "OgImageTemplateFallback",
488
+ filePath: resolve("./runtime/components/OgImageTemplate/Fallback.vue"),
322
489
  island: true
323
490
  });
324
- ["OgImageStatic", "OgImageDynamic", "OgImageScreenshot"].forEach((name) => {
491
+ [
492
+ // deprecated
493
+ "Static",
494
+ "Dynamic",
495
+ // new
496
+ "index",
497
+ "Cached",
498
+ "WithoutCache",
499
+ "Screenshot"
500
+ ].forEach((name) => {
325
501
  addComponent({
326
- name,
327
- filePath: resolve(`./runtime/components/${name}`)
502
+ global: hasNuxtModule("@nuxt/content"),
503
+ name: name === "index" ? "OgImage" : `OgImage${name}`,
504
+ filePath: resolve(`./runtime/components/OgImage/${name}`)
505
+ });
506
+ });
507
+ const ogImageComponents = [];
508
+ nuxt.hook("components:extend", (components) => {
509
+ components.forEach((component) => {
510
+ let valid = false;
511
+ config.componentDirs.forEach((dir) => {
512
+ if (component.pascalName.startsWith(dir) || component.kebabName.startsWith(dir))
513
+ valid = true;
514
+ });
515
+ if (valid) {
516
+ component.island = true;
517
+ component.mode = "server";
518
+ ogImageComponents.push({ pascalName: component.pascalName, kebabName: component.kebabName });
519
+ }
328
520
  });
329
521
  });
522
+ addTemplate({
523
+ filename: "og-image-component-names.mjs",
524
+ getContents() {
525
+ return `export const componentNames = ${JSON.stringify(ogImageComponents)}`;
526
+ },
527
+ options: { mode: "server" }
528
+ });
330
529
  const runtimeDir = resolve("./runtime");
331
530
  nuxt.options.build.transpile.push(runtimeDir);
332
- const moduleAssetDir = resolve("./runtime/public-assets");
333
- const assetDirs = [
334
- resolve(nuxt.options.rootDir, nuxt.options.dir.public),
335
- moduleAssetDir
531
+ addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
532
+ const customAssetDirs = [
533
+ // allows us to show custom error images
534
+ resolve("./runtime/public-assets")
336
535
  ];
536
+ if (config.runtimeSatori) {
537
+ if (config.fonts.includes("Inter:400"))
538
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/inter-font"));
539
+ if (nitroCompatibility.png === "resvg-wasm" && nitroCompatibility.wasm === "fetch")
540
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/resvg"));
541
+ else if (nitroCompatibility.png === "svg2png" && nitroCompatibility.wasm === "fetch")
542
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/svg2png"));
543
+ if (nitroCompatibility.satori === "yoga-wasm")
544
+ customAssetDirs.push(resolve("./runtime/public-assets-optional/yoga"));
545
+ }
337
546
  nuxt.hooks.hook("modules:done", async () => {
338
547
  nuxt.hooks.callHook("og-image:config", config);
339
- nuxt.options.runtimeConfig["nuxt-og-image"] = { ...config, assetDirs };
548
+ nuxt.options.runtimeConfig["nuxt-og-image"] = {
549
+ satoriOptions: config.satoriOptions,
550
+ runtimeSatori: config.runtimeSatori,
551
+ runtimeBrowser: config.runtimeBrowser,
552
+ // @ts-expect-error runtime type
553
+ defaults: config.defaults,
554
+ // avoid adding credentials
555
+ runtimeCacheStorage: typeof config.runtimeCacheStorage === "boolean" ? "default" : config.runtimeCacheStorage.driver,
556
+ // convert the fonts to uniform type to fix ts issue
557
+ fonts: config.fonts.map((f) => {
558
+ if (typeof f === "string") {
559
+ const [name, weight] = f.split(":");
560
+ return {
561
+ name,
562
+ weight
563
+ };
564
+ }
565
+ return f;
566
+ }),
567
+ assetDirs: [
568
+ resolve(nuxt.options.srcDir, nuxt.options.dir.public),
569
+ ...customAssetDirs,
570
+ // always add runtime dirs for prerendering to work, these are just used as scan roots
571
+ resolve("./runtime/public-assets-optional/inter-font"),
572
+ resolve("./runtime/public-assets-optional/resvg"),
573
+ resolve("./runtime/public-assets-optional/yoga"),
574
+ resolve("./runtime/public-assets-optional/svg2png")
575
+ ]
576
+ };
340
577
  });
341
- const useSatoriWasm = provider === "stackblitz";
342
578
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
343
579
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
344
580
  inline: [runtimeDir]
345
581
  });
346
- if (config.experimentalRuntimeBrowser) {
582
+ if (config.runtimeBrowser) {
347
583
  nitroConfig.alias = nitroConfig.alias || {};
348
584
  nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
349
585
  nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
350
586
  nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
351
587
  }
352
588
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
353
- nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
589
+ customAssetDirs.forEach((dir) => {
590
+ nitroConfig.publicAssets.push({ dir, maxAge: 31536e3 });
591
+ });
354
592
  const providerPath = `${runtimeDir}/nitro/providers`;
355
- if (config.browserProvider) {
356
- nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || config.experimentalRuntimeBrowser ? `
357
- import node from '${providerPath}/browser/node'
358
-
593
+ if (config.runtimeBrowser) {
594
+ nitroConfig.virtual["#nuxt-og-image/browser"] = `
595
+ let browser
359
596
  export default async function() {
360
- return node
361
- }
362
- ` : `export default async function() {
363
- return () => {}
597
+ browser = browser || await import('${providerPath}/browser/${nitroCompatibility.browser}').then((m) => m.default || m)
598
+ return browser
364
599
  }
365
600
  `;
366
601
  }
367
- if (config.satoriProvider) {
368
- nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${useSatoriWasm ? "webworker" : "node"}'
369
- export default async function() {
602
+ if (config.runtimeSatori) {
603
+ nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${nitroCompatibility.satori}'
604
+ export default function() {
370
605
  return satori
371
606
  }`;
372
- nitroConfig.virtual["#nuxt-og-image/svg2png"] = `
373
- import svg2png from '${providerPath}/svg2png/universal'
374
- export default async function() {
375
- return svg2png
376
- }`;
607
+ nitroConfig.virtual["#nuxt-og-image/png"] = `import png from '${providerPath}/png/${nitroCompatibility.png}'
608
+ export default function() {
609
+ return png
610
+ }
611
+ `;
377
612
  }
378
613
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
379
- import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
380
- import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
614
+ ${config.runtimeSatori ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
615
+ ${config.runtimeBrowser ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
381
616
 
382
617
  export async function useProvider(provider) {
383
618
  if (provider === 'satori')
384
- return satori
619
+ return ${config.runtimeSatori ? "satori" : "null"}
385
620
  if (provider === 'browser')
386
- return browser
621
+ return ${config.runtimeBrowser ? "browser" : "null"}
622
+ return null
387
623
  }
388
624
  `;
389
625
  });
390
626
  nuxt.hooks.hook("nitro:init", async (nitro) => {
391
627
  let screenshotQueue = [];
392
628
  nitro.hooks.hook("compiled", async (_nitro) => {
393
- if (edgeProvidersSupported.includes(_nitro.options.preset)) {
394
- await copy(resolve("./runtime/public-assets/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
395
- await copy(resolve("./runtime/public-assets/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
396
- if (!config.experimentalInlineWasm) {
397
- await copy(resolve("./runtime/public-assets/svg2png.wasm"), resolve(_nitro.options.output.serverDir, "svg2png.wasm"));
398
- if (useSatoriWasm)
399
- await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
629
+ if (!config.runtimeSatori || nuxt.options.dev)
630
+ return;
631
+ if (config.fonts.includes("Inter:400"))
632
+ 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"));
633
+ if (config.fonts.includes("Inter:700"))
634
+ 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"));
635
+ const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
636
+ const wasmProviderPath = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
637
+ const paths = [wasmProviderPath];
638
+ const chunks = await globby([`${_nitro.options.output.serverDir}/chunks/**/*.mjs`], { absolute: true });
639
+ paths.push(...chunks);
640
+ for (const path of paths) {
641
+ if (!await pathExists(path))
642
+ continue;
643
+ let contents = await readFile(path, "utf-8");
644
+ let updated = false;
645
+ if (_nitro.options.preset.includes("vercel") && path === wasmProviderPath) {
646
+ contents = contents.replace(".cwd(),", '?.cwd || "/",');
647
+ updated = true;
400
648
  }
401
- const indexFile = resolve(_nitro.options.output.serverDir, _nitro.options.preset === "netlify-edge" ? "server.mjs" : "index.mjs");
402
- if (await pathExists(indexFile)) {
403
- const indexContents = (await readFile(indexFile, "utf-8")).replace(".cwd(),", '?.cwd || "/",');
404
- if (!config.experimentalInlineWasm) {
405
- await writeFile(
406
- indexFile,
407
- 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)')
408
- );
409
- } else {
410
- const svg2pngWasm = await readFile(resolve("./runtime/public-assets/svg2png.wasm"), "base64");
411
- const yogaWasm = await readFile(resolve("./runtime/public-assets/yoga.wasm"), "base64");
412
- await writeFile(
413
- indexFile,
414
- indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', `Buffer.from("${svg2pngWasm}", "base64")`).replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', `Buffer.from("${yogaWasm}", "base64")`)
415
- );
649
+ if (_nitro.options.preset.includes("netlify") && path.endsWith("netlify.mjs")) {
650
+ const match = "// TODO: handle event.isBase64Encoded\n });";
651
+ contents = contents.replace(match, `${match}
652
+
653
+ const headers = normalizeOutgoingHeaders(r.headers);
654
+ // image buffers must be base64 encoded
655
+ if (Buffer.isBuffer(r.body) && headers["content-type"].startsWith("image/")) {
656
+ return {
657
+ statusCode: r.status,
658
+ headers,
659
+ body: r.body.toString("base64"),
660
+ isBase64Encoded: true
661
+ };
662
+ }`);
663
+ updated = true;
664
+ }
665
+ for (const wasm of Wasms) {
666
+ if (contents.includes(wasm.placeholder)) {
667
+ if (nitroCompatibility.wasm === "import") {
668
+ contents = contents.replace(wasm.placeholder, `import("./${wasm.file}${nitroCompatibility.wasmImportQuery || ""}").then(m => m.default || m)`);
669
+ await copy(resolve(`./runtime/public-assets-optional/${wasm.path}`), resolve(dirname(path), wasm.file));
670
+ } else if (nitroCompatibility.wasm === "inline") {
671
+ const wasmBuffer = await readFile(resolve(`./runtime/public-assets-optional/${wasm.path}`));
672
+ contents = contents.replace(wasm.placeholder, `Buffer.from("${wasmBuffer}", "base64")`);
673
+ }
674
+ updated = true;
416
675
  }
417
676
  }
677
+ if (updated)
678
+ await writeFile(path, contents, { encoding: "utf-8" });
418
679
  }
419
680
  });
420
681
  const _routeRulesMatcher = toRouteMatcher(
@@ -427,7 +688,6 @@ export async function useProvider(provider) {
427
688
  if (!html)
428
689
  return;
429
690
  const extractedOptions = extractOgImageOptions(html);
430
- ctx.contents = stripOgImageOptions(html);
431
691
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
432
692
  if (!extractedOptions || routeRules.ogImage === false)
433
693
  return;
@@ -437,7 +697,7 @@ export async function useProvider(provider) {
437
697
  ...extractedOptions,
438
698
  ...routeRules.ogImage || {}
439
699
  };
440
- if ((nuxt.options._generate || entry.static) && entry.provider === "browser")
700
+ if ((nuxt.options._generate || entry.cache) && entry.provider === "browser")
441
701
  screenshotQueue.push(entry);
442
702
  });
443
703
  if (nuxt.options.dev)
@@ -446,21 +706,6 @@ export async function useProvider(provider) {
446
706
  await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
447
707
  if (screenshotQueue.length === 0)
448
708
  return;
449
- for (const entry of screenshotQueue) {
450
- if (entry.route && Object.keys(entry).length === 1) {
451
- const html = await $fetch(entry.route);
452
- const extractedOptions = extractOgImageOptions(html);
453
- const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
454
- Object.assign(entry, {
455
- // @ts-expect-error runtime
456
- path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
457
- ...extractedOptions,
458
- ...routeRules.ogImage || {}
459
- });
460
- }
461
- if (entry.component)
462
- entry.html = await $fetch(entry.path);
463
- }
464
709
  nitro.logger.info("Ensuring chromium install for og:image generation...");
465
710
  const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
466
711
  stdio: "inherit"
@@ -487,12 +732,27 @@ export async function useProvider(provider) {
487
732
  browser = await createBrowser();
488
733
  if (browser) {
489
734
  nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
735
+ for (const entry of screenshotQueue) {
736
+ if (entry.route && Object.keys(entry).length === 1) {
737
+ const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
738
+ const extractedOptions = extractOgImageOptions(html);
739
+ const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
740
+ Object.assign(entry, {
741
+ // @ts-expect-error runtime
742
+ path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
743
+ ...extractedOptions,
744
+ ...routeRules.ogImage || {}
745
+ });
746
+ }
747
+ if (entry.component)
748
+ entry.html = await globalThis.$fetch(entry.path);
749
+ }
490
750
  for (const k in screenshotQueue) {
491
751
  const entry = screenshotQueue[k];
492
752
  const start = Date.now();
493
753
  let hasError = false;
494
- const dirname = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
495
- const filename = joinURL(dirname, "/og.png");
754
+ const dirname2 = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
755
+ const filename = joinURL(dirname2, "/og.png");
496
756
  try {
497
757
  const imgBuffer = await screenshot(browser, {
498
758
  ...config.defaults || {},
@@ -500,7 +760,7 @@ export async function useProvider(provider) {
500
760
  host
501
761
  });
502
762
  try {
503
- await mkdirp(dirname);
763
+ await mkdirp(dirname2);
504
764
  } catch (e) {
505
765
  }
506
766
  await writeFile(filename, imgBuffer);
@@ -524,7 +784,7 @@ export async function useProvider(provider) {
524
784
  }
525
785
  screenshotQueue = [];
526
786
  };
527
- if (!nuxt.options._prepare) {
787
+ if (nuxt.options._generate) {
528
788
  nitro.hooks.hook("rollup:before", async () => {
529
789
  await captureScreenshots();
530
790
  });