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

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 (50) hide show
  1. package/README.md +96 -35
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.ecebf6a5.js +1 -0
  5. package/dist/client/_nuxt/{ImageLoader.9bf39d71.js → ImageLoader.97400b2b.js} +1 -1
  6. package/dist/client/_nuxt/entry.7e98a020.css +1 -0
  7. package/dist/client/_nuxt/entry.c8bf4454.js +5 -0
  8. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.7660b68a.js} +1 -1
  9. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.776f22a1.js} +1 -1
  10. package/dist/client/_nuxt/{error-component.cf7543e5.js → error-component.d4668032.js} +2 -2
  11. package/dist/client/_nuxt/{index.3f356409.js → index.77081a6c.js} +1 -1
  12. package/dist/client/_nuxt/{options.56a3e5f9.js → options.b6164a5b.js} +1 -1
  13. package/dist/client/_nuxt/{png.37f3e77b.js → png.b914f6c4.js} +1 -1
  14. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.ba10b978.js} +1 -1
  15. package/dist/client/_nuxt/{svg.186c6bd1.js → svg.1e41877e.js} +1 -1
  16. package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.a857bced.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 +0 -1
  23. package/dist/module.json +1 -1
  24. package/dist/module.mjs +72 -60
  25. package/dist/runtime/browserUtil.d.ts +1 -0
  26. package/dist/runtime/browserUtil.mjs +7 -4
  27. package/dist/runtime/composables/defineOgImage.mjs +6 -7
  28. package/dist/runtime/nitro/middleware/og.png.mjs +17 -2
  29. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  30. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  31. package/dist/runtime/nitro/plugins/prerender.mjs +20 -0
  32. package/dist/runtime/nitro/providers/browser/node.mjs +10 -8
  33. package/dist/runtime/nitro/providers/svg2png/universal.mjs +5 -2
  34. package/dist/runtime/nitro/renderers/browser.mjs +6 -1
  35. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
  36. package/dist/runtime/nitro/renderers/satori/utils.mjs +1 -0
  37. package/dist/runtime/nitro/routes/html.mjs +11 -8
  38. package/dist/runtime/nitro/routes/options.mjs +6 -3
  39. package/dist/runtime/nitro/routes/svg.mjs +3 -1
  40. package/dist/runtime/nitro/routes/vnode.mjs +3 -1
  41. package/dist/runtime/nitro/util-hostname.d.ts +2 -0
  42. package/dist/runtime/nitro/util-hostname.mjs +15 -0
  43. package/dist/runtime/nitro/utils-pure.d.ts +3 -2
  44. package/dist/runtime/nitro/utils-pure.mjs +16 -13
  45. package/dist/runtime/nitro/utils.d.ts +3 -2
  46. package/dist/runtime/nitro/utils.mjs +9 -17
  47. package/package.json +19 -18
  48. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  49. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  50. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
package/dist/module.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
2
+ import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent, addServerPlugin } from '@nuxt/kit';
3
3
  import { execa } from 'execa';
4
4
  import chalk from 'chalk';
5
5
  import defu from 'defu';
6
6
  import { toRouteMatcher, createRouter } from 'radix3';
7
- import { joinURL } from 'ufo';
7
+ import { withBase, joinURL } from 'ufo';
8
8
  import { resolve, relative } from 'pathe';
9
9
  import { tinyws } from 'tinyws';
10
10
  import sirv from 'sirv';
@@ -16,14 +16,16 @@ import { createBirpcGroup } from 'birpc';
16
16
  import { stringify, parse } from 'flatted';
17
17
 
