nuxt-og-image 1.5.5 → 2.0.0-beta.1

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.
package/dist/module.d.ts CHANGED
@@ -35,8 +35,10 @@ interface OgImageOptions extends Partial<ScreenshotOptions> {
35
35
  interface ModuleOptions {
36
36
  /**
37
37
  * The hostname of your website.
38
+ * @deprecated use `siteUrl`
38
39
  */
39
- host: string;
40
+ host?: string;
41
+ siteUrl: string;
40
42
  defaults: OgImageOptions;
41
43
  fonts: `${string}:${number}`[];
42
44
  satoriOptions: Partial<SatoriOptions>;
@@ -47,6 +49,10 @@ interface ModuleOptions {
47
49
  experimentalRuntimeBrowser: boolean;
48
50
  playground: boolean;
49
51
  }
52
+ interface ModuleHooks {
53
+ 'og-image:config': (config: ModuleOptions) => Promise<void> | void;
54
+ 'og-image:prerenderScreenshots': (queue: OgImageOptions[]) => Promise<void> | void;
55
+ }
50
56
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
51
57
 
52
- export { ModuleOptions, _default as default };
58
+ export { ModuleHooks, ModuleOptions, _default as default };
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "compatibility": {
4
- "nuxt": "^3.2.0",
4
+ "nuxt": "^3.3.1",
5
5
  "bridge": false
6
6
  },
7
7
  "configKey": "ogImage",
8
- "version": "1.5.5"
8
+ "version": "2.0.0-beta.1"
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
- import { useNuxt, addTemplate, defineNuxtModule, createResolver, addServerHandler, addImports, addComponent } from '@nuxt/kit';
2
+ import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
3
3
  import { execa } from 'execa';
4
4
  import chalk from 'chalk';
5
5
  import defu from 'defu';
@@ -54,12 +54,14 @@ async function screenshot(browser, options) {
54
54
  width: options.width || 1200,
55
55
  height: options.height || 630
56
56
  });
57
- if (options.path.startsWith("html:")) {
58
- await page.evaluate((html) => {
57
+ const isHtml = options.path.startsWith("html:") || options.html;
58
+ if (isHtml) {
59
+ const html = options.html || options.path.substring(5);
60
+ await page.evaluate((html2) => {
59
61
  document.open("text/html");
60
- document.write(html);
62
+ document.write(html2);
61
63
  document.close();
62
- }, options.path.substring(5));
64
+ }, html);
63
65
  await page.waitForLoadState("networkidle");
64
66
  } else {
65
67
  await page.goto(`${options.host}${options.path}`, {
@@ -162,40 +164,6 @@ function getBodyJson(req) {
162
164
  });
163
165
  }
164
166
 
165
- function exposeModuleConfig(moduleName, config) {
166
- const nuxt = useNuxt();
167
- const jsExports = Object.entries(config).map(([k, v]) => `export const ${k} = ${JSON.stringify(v)}`).join("\n");
168
- const alias = `#${moduleName}/config`;
169
- nuxt.options.alias[alias] = addTemplate({
170
- filename: `modules/config/${moduleName}.mjs`,
171
- getContents: () => jsExports
172
- }).dst;
173
- nuxt.hooks.hook("nitro:config", (nitroConfig) => {
174
- nitroConfig.virtual[alias] = jsExports;
175
- });
176
- const typeDefName = `modules/config/${moduleName}.d.ts`;
177
- const tsExports = Object.keys(config).map((k) => {
178
- if (nuxt.options.dev)
179
- return ` export const ${k}: ${JSON.stringify(config[k])} | ModuleOptions['${k}']`;
180
- return ` export const ${k}: ModuleOptions['${k}']`;
181
- }).join("\n");
182
- addTemplate({
183
- filename: typeDefName,
184
- getContents: () => {
185
- return `// generated by ${moduleName}
186
- import type { ModuleOptions } from '${moduleName}'
187
- declare module '${alias}' {
188
- ${tsExports}
189
- }
190
- `;
191
- }
192
- });
193
- nuxt.hooks.hook("prepare:types", ({ references }) => {
194
- references.push({ path: resolve(nuxt.options.buildDir, typeDefName) });
195
- });
196
- return alias;
197
- }
198
-
199
167
  function decodeHtmlEntities(obj) {
200
168
  Object.entries(obj).forEach(([key, value]) => {
201
169
  if (typeof value === "string") {
@@ -242,16 +210,17 @@ const module = defineNuxtModule({
242
210
  meta: {
243
211
  name: "nuxt-og-image",
244
212
  compatibility: {
245
- nuxt: "^3.2.0",
213
+ nuxt: "^3.3.1",
246
214
  bridge: false
247
215
  },
248
216
  configKey: "ogImage"
249
217
  },
250
218
  defaults(nuxt) {
219
+ const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
251
220
  return {
252
221
  // when we run `nuxi generate` we need to force prerendering
253
222
  forcePrerender: !nuxt.options.dev && nuxt.options._generate,
254
- host: process.env.NUXT_PUBLIC_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl,
223
+ siteUrl,
255
224
  defaults: {
256
225
  component: "OgImageBasic",
257
226
  width: 1200,
@@ -268,6 +237,7 @@ const module = defineNuxtModule({
268
237
  },
269
238
  async setup(config, nuxt) {
270
239
  const { resolve } = createResolver(import.meta.url);
240
+ config.siteUrl = config.siteUrl || config.host;
271
241
  if (!config.fonts.length)
272
242
  config.fonts = ["Inter:400", "Inter:700"];
273
243
  const distResolve = (p) => {
@@ -332,11 +302,13 @@ export {}
332
302
  handler: resolve("./runtime/nitro/middleware/playground")
333
303
  });
334
304
  }
305
+ nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = [];
335
306
  ["defineOgImageDynamic", "defineOgImageStatic", "defineOgImageScreenshot"].forEach((name) => {
336
307
  addImports({
337
308
  name,
338
309
  from: resolve("./runtime/composables/defineOgImage")
339
310
  });
311
+ nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"].push(name);
340
312
  });
341
313
  await addComponent({
342
314
  name: "OgImageBasic",
@@ -356,7 +328,10 @@ export {}
356
328
  resolve(nuxt.options.rootDir, nuxt.options.dir.public),
357
329
  moduleAssetDir
358
330
  ];
359
- exposeModuleConfig("nuxt-og-image", { ...config, assetDirs });
331
+ nuxt.hooks.hook("modules:done", async () => {
332
+ nuxt.hooks.callHook("og-image:config", config);
333
+ nuxt.options.runtimeConfig["nuxt-og-image"] = { ...config, assetDirs };
334
+ });
360
335
  const useSatoriWasm = provider === "stackblitz";
361
336
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
362
337
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
@@ -451,21 +426,35 @@ export async function useProvider(provider) {
451
426
  if (!extractedOptions || routeRules.ogImage === false)
452
427
  return;
453
428
  const entry = {
429
+ route: ctx.route,
454
430
  path: extractedOptions.component ? `/api/og-image-html?path=${ctx.route}` : ctx.route,
455
431
  ...extractedOptions,
456
- ...routeRules.ogImage || {},
457
- ctx
432
+ ...routeRules.ogImage || {}
458
433
  };
459
- if (entry.component)
460
- entry.path = `html:${await $fetch(entry.path)}`;
461
434
  if ((nuxt.options._generate || entry.static) && entry.provider === "browser")
462
435
  screenshotQueue.push(entry);
463
436
  });
464
437
  if (nuxt.options.dev)
465
438
  return;
466
439
  const captureScreenshots = async () => {
440
+ await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
467
441
  if (screenshotQueue.length === 0)
468
442
  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
+ }
469
458
  nitro.logger.info("Ensuring chromium install for og:image generation...");
470
459
  const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
471
460
  stdio: "inherit"
@@ -496,7 +485,7 @@ export async function useProvider(provider) {
496
485
  const entry = screenshotQueue[k];
497
486
  const start = Date.now();
498
487
  let hasError = false;
499
- const dirname = joinURL(nitro.options.output.publicDir, `${entry.ctx.fileName.replace("index.html", "")}__og_image__/`);
488
+ const dirname = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
500
489
  const filename = joinURL(dirname, "/og.png");
501
490
  try {
502
491
  const imgBuffer = await screenshot(browser, {
@@ -515,7 +504,7 @@ export async function useProvider(provider) {
515
504
  }
516
505
  const generateTimeMS = Date.now() - start;
517
506
  nitro.logger.log(chalk[hasError ? "red" : "gray"](
518
- ` ${Number(k) === screenshotQueue.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${relative(nitro.options.output.publicDir, filename)} (${generateTimeMS}ms) ${Math.round((Number(k) + 1) / screenshotQueue.length * 100)}%`
507
+ ` ${Number(k) === screenshotQueue.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} /${relative(nitro.options.output.publicDir, filename)} (${generateTimeMS}ms) ${Math.round((Number(k) + 1) / screenshotQueue.length * 100)}%`
519
508
  ));
520
509
  }
521
510
  } else {
@@ -529,12 +518,14 @@ export async function useProvider(provider) {
529
518
  }
530
519
  screenshotQueue = [];
531
520
  };
532
- nitro.hooks.hook("rollup:before", async () => {
533
- await captureScreenshots();
534
- });
535
- nitro.hooks.hook("close", async () => {
536
- await captureScreenshots();
537
- });
521
+ if (!nuxt.options._prepare) {
522
+ nitro.hooks.hook("rollup:before", async () => {
523
+ await captureScreenshots();
524
+ });
525
+ nitro.hooks.hook("close", async () => {
526
+ await captureScreenshots();
527
+ });
528
+ }
538
529
  });
539
530
  }
540
531
  });
@@ -6,12 +6,14 @@ export async function screenshot(browser, options) {
6
6
  width: options.width || 1200,
7
7
  height: options.height || 630
8
8
  });
9
- if (options.path.startsWith("html:")) {
10
- await page.evaluate((html) => {
9
+ const isHtml = options.path.startsWith("html:") || options.html;
10
+ if (isHtml) {
11
+ const html = options.html || options.path.substring(5);
12
+ await page.evaluate((html2) => {
11
13
  document.open("text/html");
12
- document.write(html);
14
+ document.write(html2);
13
15
  document.close();
14
- }, options.path.substring(5));
16
+ }, html);
15
17
  await page.waitForLoadState("networkidle");
16
18
  } else {
17
19
  await page.goto(`${options.host}${options.path}`, {
@@ -3,7 +3,8 @@ import { defineOgImageDynamic } from "#imports";
3
3
  export default defineComponent({
4
4
  name: "OgImageDynamic",
5
5
  setup(_, { attrs }) {
6
- defineOgImageDynamic(attrs);
6
+ if (process.server)
7
+ defineOgImageDynamic(attrs);
7
8
  return () => null;
8
9
  }
9
10
  });
@@ -3,7 +3,8 @@ import { defineOgImageScreenshot } from "#imports";
3
3
  export default defineComponent({
4
4
  name: "OgImageScreenshot",
5
5
  setup(_, { attrs }) {
6
- defineOgImageScreenshot(attrs);
6
+ if (process.server)
7
+ defineOgImageScreenshot(attrs);
7
8
  return () => null;
8
9
  }
9
10
  });
@@ -3,7 +3,8 @@ import { defineOgImageStatic } from "#imports";
3
3
  export default defineComponent({
4
4
  name: "OgImageStatic",
5
5
  setup(_, { attrs }) {
6
- defineOgImageStatic(attrs);
6
+ if (process.server)
7
+ defineOgImageStatic(attrs);
7
8
  return () => null;
8
9
  }
9
10
  });
@@ -2,7 +2,6 @@ import { useServerHead } from "@vueuse/head";
2
2
  import { withBase } from "ufo";
3
3
  import { useRequestEvent } from "#app";
4
4
  import { useRouter } from "#imports";
5
- import { defaults, forcePrerender, host, satoriProvider } from "#nuxt-og-image/config";
6
5
  export function defineOgImageScreenshot(options = {}) {
7
6
  const router = useRouter();
8
7
  const route = router?.currentRoute?.value?.path || "";
@@ -15,6 +14,7 @@ export function defineOgImageScreenshot(options = {}) {
15
14
  });
16
15
  }
17
16
  export function defineOgImageDynamic(options = {}) {
17
+ const { satoriProvider, forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
18
18
  defineOgImage({
19
19
  provider: satoriProvider ? "satori" : "browser",
20
20
  static: !!forcePrerender,
@@ -22,6 +22,7 @@ export function defineOgImageDynamic(options = {}) {
22
22
  });
23
23
  }
24
24
  export function defineOgImageStatic(options = {}) {
25
+ const { satoriProvider } = useRuntimeConfig()["nuxt-og-image"];
25
26
  defineOgImage({
26
27
  provider: satoriProvider ? "satori" : "browser",
27
28
  static: true,
@@ -30,6 +31,7 @@ export function defineOgImageStatic(options = {}) {
30
31
  }
31
32
  export function defineOgImage(options = {}) {
32
33
  if (process.server) {
34
+ const { forcePrerender, defaults, host } = useRuntimeConfig()["nuxt-og-image"];
33
35
  const router = useRouter();
34
36
  const route = router?.currentRoute?.value?.path || "";
35
37
  const e = useRequestEvent();
@@ -13,11 +13,15 @@ export default {
13
13
  const createBrowser = await loadBrowser();
14
14
  const browser = await createBrowser();
15
15
  if (browser) {
16
- return screenshot(browser, {
17
- ...options,
18
- host: url.origin,
19
- path: `/api/og-image-html?path=${url.pathname}`
20
- });
16
+ try {
17
+ return await screenshot(browser, {
18
+ ...options,
19
+ host: url.origin,
20
+ path: `/api/og-image-html?path=${url.pathname}`
21
+ });
22
+ } finally {
23
+ browser.close();
24
+ }
21
25
  }
22
26
  return null;
23
27
  }
@@ -7,15 +7,15 @@ import twClasses from "./plugins/twClasses.mjs";
7
7
  import flex from "./plugins/flex.mjs";
8
8
  import emojis from "./plugins/emojis.mjs";
9
9
  import encoding from "./plugins/encoding.mjs";
10
- import { fonts, satoriOptions } from "#nuxt-og-image/config";
11
10
  import loadSvg2png from "#nuxt-og-image/svg2png";
12
11
  import loadSatori from "#nuxt-og-image/satori";
12
+ import { useRuntimeConfig } from "#internal/nitro";
13
13
  const satoriFonts = [];
14
14
  let fontLoadPromise = null;
15
- function loadFonts(fonts2) {
15
+ function loadFonts(fonts) {
16
16
  if (fontLoadPromise)
17
17
  return fontLoadPromise;
18
- return fontLoadPromise = Promise.all(fonts2.map((font) => loadFont(font)));
18
+ return fontLoadPromise = Promise.all(fonts.map((font) => loadFont(font)));
19
19
  }
20
20
  export default {
21
21
  name: "satori",
@@ -46,6 +46,7 @@ export default {
46
46
  return satoriTree;
47
47
  },
48
48
  createSvg: async function createSvg(baseUrl, options) {
49
+ const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
49
50
  const vnodes = await this.createVNode(baseUrl, options);
50
51
  if (!satoriFonts.length)
51
52
  satoriFonts.push(...await loadFonts(fonts));
@@ -3,8 +3,9 @@ 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 { defaults, fonts } from "#nuxt-og-image/config";
6
+ import { useRuntimeConfig } from "#internal/nitro";
7
7
  export default defineEventHandler(async (e) => {
8
+ const { fonts, defaults } = useRuntimeConfig()["nuxt-og-image"];
8
9
  const path = getQuery(e).path || "/";
9
10
  const scale = getQuery(e).scale;
10
11
  const mode = getQuery(e).mode || "light";
@@ -1,7 +1,6 @@
1
1
  import { createError, defineEventHandler, getHeaders, getQuery } from "h3";
2
2
  import { extractOgImageOptions, useHostname } from "../utils.mjs";
3
- import { getRouteRules } from "#internal/nitro";
4
- import { defaults } from "#nuxt-og-image/config";
3
+ import { getRouteRules, useRuntimeConfig } from "#internal/nitro";
5
4
  export default defineEventHandler(async (e) => {
6
5
  const query = getQuery(e);
7
6
  const path = query.path || "/";
@@ -26,6 +25,7 @@ export default defineEventHandler(async (e) => {
26
25
  e.node.req.url = e.path;
27
26
  if (routeRules === false)
28
27
  return false;
28
+ const { defaults } = useRuntimeConfig()["nuxt-og-image"];
29
29
  return {
30
30
  path,
31
31
  ...defaults,
@@ -11,7 +11,7 @@ export declare function renderIsland(payload: OgImageOptions): Promise<{
11
11
  html: string;
12
12
  head: any;
13
13
  }>;
14
- export declare function useHostname(e: H3Event): string;
14
+ export declare function useHostname(e: H3Event): any;
15
15
  export declare function readPublicAsset(file: string, encoding?: BufferEncoding): Promise<string | Buffer | undefined>;
16
16
  export declare function readPublicAssetBase64(file: string): Promise<string | undefined>;
17
17
  export * from './utils-pure';
@@ -1,7 +1,6 @@
1
1
  import { existsSync, promises as fsp } from "node:fs";
2
2
  import { getHeaders, getQuery, getRequestHeader } from "h3";
3
3
  import { join } from "pathe";
4
- import { assetDirs } from "#nuxt-og-image/config";
5
4
  import { useRuntimeConfig } from "#internal/nitro";
6
5
  export function wasmLoader(key, fallback, baseUrl) {
7
6
  let promise;
@@ -60,6 +59,9 @@ export function renderIsland(payload) {
60
59
  });
61
60
  }
62
61
  export function useHostname(e) {
62
+ const config = useRuntimeConfig()["nuxt-og-image"];
63
+ if (!process.dev && config.siteUrl)
64
+ return config.siteUrl;
63
65
  const host = getRequestHeader(e, "host") || process.env.NITRO_HOST || process.env.HOST || "localhost";
64
66
  const protocol = getRequestHeader(e, "x-forwarded-proto") || "http";
65
67
  const useHttp = process.env.NODE_ENV === "development" || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
@@ -71,6 +73,7 @@ const r = (base, key) => {
71
73
  return join(base, key.replace(/:/g, "/"));
72
74
  };
73
75
  export async function readPublicAsset(file, encoding) {
76
+ const { assetDirs } = useRuntimeConfig()["nuxt-og-image"];
74
77
  for (const assetDir of assetDirs) {
75
78
  const path = r(assetDir, file);
76
79
  if (existsSync(path))
package/dist/types.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
 
2
- import { ModuleOptions } from './module'
2
+ import { ModuleOptions, ModuleHooks } from './module'
3
3
 
4
4
  declare module '@nuxt/schema' {
5
5
  interface NuxtConfig { ['ogImage']?: Partial<ModuleOptions> }
6
6
  interface NuxtOptions { ['ogImage']?: ModuleOptions }
7
+ interface NuxtHooks extends ModuleHooks {}
7
8
  }
8
9
 
9
10
 
10
- export { ModuleOptions, default } from './module'
11
+ export { ModuleHooks, ModuleOptions, default } from './module'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "type": "module",
4
- "version": "1.5.5",
4
+ "version": "2.0.0-beta.1",
5
5
  "packageManager": "pnpm@7.8.0",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -28,20 +28,20 @@
28
28
  "dependencies": {
29
29
  "@nuxt/kit": "3.3.1",
30
30
  "@types/fs-extra": "^11.0.1",
31
- "birpc": "^0.2.8",
31
+ "birpc": "^0.2.10",
32
32
  "chalk": "^5.2.0",
33
33
  "chrome-launcher": "^0.15.1",
34
34
  "defu": "^6.1.2",
35
35
  "execa": "^7.1.1",
36
36
  "fast-glob": "^3.2.12",
37
37
  "flatted": "^3.2.7",
38
- "fs-extra": "^11.1.0",
38
+ "fs-extra": "^11.1.1",
39
39
  "launch-editor": "^2.6.0",
40
40
  "ohash": "^1.0.0",
41
41
  "pathe": "^1.1.0",
42
42
  "playwright-core": "^1.31.2",
43
43
  "radix3": "^1.0.0",
44
- "satori": "0.4.3",
44
+ "satori": "0.4.4",
45
45
  "satori-html": "^0.3.2",
46
46
  "sirv": "^2.0.2",
47
47
  "std-env": "^3.3.2",
@@ -53,8 +53,8 @@
53
53
  "yoga-wasm-web": "^0.3.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@antfu/eslint-config": "^0.36.0",
57
- "@nuxt/devtools-edge": "0.2.5-27981287.ed164c8",
56
+ "@antfu/eslint-config": "^0.37.0",
57
+ "@nuxt/devtools-edge": "0.2.5-27988848.51397f9",
58
58
  "@nuxt/module-builder": "^0.2.1",
59
59
  "@nuxt/test-utils": "3.3.1",
60
60
  "@nuxtjs/eslint-config-typescript": "^12.0.0",
@@ -64,7 +64,7 @@
64
64
  "jest-image-snapshot": "^6.1.0",
65
65
  "nuxt": "^3.3.1",
66
66
  "puppeteer": "^19.7.5",
67
- "vitest": "^0.29.3"
67
+ "vitest": "^0.29.7"
68
68
  },
69
69
  "scripts": {
70
70
  "build": "pnpm dev:prepare && pnpm build:module && pnpm build:client",