nuxt-og-image 5.0.4 → 5.1.0

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 (37) hide show
  1. package/dist/client/200.html +10 -10
  2. package/dist/client/404.html +10 -10
  3. package/dist/client/_nuxt/{DynFtWBP.js → BKhpHsoX.js} +1 -1
  4. package/dist/client/_nuxt/BLmTiKMJ.js +1 -0
  5. package/dist/client/_nuxt/{Ca79vr2h.js → DVcaHLff.js} +1 -1
  6. package/dist/client/_nuxt/DpHmQ6sx.js +3991 -0
  7. package/dist/client/_nuxt/Dy0dDCjl.js +1 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/a1854235-e297-4e1d-9aec-f8c2f24f15ac.json +1 -0
  10. package/dist/client/_nuxt/entry.Dw_RMJvc.css +1 -0
  11. package/dist/client/_nuxt/error-404.Db0f7OxX.css +1 -0
  12. package/dist/client/_nuxt/error-500.CgBP5IsV.css +1 -0
  13. package/dist/client/index.html +10 -10
  14. package/dist/module.cjs +211 -143
  15. package/dist/module.d.cts +1 -1
  16. package/dist/module.d.mts +1 -1
  17. package/dist/module.d.ts +1 -1
  18. package/dist/module.json +1 -1
  19. package/dist/module.mjs +213 -145
  20. package/dist/runtime/app/composables/mock.js +14 -0
  21. package/dist/runtime/app/utils/plugins.js +2 -0
  22. package/dist/runtime/server/og-image/context.js +19 -9
  23. package/dist/runtime/server/og-image/satori/plugins/nuxt-icon.d.ts +2 -0
  24. package/dist/runtime/server/og-image/satori/plugins/nuxt-icon.js +20 -0
  25. package/dist/runtime/server/og-image/satori/transforms/inlineCss.js +1 -1
  26. package/dist/runtime/server/og-image/satori/vnodes.js +3 -1
  27. package/dist/runtime/server/util/encoding.js +2 -2
  28. package/dist/runtime/types.d.ts +1 -0
  29. package/package.json +20 -20
  30. package/virtual.d.ts +2 -2
  31. package/dist/client/_nuxt/DGRTSKzu.js +0 -1
  32. package/dist/client/_nuxt/DRTpreTA.js +0 -3979
  33. package/dist/client/_nuxt/DTAJTTim.js +0 -1
  34. package/dist/client/_nuxt/builds/meta/f2f318ac-acd5-43ce-b2d4-31208c9cc147.json +0 -1
  35. package/dist/client/_nuxt/entry.DPIkPTtM.css +0 -1
  36. package/dist/client/_nuxt/error-404.D1BP3bDE.css +0 -1
  37. package/dist/client/_nuxt/error-500.28Afz2Fq.css +0 -1
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
3
  import { readFile, writeFile } from 'node:fs/promises';
4
- import { resolvePath, useNuxt, addTemplate, loadNuxtModuleInstance, createResolver, defineNuxtModule, addImports, addBuildPlugin, hasNuxtModule, hasNuxtModuleCompatibility, addServerPlugin, addServerHandler, addComponentsDir, addComponent, addPlugin } from '@nuxt/kit';
4
+ import { resolvePath, useNuxt, addTemplate, updateTemplates, loadNuxtModuleInstance, createResolver, defineNuxtModule, addImports, addBuildPlugin, addServerHandler, hasNuxtModule, hasNuxtModuleCompatibility, addServerPlugin, addComponentsDir, addComponent, addPlugin } from '@nuxt/kit';
5
5
  import { defu } from 'defu';
6
6
  import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
7
7
  import { hash } from 'ohash';
