nuxt-og-image 2.0.0-beta.6 → 2.0.0-beta.60

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