18
18
  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) {
19
+ if (process.dev || process.env.prerender) {
20
+ try {
21
+ const { Launcher } = await import(String("chrome-launcher"));
22
+ const chromePath = Launcher.getFirstInstallation();
23
+ return await playwrightCore.chromium.launch({
24
+ headless: true,
25
+ executablePath: chromePath
26
+ });
27
+ } catch (e) {
28
+ }
27
29
  }
28
30
  try {
29
31
  return await playwrightCore.chromium.launch({
@@ -54,9 +56,9 @@ async function screenshot(browser, options) {
54
56
  width: options.width || 1200,
55
57
  height: options.height || 630
56
58
  });
57
- const isHtml = options.path.startsWith("html:") || options.html;
59
+ const isHtml = options.html || options.path?.startsWith("html:");
58
60
  if (isHtml) {
59
- const html = options.html || options.path.substring(5);
61
+ const html = options.html || options.path?.substring(5);
60
62
  await page.evaluate((html2) => {
61
63
  document.open("text/html");
62
64
  document.write(html2);
@@ -69,6 +71,9 @@ async function screenshot(browser, options) {
69
71
  waitUntil: "networkidle"
70
72
  });
71
73
  }
74
+ const screenshotOptions = {
75
+ timeout: 1e4
76
+ };
72
77
  if (options.delay)
73
78
  await page.waitForTimeout(options.delay);
74
79
  if (options.mask) {
@@ -78,8 +83,8 @@ async function screenshot(browser, options) {
78
83
  }, options.mask);
79
84
  }
80
85
  if (options.selector)
81
- return await page.locator(options.selector).screenshot();
82
- return await page.screenshot();
86
+ return await page.locator(options.selector).screenshot(screenshotOptions);
87
+ return await page.screenshot(screenshotOptions);
83
88
  }
84
89
 
85
90
  function setupPlaygroundRPC(nuxt, config) {
@@ -113,7 +118,7 @@ function setupPlaygroundRPC(nuxt, config) {
113
118
  const birpc = createBirpcGroup(serverFunctions, []);
114
119
  nuxt.hook("builder:watch", (e, path) => {
115
120
  if (e === "change")
116
- birpc.boardcast.refresh.asEvent(path);
121
+ birpc.broadcast.refresh.asEvent(path);
117
122
  });
118
123
  const middleware = async (req, res) => {
119
124
  if (req.ws) {
@@ -164,11 +169,15 @@ function getBodyJson(req) {
164
169
  });
165
170
  }
166
171
 
167
- function decodeHtmlEntities(obj) {
172
+ function decodeHtml(html) {
173
+ 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) => {
174
+ return String.fromCharCode(parseInt(int));
175
+ });
176
+ }
177
+ function decodeObjectHtmlEntities(obj) {
168
178
  Object.entries(obj).forEach(([key, value]) => {
169
- if (typeof value === "string") {
170
- obj[key] = value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
171
- }
179
+ if (typeof value === "string")
180
+ obj[key] = decodeHtml(value);
172
181
  });
173
182
  return obj;
174
183
  }
@@ -185,24 +194,24 @@ function extractOgImageOptions(html) {
185
194
  console.warn("Failed to parse #nuxt-og-image-options", e, options);
186
195
  }
187
196
  if (options) {
188
- const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
189
- if (description)
190
- options.description = description;
191
- else
192
- options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
193
- return decodeHtmlEntities(options);
197
+ if (!options.description) {
198
+ const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
199
+ if (description)
200
+ options.description = description;
201
+ else
202
+ options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
203
+ }
204
+ return decodeObjectHtmlEntities(options);
194
205
  }
195
206
  return false;
196
207
  }
197
- function stripOgImageOptions(html) {
198
- return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
199
- }
200
208
 
201
209
  const PATH = "/__nuxt_og_image__";
202
210
  const PATH_ENTRY = `${PATH}/entry`;
203
211
  const PATH_PLAYGROUND = `${PATH}/client`;
204
212
  const edgeProvidersSupported = [
205
213
  "cloudflare",
214
+ "cloudflare-pages",
206
215
  "vercel-edge",
207
216
  "netlify-edge"
208
217
  ];
@@ -217,6 +226,7 @@ const module = defineNuxtModule({
217
226
  },
218
227
  defaults(nuxt) {
219
228
  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);
220
230
  return {
221
231
  // when we run `nuxi generate` we need to force prerendering
222
232
  forcePrerender: !nuxt.options.dev && nuxt.options._generate,
@@ -227,11 +237,11 @@ const module = defineNuxtModule({
227
237
  height: 630
228
238
  },
229
239
  satoriProvider: true,
230
- browserProvider: true,
240
+ // disable browser in edge environments
241
+ browserProvider: !isEdgeProvider,
231
242
  fonts: [],
232
243
  satoriOptions: {},
233
244
  experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
234
- experimentalRuntimeBrowser: false,
235
245
  playground: process.env.NODE_ENV === "development" || nuxt.options.dev
236
246
  };
237
247
  },
@@ -323,6 +333,7 @@ export {}
323
333
  });
324
334
  const runtimeDir = resolve("./runtime");
325
335
  nuxt.options.build.transpile.push(runtimeDir);
336
+ addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
326
337
  const moduleAssetDir = resolve("./runtime/public-assets");
327
338
  const assetDirs = [
328
339
  resolve(nuxt.options.rootDir, nuxt.options.dir.public),
@@ -337,17 +348,13 @@ export {}
337
348
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
338
349
  inline: [runtimeDir]
339
350
  });