@@ -186,10 +186,11 @@ async function applyNitroPresetCompatibility(nitroConfig, options) {
186
186
  const { resolve } = options;
187
187
  const satoriEnabled = typeof options.compatibility?.satori !== "undefined" ? !!options.compatibility.satori : !!compatibility.satori;
188
188
  const chromiumEnabled = typeof options.compatibility?.chromium !== "undefined" ? !!options.compatibility.chromium : !!compatibility.chromium;
189
- nitroConfig.alias["#og-image/renderers/satori"] = satoriEnabled ? resolve("./runtime/server/og-image/satori/renderer") : resolve("./runtime/mock/empty");
190
- nitroConfig.alias["#og-image/renderers/chromium"] = chromiumEnabled ? resolve("./runtime/server/og-image/chromium/renderer") : resolve("./runtime/mock/empty");
189
+ const emptyMock = await resolve.resolvePath("./runtime/mock/empty");
190
+ nitroConfig.alias["#og-image/renderers/satori"] = satoriEnabled ? await resolve.resolvePath("./runtime/server/og-image/satori/renderer") : emptyMock;
191
+ nitroConfig.alias["#og-image/renderers/chromium"] = chromiumEnabled ? await resolve.resolvePath("./runtime/server/og-image/chromium/renderer") : emptyMock;
191
192
  const resolvedCompatibility = {};
192
- function applyBinding(key) {
193
+ async function applyBinding(key) {
193
194
  let binding = options.compatibility?.[key];
194
195
  if (typeof binding === "undefined")
195
196
  binding = compatibility[key];
@@ -202,15 +203,15 @@ async function applyNitroPresetCompatibility(nitroConfig, options) {
202
203
  }
203
204
  resolvedCompatibility[key] = binding;
204
205
  return {
205
- [`#og-image/bindings/${key}`]: binding === false ? resolve("./runtime/mock/empty") : resolve(`./runtime/server/og-image/bindings/${key}/${binding}`)
206
+ [`#og-image/bindings/${key}`]: binding === false ? emptyMock : await resolve.resolvePath(`./runtime/server/og-image/bindings/${key}/${binding}`)
206
207
  };
207
208
  }
208
209
  nitroConfig.alias = defu(
209
- applyBinding("chromium"),
210
- applyBinding("satori"),
211
- applyBinding("resvg"),
212
- applyBinding("sharp"),
213
- applyBinding("css-inline"),
210
+ await applyBinding("chromium"),
211
+ await applyBinding("satori"),
212
+ await applyBinding("resvg"),
213
+ await applyBinding("sharp"),
214
+ await applyBinding("css-inline"),
214
215
  nitroConfig.alias || {}
215
216
  );
216
217
  if (Object.values(compatibility).includes("wasm")) {
@@ -244,11 +245,11 @@ async function setupBuildHandler(config, resolve, nuxt = useNuxt()) {
244
245
  nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
245
246
  nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
246
247
  await applyNitroPresetCompatibility(nitroConfig, { compatibility: config.compatibility?.runtime, resolve });
247
- nitroConfig.alias.electron = resolve("./runtime/mock/proxy-cjs");
248
- nitroConfig.alias.bufferutil = resolve("./runtime/mock/proxy-cjs");
249
- nitroConfig.alias["utf-8-validate"] = resolve("./runtime/mock/proxy-cjs");
250
- nitroConfig.alias.queue = resolve("./runtime/mock/proxy-cjs");
251
- nitroConfig.alias["chromium-bidi"] = resolve("./runtime/mock/proxy-cjs");
248
+ nitroConfig.alias.electron = await resolve.resolvePath("./runtime/mock/proxy-cjs");
249
+ nitroConfig.alias.bufferutil = await resolve.resolvePath("./runtime/mock/proxy-cjs");
250
+ nitroConfig.alias["utf-8-validate"] = await resolve.resolvePath("./runtime/mock/proxy-cjs");
251
+ nitroConfig.alias.queue = await resolve.resolvePath("./runtime/mock/proxy-cjs");
252
+ nitroConfig.alias["chromium-bidi"] = await resolve.resolvePath("./runtime/mock/proxy-cjs");
252
253
  });
253
254
  nuxt.hooks.hook("nitro:init", async (nitro) => {
254
255
  const target = resolveNitroPreset(nitro.options);
@@ -325,8 +326,27 @@ function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
325
326
  };
326
327
  });
327
328
  }
329
+ const useNitro = new Promise((resolve2) => {
330
+ nuxt.hooks.hook("nitro:init", resolve2);
331
+ });
328
332
  onDevToolsInitialized(async () => {
329
- const rpc = extendServerRpc("nuxt-og-image", {});
333
+ const rpc = extendServerRpc("nuxt-og-image", {
334
+ async ejectCommunityTemplate(path) {
335
+ const [dirName, componentName] = path.split("/");
336
+ const dir = resolve(nuxt.options.rootDir, "components", dirName);
337
+ if (!existsSync(dir)) {
338
+ mkdirSync(dir);
339
+ }
340
+ const newPath = resolve(dir, componentName);
341
+ const templatePath = resolve(`./runtime/app/components/Templates/Community/${componentName}`);
342
+ const template = (await readFile(templatePath, "utf-8")).replace("{{ title }}", `{{ title }} - Ejected!`);
343
+ await writeFile(newPath, template, { encoding: "utf-8" });
344
+ await updateTemplates({ filter: (t) => t.filename.includes("nuxt-og-image/components.mjs") });
345
+ const nitro = await useNitro;
346
+ await nitro.hooks.callHook("rollup:reload");
347
+ return newPath;
348
+ }
349
+ });
330
350
  nuxt.hook("builder:watch", (e, path) => {
331
351
  path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path));
332
352
  if ((e === "change" || e.includes("link")) && (path.startsWith("pages") || path.startsWith("content"))) {
@@ -526,6 +546,10 @@ function normaliseFontInput(fonts) {
526
546
  });
527
547
  }
528
548
 
549
+ function isProviderEnabledForEnv(provider, nuxt, config) {
550
+ return nuxt.options.dev && config.compatibility?.dev?.[provider] !== false || !nuxt.options.dev && (config.compatibility?.runtime?.[provider] !== false || config.compatibility?.prerender?.[provider] !== false);
551
+ }
552
+ const defaultComponentDirs = ["OgImage", "og-image", "OgImageTemplate"];
529
553
  const module = defineNuxtModule({
530
554
  meta: {
531
555
  name: "nuxt-og-image",
@@ -548,14 +572,15 @@ const module = defineNuxtModule({
548
572
  // default is to cache the image for 3 day (72 hours)
549
573
  cacheMaxAgeSeconds: 60 * 60 * 24 * 3
550
574
  },
551
- componentDirs: ["OgImage", "OgImageTemplate"],
575
+ componentDirs: defaultComponentDirs,
552
576
  fonts: [],
553
577
  runtimeCacheStorage: true,
554
578
  debug: isDevelopment
555
579
  };
556
580
  },
557
581
  async setup(config, nuxt) {
558
- const { resolve } = createResolver(import.meta.url);
582
+ const resolver = createResolver(import.meta.url);
583
+ const { resolve } = resolver;
559
584
  const { version } = await readPackageJSON(resolve("../package.json"));
560
585
  logger.level = config.debug || nuxt.options.debug ? 4 : 3;
561
586
  if (config.enabled === false) {
@@ -587,68 +612,152 @@ const module = defineNuxtModule({
587
612
  nuxt.options.alias["#og-image-cache"] = resolve("./runtime/server/og-image/cache/mock");
588
613
  }
589
614
  }
590
- let isUsingSharp = false;
591
- const userConfiguredExtension = config.defaults.extension;
592
- const hasConfiguredJpegs = userConfiguredExtension && ["jpeg", "jpg"].includes(userConfiguredExtension);
593
- if (!!config.sharpOptions || hasConfiguredJpegs && config.defaults.renderer !== "chromium") {
594
- isUsingSharp = true;
595
- const hasSharpDependency = await hasResolvableDependency("sharp");
596
- if (hasSharpDependency && !targetCompatibility.sharp) {
597
- logger.warn(`Rendering JPEGs requires sharp which does not work with ${preset}. Images will be rendered as PNG at runtime.`);
615
+ const basePath = config.zeroRuntime ? "./runtime/server/routes/__zero-runtime" : "./runtime/server/routes";
616
+ let publicDirAbs = nuxt.options.dir.public;
617
+ if (!isAbsolute(publicDirAbs)) {
618
+ publicDirAbs = publicDirAbs in nuxt.options.alias ? nuxt.options.alias[publicDirAbs] : resolve(nuxt.options.rootDir, publicDirAbs);
619
+ }
620
+ if (isProviderEnabledForEnv("satori", nuxt, config)) {
621
+ let isUsingSharp = false;
622
+ if (isProviderEnabledForEnv("sharp", nuxt, config)) {
623
+ const userConfiguredExtension = config.defaults.extension;
624
+ const hasConfiguredJpegs = userConfiguredExtension && ["jpeg", "jpg"].includes(userConfiguredExtension);
625
+ if (!!config.sharpOptions || hasConfiguredJpegs && config.defaults.renderer !== "chromium") {
626
+ isUsingSharp = true;
627
+ const hasSharpDependency = await hasResolvableDependency("sharp");
628
+ if (hasSharpDependency && !targetCompatibility.sharp) {
629
+ logger.warn(`Rendering JPEGs requires sharp which does not work with ${preset}. Images will be rendered as PNG at runtime.`);
630
+ config.compatibility = defu(config.compatibility, {
631
+ runtime: { sharp: false }
632
+ });
633
+ } else if (!hasSharpDependency) {
634
+ logger.warn("You have enabled `JPEG` images. These require the `sharp` dependency which is missing, installing it for you.");
635
+ await ensureDependencies(["sharp"]);
636
+ logger.warn("Support for `sharp` is limited so check the compatibility guide.");
637
+ }
638
+ }
639
+ }
640
+ if (!isUsingSharp) {
598
641
  config.compatibility = defu(config.compatibility, {
599
- runtime: { sharp: false }
642
+ runtime: { sharp: false },
643
+ dev: { sharp: false },
644
+ prerender: { sharp: false }
600
645
  });
601
- } else if (!hasSharpDependency) {
602
- logger.warn("You have enabled `JPEG` images. These require the `sharp` dependency which is missing, installing it for you.");
603
- await ensureDependencies(["sharp"]);
604
- logger.warn("Support for `sharp` is limited so check the compatibility guide.");
605
646
  }
606
- }
607
- if (!isUsingSharp) {
608
- config.compatibility = defu(config.compatibility, {
609
- runtime: { sharp: false },
610
- dev: { sharp: false },
611
- prerender: { sharp: false }
647
+ if (isProviderEnabledForEnv("resvg", nuxt, config)) {
648
+ await import('@resvg/resvg-js').catch(() => {
649
+ logger.warn("ReSVG is missing dependencies for environment. Falling back to WASM version, this may slow down PNG rendering.");
650
+ config.compatibility = defu(config.compatibility, {
651
+ dev: { resvg: "wasm-fs" },
652
+ prerender: { resvg: "wasm-fs" }
653
+ });
654
+ if (targetCompatibility.resvg === "node") {
655
+ config.compatibility = defu(config.compatibility, {
656
+ runtime: { resvg: "wasm" }
657
+ });
658
+ }
659
+ });
660
+ }
661
+ if (!config.fonts.length) {
662
+ config.fonts = [
663
+ {
664
+ name: "Inter",
665
+ weight: 400,
666
+ path: resolve("./runtime/assets/Inter-normal-400.ttf.base64"),
667
+ absolutePath: true
668
+ },
669
+ {
670
+ name: "Inter",
671
+ weight: 700,
672
+ path: resolve("./runtime/assets/Inter-normal-700.ttf.base64"),
673
+ absolutePath: true
674
+ }
675
+ ];
676
+ }
677
+ const serverFontsDir = resolve(nuxt.options.buildDir, "cache", `nuxt-og-image@${version}`, "_fonts");
678
+ const fontStorage = createStorage({
679
+ driver: fsDriver({
680
+ base: serverFontsDir
681
+ })
682
+ });
683
+ config.fonts = (await Promise.all(normaliseFontInput(config.fonts).map(async (f) => {
684
+ const fontKey = `${f.name}:${f.style}:${f.weight}`;
685
+ const fontFileBase = fontKey.replaceAll(":", "-");
686
+ if (!f.key && !f.path) {
687
+ if (preset === "stackblitz") {
688
+ logger.warn(`The ${fontKey} font was skipped because remote fonts are not available in StackBlitz, please use a local font.`);
689
+ return false;
690
+ }
691
+ if (await downloadFont(f, fontStorage, config.googleFontMirror)) {
692
+ f.key = `nuxt-og-image:fonts:${fontFileBase}.ttf.base64`;
693
+ } else {
694
+ logger.warn(`Failed to download font ${fontKey}. You may be offline or behind a firewall blocking Google. Consider setting \`googleFontMirror: true\`.`);
695
+ return false;
696
+ }
697
+ } else if (f.path) {
698
+ const extension = basename(f.path.replace(".base64", "")).split(".").pop();
699
+ if (!["woff", "ttf", "otf"].includes(extension)) {
700
+ logger.warn(`The ${fontKey} font was skipped because the file extension ${extension} is not supported. Only woff, ttf and otf are supported.`);
701
+ return false;
702
+ }
703
+ if (!f.absolutePath)
704
+ f.path = resolve(publicDirAbs, withoutLeadingSlash(f.path));
705
+ if (!existsSync(f.path)) {
706
+ logger.warn(`The ${fontKey} font was skipped because the file does not exist at path ${f.path}.`);
707
+ return false;
708
+ }
709
+ const fontData = await readFile(f.path, f.path.endsWith(".base64") ? "utf-8" : "base64");
710
+ f.key = `nuxt-og-image:fonts:${fontFileBase}.${extension}.base64`;
711
+ await fontStorage.setItem(`${fontFileBase}.${extension}.base64`, fontData);
712
+ delete f.path;
713
+ delete f.absolutePath;
714
+ }
715
+ return f;
716
+ }))).filter(Boolean);
717
+ const fontKeys = config.fonts.map((f) => f.key?.split(":").pop());
718
+ const fontStorageKeys = await fontStorage.getKeys();
719
+ await Promise.all(fontStorageKeys.filter((key) => !fontKeys.includes(key)).map(async (key) => {
720
+ logger.info(`Nuxt OG Image removing outdated cached font file \`${key}\``);
721
+ await fontStorage.removeItem(key);
722
+ }));
723
+ if (!config.zeroRuntime) {
724
+ nuxt.options.nitro.serverAssets = nuxt.options.nitro.serverAssets || [];
725
+ nuxt.options.nitro.serverAssets.push({ baseName: "nuxt-og-image:fonts", dir: serverFontsDir });
726
+ }
727
+ addServerHandler({
728
+ lazy: true,
729
+ route: "/__og-image__/font/**",
730
+ handler: resolve(`${basePath}/font`)
612
731
  });
613
732
  }
614
- const hasChromeLocally = checkLocalChrome();
615
- const hasPlaywrightDependency = await hasResolvableDependency("playwright");
616
- const chromeCompatibilityFlags = {
617
- prerender: config.compatibility?.prerender?.chromium,
618
- dev: config.compatibility?.dev?.chromium,
619
- runtime: config.compatibility?.runtime?.chromium
620
- };
621
- const chromiumBinding = {
622
- dev: null,
623
- prerender: null,
624
- runtime: null
625
- };
626
- if (nuxt.options.dev) {
627
- if (isUndefinedOrTruthy(chromeCompatibilityFlags.dev))
628
- chromiumBinding.dev = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
629
- } else {
630
- if (isUndefinedOrTruthy(chromeCompatibilityFlags.prerender))
631
- chromiumBinding.prerender = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
632
- if (isUndefinedOrTruthy(chromeCompatibilityFlags.runtime))
633
- chromiumBinding.runtime = hasPlaywrightDependency ? "playwright" : null;
634
- }
635
- config.compatibility = defu(config.compatibility, {
636
- runtime: { chromium: chromiumBinding.runtime },
637
- dev: { chromium: chromiumBinding.dev },
638
- prerender: { chromium: chromiumBinding.prerender }
639
- });
640
- await import('@resvg/resvg-js').catch(() => {
641
- logger.warn("ReSVG is missing dependencies for environment. Falling back to WASM version, this may slow down PNG rendering.");
733
+ if (isProviderEnabledForEnv("chromium", nuxt, config)) {
734
+ const hasChromeLocally = checkLocalChrome();
735
+ const hasPlaywrightDependency = await hasResolvableDependency("playwright");
736
+ const chromeCompatibilityFlags = {
737
+ prerender: config.compatibility?.prerender?.chromium,
738
+ dev: config.compatibility?.dev?.chromium,
739
+ runtime: config.compatibility?.runtime?.chromium
740
+ };
741
+ const chromiumBinding = {
742
+ dev: null,
743
+ prerender: null,
744
+ runtime: null
745
+ };
746
+ if (nuxt.options.dev) {
747
+ if (isUndefinedOrTruthy(chromeCompatibilityFlags.dev))
748
+ chromiumBinding.dev = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
749
+ } else {
750
+ if (isUndefinedOrTruthy(chromeCompatibilityFlags.prerender))
751
+ chromiumBinding.prerender = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
752
+ if (isUndefinedOrTruthy(chromeCompatibilityFlags.runtime))
753
+ chromiumBinding.runtime = hasPlaywrightDependency ? "playwright" : null;
754
+ }
642
755
  config.compatibility = defu(config.compatibility, {
643
- dev: { resvg: "wasm-fs" },
644
- prerender: { resvg: "wasm-fs" }
756
+ runtime: { chromium: chromiumBinding.runtime },
757
+ dev: { chromium: chromiumBinding.dev },
758
+ prerender: { chromium: chromiumBinding.prerender }
645
759
  });
646
- if (targetCompatibility.resvg === "node") {
647
- config.compatibility = defu(config.compatibility, {
648
- runtime: { resvg: "wasm" }
649
- });
650
- }
651
- });
760
+ }
652
761
  await installNuxtSiteConfig();
653
762
  const usingNuxtContent = hasNuxtModule("@nuxt/content");
654
763
  const isNuxtContentV3 = usingNuxtContent && await hasNuxtModuleCompatibility("@nuxt/content", "^3");
@@ -660,73 +769,7 @@ const module = defineNuxtModule({
660
769
  } else if (isNuxtContentV2) {
661
770
  addServerPlugin(resolve("./runtime/server/plugins/nuxt-content-v2"));
662
771
  }
663
- if (!config.fonts.length) {
664
- config.fonts = [
665
- { name: "Inter", weight: 400, path: resolve("./runtime/assets/Inter-normal-400.ttf.base64"), absolutePath: true },
666
- { name: "Inter", weight: 700, path: resolve("./runtime/assets/Inter-normal-700.ttf.base64"), absolutePath: true }
667
- ];
668
- }
669
- let publicDirAbs = nuxt.options.dir.public;
670
- if (!isAbsolute(publicDirAbs)) {
671
- publicDirAbs = publicDirAbs in nuxt.options.alias ? nuxt.options.alias[publicDirAbs] : resolve(nuxt.options.rootDir, publicDirAbs);
672
- }
673
- const serverFontsDir = resolve(nuxt.options.buildDir, "cache", `nuxt-og-image@${version}`, "_fonts");
674
- const fontStorage = createStorage({
675
- driver: fsDriver({
676
- base: serverFontsDir
677
- })
678
- });
679
- config.fonts = (await Promise.all(normaliseFontInput(config.fonts).map(async (f) => {
680
- const fontKey = `${f.name}:${f.style}:${f.weight}`;
681
- const fontFileBase = fontKey.replaceAll(":", "-");
682
- if (!f.key && !f.path) {
683
- if (preset === "stackblitz") {
684
- logger.warn(`The ${fontKey} font was skipped because remote fonts are not available in StackBlitz, please use a local font.`);
685
- return false;
686
- }
687
- if (await downloadFont(f, fontStorage, config.googleFontMirror)) {
688
- f.key = `nuxt-og-image:fonts:${fontFileBase}.ttf.base64`;
689
- } else {
690
- logger.warn(`Failed to download font ${fontKey}. You may be offline or behind a firewall blocking Google. Consider setting \`googleFontMirror: true\`.`);
691
- return false;
692
- }
693
- } else if (f.path) {
694
- const extension = basename(f.path.replace(".base64", "")).split(".").pop();
695
- if (!["woff", "ttf", "otf"].includes(extension)) {
696
- logger.warn(`The ${fontKey} font was skipped because the file extension ${extension} is not supported. Only woff, ttf and otf are supported.`);
697
- return false;
698
- }
699
- if (!f.absolutePath)
700
- f.path = resolve(publicDirAbs, withoutLeadingSlash(f.path));
701
- if (!existsSync(f.path)) {
702
- logger.warn(`The ${fontKey} font was skipped because the file does not exist at path ${f.path}.`);
703
- return false;
704
- }
705
- const fontData = await readFile(f.path, f.path.endsWith(".base64") ? "utf-8" : "base64");
706
- f.key = `nuxt-og-image:fonts:${fontFileBase}.${extension}.base64`;
707
- await fontStorage.setItem(`${fontFileBase}.${extension}.base64`, fontData);
708
- delete f.path;
709
- delete f.absolutePath;
710
- }
711
- return f;
712
- }))).filter(Boolean);
713
- const fontKeys = config.fonts.map((f) => f.key?.split(":").pop());
714
- const fontStorageKeys = await fontStorage.getKeys();
715
- await Promise.all(fontStorageKeys.filter((key) => !fontKeys.includes(key)).map(async (key) => {
716
- logger.info(`Nuxt OG Image removing outdated cached font file \`${key}\``);
717
- await fontStorage.removeItem(key);
718
- }));
719
- if (!config.zeroRuntime) {
720
- nuxt.options.nitro.serverAssets = nuxt.options.nitro.serverAssets || [];
721
- nuxt.options.nitro.serverAssets.push({ baseName: "nuxt-og-image:fonts", dir: serverFontsDir });
722
- }
723
772
  nuxt.options.experimental.componentIslands ||= true;
724
- const basePath = config.zeroRuntime ? "./runtime/server/routes/__zero-runtime" : "./runtime/server/routes";
725
- addServerHandler({
726
- lazy: true,
727
- route: "/__og-image__/font/**",
728
- handler: resolve(`${basePath}/font`)
729
- });
730
773
  if (config.debug || nuxt.options.dev) {
731
774
  addServerHandler({
732
775
  lazy: true,
@@ -747,7 +790,18 @@ const module = defineNuxtModule({
747
790
  if (!nuxt.options.dev) {
748
791
  nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = [];
749
792
  }
750
- ["defineOgImage", "defineOgImageComponent", "defineOgImageScreenshot"].forEach((name) => {
793
+ [
794
+ "defineOgImage",
795
+ "defineOgImageComponent",
796
+ { name: "defineOgImageScreenshot", enabled: isProviderEnabledForEnv("chromium", nuxt, config) }
797
+ ].forEach((name) => {
798
+ if (typeof name === "object") {
799
+ if (!name.enabled) {
800
+ addImports({ name: name.name, from: resolve(`./runtime/app/composables/mock`) });
801
+ return;
802
+ }
803
+ name = name.name;
804
+ }
751
805
  addImports({
752
806
  name,
753
807
  from: resolve(`./runtime/app/composables/${name}`)
@@ -777,6 +831,17 @@ const module = defineNuxtModule({
777
831
  const basePluginPath = `./runtime/app/plugins${config.zeroRuntime ? "/__zero-runtime" : ""}`;
778
832
  addPlugin({ mode: "server", src: resolve(`${basePluginPath}/route-rule-og-image.server`) });
779
833
  addPlugin({ mode: "server", src: resolve(`${basePluginPath}/og-image-canonical-urls.server`) });
834
+ for (const componentDir of config.componentDirs) {
835
+ const path = resolve(nuxt.options.srcDir, "components", componentDir);
836
+ if (existsSync(path)) {
837
+ addComponentsDir({
838
+ path,
839
+ island: true
840
+ });
841
+ } else if (!defaultComponentDirs.includes(componentDir)) {
842
+ logger.warn(`The configured component directory \`./${relative$1(nuxt.options.rootDir, path)}\` does not exist. Skipping.`);
843
+ }
844
+ }
780
845
  const ogImageComponentCtx = { components: [] };
781
846
  nuxt.hook("components:extend", (components) => {
782
847
  ogImageComponentCtx.components = [];
@@ -905,20 +970,23 @@ declare module '#og-image/unocss-config' {
905
970
  // @ts-expect-error runtime type
906
971
  isNuxtContentDocumentDriven: config.strictNuxtContentPaths || !!nuxt.options.content?.documentDriven
907
972
  };
973
+ if (nuxt.options.dev) {
974
+ runtimeConfig.componentDirs = config.componentDirs;
975
+ }
908
976
  nuxt.hooks.callHook("nuxt-og-image:runtime-config", runtimeConfig);
909
977
  nuxt.options.runtimeConfig["nuxt-og-image"] = runtimeConfig;
910
978
  });
911
979
  if (nuxt.options.dev) {
912
- setupDevHandler(config, resolve);
980
+ setupDevHandler(config, resolver);
913
981
  setupDevToolsUI(config, resolve);
914
982
  } else if (isNuxtGenerate()) {
915
- setupGenerateHandler(config, resolve);
983
+ setupGenerateHandler(config, resolver);
916
984
  } else if (nuxt.options.build) {
917
- await setupBuildHandler(config, resolve);
985
+ await setupBuildHandler(config, resolver);
918
986
  }
919
987
  if (nuxt.options.build)
920
988
  addServerPlugin(resolve("./runtime/server/plugins/prerender"));
921
- setupPrerenderHandler(config, resolve);
989
+ setupPrerenderHandler(config, resolver);
922
990
  }
923
991
  });
924
992
 
@@ -1,6 +1,20 @@
1
+ import { useRuntimeConfig } from "nuxt/app";
1
2
  export function defineOgImage(_options = {}) {
3
+ if (import.meta.dev) {
4
+ console.warn("`defineOgImage()` is skipped as the OG Image module is not enabled.");
5
+ }
2
6
  }
3
7
  export function defineOgImageComponent(component, props = {}, options = {}) {
8
+ if (import.meta.dev) {
9
+ console.warn("`defineOgImageComponent()` is skipped as the OG Image module is not enabled.");
10
+ }
4
11
  }
5
12
  export function defineOgImageScreenshot(options = {}) {
13
+ if (import.meta.dev) {
14
+ if (useRuntimeConfig()["nuxt-og-image"]) {
15
+ console.warn("`defineOgImageScreenshot()` is skipped as the `chromium` compatibility is disabled.");
16
+ } else {
17
+ console.warn("`defineOgImageScreenshot()` is skipped as the OG Image module is not enabled.");
18
+ }
19
+ }
6
20
  }
@@ -1,4 +1,5 @@
1
1
  import { useRequestEvent, withSiteUrl } from "#imports";
2
+ import { TemplateParamsPlugin } from "@unhead/vue/plugins";
2
3
  import { defu } from "defu";
3
4
  import { parse, stringify } from "devalue";
4
5
  import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
@@ -14,6 +15,7 @@ export function ogImageCanonicalUrls(nuxtApp) {
14
15
  const path = parseURL(e.path).pathname;
15
16
  if (isInternalRoute(path))
16
17
  return;
18
+ ssrContext?.head.use(TemplateParamsPlugin);
17
19
  ssrContext?.head.use({
18
20
  key: "nuxt-og-image:overrides-and-canonical-urls",
19
21
  hooks: {
@@ -99,10 +99,10 @@ export async function resolveContext(e) {
99
99
  renderer = await useChromiumRenderer();
100
100
  break;
101
101
  }
102
- if (!renderer || renderer.__unenv__) {
102
+ if (!renderer || renderer.__mock__) {
103
103
  throw createError({
104
104
  statusCode: 400,
105
- statusMessage: `[Nuxt OG Image] Renderer ${options.renderer} is missing.`
105
+ statusMessage: `[Nuxt OG Image] Renderer ${options.renderer} is not enabled.`
106
106
  });
107
107
  }
108
108
  const unocss = await createGenerator({ theme }, {
@@ -133,11 +133,9 @@ function getPayloadFromHtml(html) {
133
133
  }
134
134
  export function extractAndNormaliseOgImageOptions(html) {
135
135
  const _payload = getPayloadFromHtml(html);
136
- if (!_payload)
137
- return false;
138
136
  let options = false;
139
137
  try {
140
- const payload2 = parse(_payload);
138
+ const payload2 = parse(_payload || "{}");
141
139
  Object.entries(payload2).forEach(([key, value]) => {
142
140
  if (!value && value !== 0)
143
141
  delete payload2[key];
@@ -147,9 +145,7 @@ export function extractAndNormaliseOgImageOptions(html) {
147
145
  if (import.meta.dev)
148
146
  console.warn("Failed to parse #nuxt-og-image-options", e, options);
149
147
  }
150
- if (!options)
151
- return false;
152
- if (typeof options.props?.description === "undefined") {
148
+ if (options && typeof options?.props?.description === "undefined") {
153
149
  const description = html.match(/<meta[^>]+name="description"[^>]*>/)?.[0];
154
150
  if (description) {
155
151
  const [, content] = description.match(/content="([^"]+)"/) || [];
@@ -157,7 +153,7 @@ export function extractAndNormaliseOgImageOptions(html) {
157
153
  options.props.description = content;
158
154
  }
159
155
  }
160
- const payload = decodeObjectHtmlEntities(options);
156
+ const payload = decodeObjectHtmlEntities(options || {});
161
157
  if (import.meta.dev) {
162
158
  const socialPreview = {};
163
159
  const socialMetaTags = html.match(/<meta[^>]+(property|name)="(twitter|og):([^"]+)"[^>]*>/g);
@@ -235,6 +231,20 @@ async function fetchPathHtmlAndExtractOptions(e, path, key) {
235
231
  });
236
232
  }
237
233
  if (!_payload) {
234
+ const payload2 = extractAndNormaliseOgImageOptions(html);
235
+ if (payload2?.socialPreview?.og?.image) {
236
+ const p = {
237
+ custom: true,
238
+ url: payload2.socialPreview.og.image
239
+ };
240
+ if (payload2.socialPreview.og.image["image:width"]) {
241
+ p.width = payload2.socialPreview.og.image["image:width"];
242
+ }
243
+ if (payload2.socialPreview.og.image["image:height"]) {
244
+ p.height = payload2.socialPreview.og.image["image:height"];
245
+ }
246
+ return p;
247
+ }
238
248
  return createError({
239
249
  statusCode: 500,
240
250
  statusMessage: `[Nuxt OG Image] HTML response from ${path} is missing the #nuxt-og-image-options script tag. Make sure you have defined an og image for this page.`
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../../../../types").SatoriTransformer | import("../../../../types").SatoriTransformer[];
2
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { logger } from "../../../util/logger.js";
2
+ import { defineSatoriTransformer } from "../utils.js";
3
+ export default defineSatoriTransformer([
4
+ // need to make sure parent div has flex for the emoji to render inline
5
+ {
6
+ filter: (node) => node.type === "span" && node.props?.class?.includes("iconify"),
7
+ transform: (node, e) => {
8
+ if (import.meta.dev) {
9
+ logger.warn(`When using the Nuxt Icon components in \`${e.options.component}\` you must provide \`mode="svg"\` to ensure correct rendering.`);
10
+ }
11
+ }
12
+ },
13
+ // need to make sure parent div has flex for the emoji to render inline
14
+ {
15
+ filter: (node) => node.type === "svg" && node.props?.class?.includes("iconify"),
16
+ transform: (node) => {
17
+ node.props.class = String(node.props.class).split(" ").filter((c) => !c.startsWith("iconify")).join(" ");
18
+ }
19
+ }
20
+ ]);
@@ -31,7 +31,7 @@ export async function applyInlineCss(ctx, island) {
31
31
  if (!css.trim().length)
32
32
  return false;
33
33
  const cssInline = await useCssInline();
34
- if (!cssInline || cssInline?.__unenv__) {
34
+ if (!cssInline || cssInline?.__mock__) {
35
35
  if (componentInlineStyles.length) {
36
36
  const logger = createConsola().withTag("Nuxt OG Image");
37
37
  logger.warn("To have inline styles applied you need to install either the `@css-inline/css-inline` or `@css-inline/css-inline-wasm` package.");
@@ -6,6 +6,7 @@ import emojis from "./plugins/emojis.js";
6
6
  import encoding from "./plugins/encoding.js";
7
7
  import flex from "./plugins/flex.js";
8
8
  import imageSrc from "./plugins/imageSrc.js";
9
+ import nuxtIcon from "./plugins/nuxt-icon.js";
9
10
  import unocss from "./plugins/unocss.js";
10
11
  import { applyEmojis } from "./transforms/emojis.js";
11
12
  import { applyInlineCss } from "./transforms/inlineCss.js";
@@ -28,7 +29,8 @@ export async function createVNodes(ctx) {
28
29
  emojis,
29
30
  classes,
30
31
  flex,
31
- encoding
32
+ encoding,
33
+ nuxtIcon
32
34
  ]);
33
35
  await Promise.all(walkSatoriTree(ctx, satoriTree, [
34
36
  unocss,
@@ -2,9 +2,9 @@ export function htmlDecodeQuotes(html) {
2
2
  return html.replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'");
3
3
  }
4
4
  export function decodeHtml(html) {
5
- 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(/&#(\d+);/g, (full, int) => {
5
+ return html.replace(/&lt;/g, "<").replace(/&gt;/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(/&#(\d+);/g, (full, int) => {
6
6
  return String.fromCharCode(Number.parseInt(int));
7
- });
7
+ }).replace(/&amp;/g, "&");
8
8
  }
9
9
  export function decodeObjectHtmlEntities(obj) {
10
10
  Object.entries(obj).forEach(([key, value]) => {
@@ -37,6 +37,7 @@ export interface OgImageRuntimeConfig {
37
37
  isNuxtContentDocumentDriven: boolean;
38
38
  strictNuxtContentPaths: boolean;
39
39
  zeroRuntime: boolean;
40
+ componentDirs?: string[];
40
41
  app: {
41
42
  baseURL: string;
42
43
  };