340
- if (config.experimentalRuntimeBrowser) {
341
- nitroConfig.alias = nitroConfig.alias || {};
342
- nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
343
- nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
344
- nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
345
- }
346
351
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
347
352
  nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
348
353
  const providerPath = `${runtimeDir}/nitro/providers`;
354
+ const nitroPreset = nuxt.options.nitro.preset || process.env.NITRO_PRESET;
355
+ const isNodeNitroServer = !nitroPreset || nitroPreset === "node";
349
356
  if (config.browserProvider) {
350
- nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || config.experimentalRuntimeBrowser ? `
357
+ nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || process.env.prerender || isNodeNitroServer ? `
351
358
  import node from '${providerPath}/browser/node'
352
359
 
353
360
  export default async function() {
@@ -370,14 +377,15 @@ export default async function() {
370
377
  }`;
371
378
  }
372
379
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
373
- import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
374
- import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
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"))}'` : ""}
375
382
 
376
383
  export async function useProvider(provider) {
377
384
  if (provider === 'satori')
378
- return satori
385
+ return ${config.satoriProvider ? "satori" : "null"}
379
386
  if (provider === 'browser')
380
- return browser
387
+ return ${config.browserProvider ? "browser" : "null"}
388
+ return null
381
389
  }
382
390
  `;
383
391
  });
@@ -392,9 +400,14 @@ export async function useProvider(provider) {
392
400
  if (useSatoriWasm)
393
401
  await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
394
402
  }
395
- const indexFile = resolve(_nitro.options.output.serverDir, _nitro.options.preset === "netlify-edge" ? "server.mjs" : "index.mjs");
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);
396
406
  if (await pathExists(indexFile)) {
397
- const indexContents = (await readFile(indexFile, "utf-8")).replace(".cwd(),", '?.cwd || "/",');
407
+ let indexContents = await readFile(indexFile, "utf-8");
408
+ if (_nitro.options.preset.includes("vercel")) {
409
+ indexContents = indexContents.replace(".cwd(),", '?.cwd || "/",');
410
+ }
398
411
  if (!config.experimentalInlineWasm) {
399
412
  await writeFile(
400
413
  indexFile,
@@ -421,7 +434,6 @@ export async function useProvider(provider) {
421
434
  if (!html)
422
435
  return;
423
436
  const extractedOptions = extractOgImageOptions(html);
424
- ctx.contents = stripOgImageOptions(html);
425
437
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
426
438
  if (!extractedOptions || routeRules.ogImage === false)
427
439
  return;
@@ -440,21 +452,6 @@ export async function useProvider(provider) {
440
452
  await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
441
453
  if (screenshotQueue.length === 0)
442
454
  return;
443
- for (const entry of screenshotQueue) {
444
- if (entry.route && Object.keys(entry).length === 1) {
445
- const html = await $fetch(entry.route);
446
- const extractedOptions = extractOgImageOptions(html);
447
- const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
448
- Object.assign(entry, {
449
- // @ts-expect-error runtime
450
- path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
451
- ...extractedOptions,
452
- ...routeRules.ogImage || {}
453
- });
454
- }
455
- if (entry.component)
456
- entry.html = await $fetch(entry.path);
457
- }
458
455
  nitro.logger.info("Ensuring chromium install for og:image generation...");
459
456
  const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
460
457
  stdio: "inherit"
@@ -481,6 +478,21 @@ export async function useProvider(provider) {
481
478
  browser = await createBrowser();
482
479
  if (browser) {
483
480
  nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
481
+ for (const entry of screenshotQueue) {
482
+ if (entry.route && Object.keys(entry).length === 1) {
483
+ const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
484
+ const extractedOptions = extractOgImageOptions(html);
485
+ const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
486
+ Object.assign(entry, {
487
+ // @ts-expect-error runtime
488
+ path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
489
+ ...extractedOptions,
490
+ ...routeRules.ogImage || {}
491
+ });
492
+ }
493
+ if (entry.component)
494
+ entry.html = await globalThis.$fetch(entry.path);
495
+ }
484
496
  for (const k in screenshotQueue) {
485
497
  const entry = screenshotQueue[k];
486
498
  const start = Date.now();
@@ -518,7 +530,7 @@ export async function useProvider(provider) {
518
530
  }
519
531
  screenshotQueue = [];
520
532
  };
521
- if (!nuxt.options._prepare) {
533
+ if (nuxt.options._generate) {
522
534
  nitro.hooks.hook("rollup:before", async () => {
523
535
  await captureScreenshots();
524
536
  });
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ import type { Buffer } from 'node:buffer';
2
3
  import type { Browser } from 'playwright-core';
3
4
  import type { ScreenshotOptions } from '../types';
4
5
  export declare function screenshot(browser: Browser, options: Partial<ScreenshotOptions> & Record<string, any>): Promise<Buffer>;
@@ -6,9 +6,9 @@ export async function screenshot(browser, options) {
6
6
  width: options.width || 1200,
7
7
  height: options.height || 630
8
8
  });
9
- const isHtml = options.path.startsWith("html:") || options.html;
9
+ const isHtml = options.html || options.path?.startsWith("html:");
10
10
  if (isHtml) {
11
- const html = options.html || options.path.substring(5);
11
+ const html = options.html || options.path?.substring(5);
12
12
  await page.evaluate((html2) => {
13
13
  document.open("text/html");
14
14
  document.write(html2);
@@ -21,6 +21,9 @@ export async function screenshot(browser, options) {
21
21
  waitUntil: "networkidle"
22
22
  });
23
23
  }
24
+ const screenshotOptions = {
25
+ timeout: 1e4
26
+ };
24
27
  if (options.delay)
25
28
  await page.waitForTimeout(options.delay);
26
29
  if (options.mask) {
@@ -30,6 +33,6 @@ export async function screenshot(browser, options) {
30
33
  }, options.mask);
31
34
  }
32
35
  if (options.selector)
33
- return await page.locator(options.selector).screenshot();
34
- return await page.screenshot();
36
+ return await page.locator(options.selector).screenshot(screenshotOptions);
37
+ return await page.screenshot(screenshotOptions);
35
38
  }
@@ -1,7 +1,7 @@
1
- import { useServerHead } from "@vueuse/head";
2
1
  import { withBase } from "ufo";
3
2
  import { useRequestEvent } from "#app";
4
- import { useRouter, useRuntimeConfig } from "#imports";
3
+ import { useHostname } from "../nitro/util-hostname.mjs";
4
+ import { useRouter, useRuntimeConfig, useServerHead } from "#imports";
5
5
  export function defineOgImageScreenshot(options = {}) {
6
6
  const router = useRouter();
7
7
  const route = router?.currentRoute?.value?.path || "";
@@ -31,12 +31,11 @@ export function defineOgImageStatic(options = {}) {
31
31
  }
32
32
  export function defineOgImage(options = {}) {
33
33
  if (process.server) {
34
- const { forcePrerender, defaults, host } = useRuntimeConfig()["nuxt-og-image"];
34
+ const { defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
35
35
  const router = useRouter();
36
36
  const route = router?.currentRoute?.value?.path || "";
37
37
  const e = useRequestEvent();
38
- if ((forcePrerender || options.static) && options.provider === "satori")
39
- e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
38
+ const baseUrl = process.env.prerender ? siteUrl : useHostname(e);
40
39
  const meta = [
41
40
  {
42
41
  name: "twitter:card",
@@ -44,11 +43,11 @@ export function defineOgImage(options = {}) {
44
43
  },
45
44
  {
46
45
  name: "twitter:image:src",
47
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, host)
46
+ content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
48
47
  },
49
48
  {
50
49
  property: "og:image",
51
- content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, host)
50
+ content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
52
51
  },
53
52
  {
54
53
  property: "og:image:width",
@@ -9,7 +9,6 @@ export default defineEventHandler(async (e) => {
9
9
  const basePath = withoutTrailingSlash(
10
10
  path.replace("__og_image__/og.png", "")
11
11
  );
12
- setHeader(e, "Content-Type", "image/png");
13
12
  setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
14
13
  setHeader(e, "Pragma", "no-cache");
15
14
  setHeader(e, "Expires", "0");
@@ -21,5 +20,21 @@ export default defineEventHandler(async (e) => {
21
20
  statusMessage: `Provider ${options.provider} is missing.`
22
21
  });
23
22
  }
24
- return provider.createPng(withBase(basePath, useHostname(e)), options);
23
+ try {
24
+ const png = await provider.createPng(withBase(basePath, useHostname(e)), options);
25
+ if (png) {
26
+ setHeader(e, "Content-Type", "image/png");
27
+ return png;
28
+ }
29
+ } catch (err) {
30
+ throw createError({
31
+ statusCode: 500,
32
+ statusMessage: `Failed to create og image: ${err.message}`
33
+ });
34
+ }
35
+ throw createError({
36
+ statusCode: 500,
37
+ statusMessage: "Failed to create og image, unknown error."
38
+ });
39
+ return false;
25
40
  });
@@ -1,11 +1,12 @@
1
1
  import { defineEventHandler } from "h3";
2
- import { parseURL, withoutTrailingSlash } from "ufo";
2
+ import { parseURL, withBase, withoutTrailingSlash } from "ufo";
3
3
  import { fetchOptions } from "../utils.mjs";
4
+ import { useRuntimeConfig } from "#imports";
4
5
  export default defineEventHandler(async (e) => {
5
6
  const path = withoutTrailingSlash(parseURL(e.path).pathname);
6
7
  if (!path.endsWith("/__og_image__"))
7
8
  return;
8
- const basePath = path.replace("/__og_image__", "");
9
+ const basePath = withBase(path.replace("/__og_image__", ""), useRuntimeConfig().app.baseURL);
9
10
  const options = await fetchOptions(e, basePath === "" ? "/" : basePath);
10
11
  if (!options)
11
12
  return `The route ${basePath} has not been set up for og:image generation.`;
@@ -22,5 +23,5 @@ export default defineEventHandler(async (e) => {
22
23
  }
23
24
  </style>
24
25
  <title>OG Image Playground</title>
25
- <iframe src="/__nuxt_og_image__/client/?&path=${basePath}"></iframe>`;
26
+ <iframe src="/__nuxt_og_image__/client?&path=${basePath}&base=${useRuntimeConfig().app.baseURL}"></iframe>`;
26
27
  });
@@ -0,0 +1,3 @@
1
+ import type { NitroAppPlugin } from 'nitropack';
2
+ declare const OgImagePrenderNitroPlugin: NitroAppPlugin;
3
+ export default OgImagePrenderNitroPlugin;
@@ -0,0 +1,20 @@
1
+ import { appendHeader } from "h3";
2
+ import { joinURL } from "ufo";
3
+ import { extractOgImageOptions } from "../utils-pure.mjs";
4
+ import { useRuntimeConfig } from "#imports";
5
+ const OgImagePrenderNitroPlugin = (nitroApp) => {
6
+ if (!process.env.prerender)
7
+ return;
8
+ const { forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
9
+ nitroApp.hooks.hook("render:html", async (ctx, { event }) => {
10
+ const url = event.node.req.url;
11
+ if (url.includes(".") || url.startsWith("/__nuxt_island/"))
12
+ return;
13
+ const options = extractOgImageOptions(ctx.head.join("\n"));
14
+ if (!options)
15
+ return;
16
+ if ((forcePrerender || options.static) && options.provider === "satori")
17
+ appendHeader(event, "x-nitro-prerender", joinURL(url, "/__og_image__/og.png"));
18
+ });
19
+ };
20
+ export default OgImagePrenderNitroPlugin;
@@ -1,13 +1,15 @@
1
1
  import playwrightCore from "playwright-core";
2
2
  export default async function createBrowser() {
3
- try {
4
- const { Launcher } = await import(String("chrome-launcher"));
5
- const chromePath = Launcher.getFirstInstallation();
6
- return await playwrightCore.chromium.launch({
7
- headless: true,
8
- executablePath: chromePath
9
- });
10
- } catch (e) {
3
+ if (process.dev || process.env.prerender) {
4
+ try {
5
+ const { Launcher } = await import(String("chrome-launcher"));
6
+ const chromePath = Launcher.getFirstInstallation();
7
+ return await playwrightCore.chromium.launch({
8
+ headless: true,
9
+ executablePath: chromePath
10
+ });
11
+ } catch (e) {
12
+ }
11
13
  }
12
14
  try {
13
15
  return await playwrightCore.chromium.launch({
@@ -2,8 +2,11 @@ import { initialize, svg2png } from "svg2png-wasm";
2
2
  import { wasmLoader } from "../../utils.mjs";
3
3
  export default async function(svg, options) {
4
4
  const loader = wasmLoader("/* NUXT_OG_IMAGE_SVG2PNG_WASM */", "/svg2png.wasm", options.baseUrl);
5
- if (!await loader.loaded())
6
- await initialize(await loader.load()).catch(() => {
5
+ if (!await loader.loaded()) {
6
+ await initialize(await loader.load()).catch((e) => {
7
+ if (!e.message.trim().endsWith("function can be used only once."))
8
+ throw e;
7
9
  });
10
+ }
8
11
  return await svg2png(svg, options);
9
12
  }
@@ -1,5 +1,7 @@
1
+ import { withBase } from "ufo";
1
2
  import { screenshot } from "../../browserUtil.mjs";
2
3
  import loadBrowser from "#nuxt-og-image/browser";
4
+ import { useRuntimeConfig } from "#imports";
3
5
  export default {
4
6
  name: "browser",
5
7
  createSvg: async function createSvg() {
@@ -11,12 +13,15 @@ export default {
11
13
  createPng: async function createPng(basePath, options) {
12
14
  const url = new URL(basePath);
13
15
  const createBrowser = await loadBrowser();
16
+ if (!createBrowser) {
17
+ throw new Error("Failed to load browser. Is the `browserProvider` enabled?");
18
+ }
14
19
  const browser = await createBrowser();
15
20
  if (browser) {
16
21
  try {
17
22
  return await screenshot(browser, {
18
23
  ...options,
19
- host: url.origin,
24
+ host: withBase(useRuntimeConfig().app.baseURL, url.origin),
20
25
  path: `/api/og-image-html?path=${url.pathname}`
21
26
  });
22
27
  } finally {
@@ -1,9 +1,10 @@
1
1
  import { defineSatoriTransformer } from "../utils.mjs";
2
+ import { decodeHtml } from "../../../utils-pure.mjs";
2
3
  export default defineSatoriTransformer(() => {
3
4
  return {
4
5
  filter: (node) => typeof node.props?.children === "string",
5
6
  transform: async (node) => {
6
- node.props.children = node.props.children.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
7
+ node.props.children = decodeHtml(node.props.children);
7
8
  }
8
9
  };
9
10
  });
@@ -1,3 +1,4 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import { base64ToArrayBuffer, readPublicAsset } from "../../utils.mjs";
2
3
  import { useStorage } from "#internal/nitro";
3
4
  const cachedFonts = {};
@@ -3,19 +3,22 @@ import { renderSSRHead } from "@unhead/ssr";
3
3
  import { createHeadCore } from "@unhead/vue";
4
4
  import { defineEventHandler, getQuery, sendRedirect } from "h3";
5
5
  import { fetchOptions, renderIsland, useHostname } from "../utils.mjs";
6
- import { useRuntimeConfig } from "#internal/nitro";
6
+ import { useRuntimeConfig } from "#imports";
7
7
  export default defineEventHandler(async (e) => {
8
8
  const { fonts, defaults } = useRuntimeConfig()["nuxt-og-image"];
9
- const path = getQuery(e).path || "/";
10
- const scale = getQuery(e).scale;
11
- const mode = getQuery(e).mode || "light";
9
+ const query = getQuery(e);
10
+ const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
11
+ const scale = query.scale;
12
+ const mode = query.mode || "light";
12
13
  let options;
13
- if (getQuery(e).options)
14
- options = JSON.parse(getQuery(e).options);
14
+ if (query.options)
15
+ options = JSON.parse(query.options);
15
16
  if (!options)
16
17
  options = await fetchOptions(e, path);
17
- if (options.provider === "browser" && !options.component)
18
- return sendRedirect(e, withBase(path, useHostname(e)));
18
+ if (options.provider === "browser" && !options.component) {
19
+ const pathWithoutBase = path.replace(new RegExp(`^${useRuntimeConfig().app.baseURL}`), "");
20
+ return sendRedirect(e, withBase(pathWithoutBase, useHostname(e)));
21
+ }
19
22
  const island = await renderIsland(options);
20
23
  const head = createHeadCore();
21
24
  head.push(island.head);
@@ -1,9 +1,12 @@
1
1
  import { createError, defineEventHandler, getHeaders, getQuery } from "h3";
2
- import { extractOgImageOptions, useHostname } from "../utils.mjs";
3
- import { getRouteRules, useRuntimeConfig } from "#internal/nitro";
2
+ import { withoutBase } from "ufo";
3
+ import { extractOgImageOptions } from "../utils.mjs";
4
+ import { useHostname } from "../util-hostname.mjs";
5
+ import { getRouteRules } from "#internal/nitro";
6
+ import { useRuntimeConfig } from "#imports";
4
7
  export default defineEventHandler(async (e) => {
5
8
  const query = getQuery(e);
6
- const path = query.path || "/";
9
+ const path = withoutBase(query.path || "/", useRuntimeConfig().app.baseURL);
7
10
  const fetchOptions = process.dev || process.env.prerender ? {
8
11
  headers: getHeaders(e)
9
12
  } : {
@@ -2,8 +2,10 @@ import { createError, defineEventHandler, getQuery, setHeader } from "h3";
2
2
  import { withBase } from "ufo";
3
3
  import { fetchOptions, useHostname } from "../utils.mjs";
4
4
  import { useProvider } from "#nuxt-og-image/provider";
5
+ import { useRuntimeConfig } from "#imports";
5
6
  export default defineEventHandler(async (e) => {
6
- const path = getQuery(e).path || "/";
7
+ const query = getQuery(e);
8
+ const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
7
9
  const options = await fetchOptions(e, path);
8
10
  setHeader(e, "Content-Type", "image/svg+xml");
9
11
  const provider = await useProvider(options.provider);
@@ -2,8 +2,10 @@ import { createError, defineEventHandler, getQuery, setHeader } from "h3";
2
2
  import { withBase } from "ufo";
3
3
  import { fetchOptions, useHostname } from "../utils.mjs";
4
4
  import { useProvider } from "#nuxt-og-image/provider";
5
+ import { useRuntimeConfig } from "#imports";
5
6
  export default defineEventHandler(async (e) => {
6
- const path = getQuery(e).path || "/";
7
+ const query = getQuery(e);
8
+ const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
7
9
  const options = await fetchOptions(e, path);
8
10
  setHeader(e, "Content-Type", "application/json");
9
11
  const provider = await useProvider(options.provider);
@@ -0,0 +1,2 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function useHostname(e: H3Event): string;
@@ -0,0 +1,15 @@
1
+ import { getRequestHost, getRequestProtocol } from "h3";
2
+ import { withBase } from "ufo";
3
+ import { useRuntimeConfig } from "#imports";
4
+ export function useHostname(e) {
5
+ const base = useRuntimeConfig().app.baseURL;
6
+ let host = getRequestHost(e);
7
+ if (host === "localhost")
8
+ host = process.env.NITRO_HOST || process.env.HOST || host;
9
+ const protocol = getRequestProtocol(e);
10
+ const useHttp = process.dev || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
11
+ let port = host.includes(":") ? host.split(":").pop() : false;
12
+ if ((process.dev || process.env.prerender || host.includes("localhost")) && !port)
13
+ port = process.env.NITRO_PORT || process.env.PORT;
14
+ return withBase(base, `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}`);
15
+ }
@@ -1,2 +1,3 @@
1
- export declare function extractOgImageOptions(html: string): false | Record<string, any>;
2
- export declare function stripOgImageOptions(html: string): string;
1
+ import type { OgImageOptions } from '../../types';
2
+ export declare function decodeHtml(html: string): string;
3
+ export declare function extractOgImageOptions(html: string): OgImageOptions | false;