nuxt-og-image 6.7.8 → 6.9.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 (38) hide show
  1. package/dist/chunks/tw4.mjs +2 -2
  2. package/dist/chunks/uno.mjs +4 -4
  3. package/dist/cli.mjs +1 -1
  4. package/dist/devtools/nuxt.config.ts +4 -1
  5. package/dist/module.d.mts +10 -0
  6. package/dist/module.d.ts +10 -0
  7. package/dist/module.json +1 -1
  8. package/dist/module.mjs +3 -3
  9. package/dist/runtime/app/client-utils.js +4 -3
  10. package/dist/runtime/app/utils.js +3 -2
  11. package/dist/runtime/server/nitro.d.ts +9 -0
  12. package/dist/runtime/server/nitro.js +10 -0
  13. package/dist/runtime/server/og-image/bindings/fetch/node.d.ts +2 -0
  14. package/dist/runtime/server/og-image/bindings/fetch/node.js +42 -0
  15. package/dist/runtime/server/og-image/bindings/fetch/web.d.ts +7 -0
  16. package/dist/runtime/server/og-image/bindings/fetch/web.js +3 -0
  17. package/dist/runtime/server/og-image/bindings/font-assets/external-url.d.ts +0 -11
  18. package/dist/runtime/server/og-image/bindings/font-assets/external-url.js +2 -19
  19. package/dist/runtime/server/og-image/bindings/satori/harfbuzz.d.ts +4 -0
  20. package/dist/runtime/server/og-image/bindings/satori/harfbuzz.js +33 -0
  21. package/dist/runtime/server/og-image/browser/screenshot.js +4 -1
  22. package/dist/runtime/server/og-image/context.js +9 -1
  23. package/dist/runtime/server/og-image/font-subsets.d.ts +3 -11
  24. package/dist/runtime/server/og-image/font-subsets.js +50 -27
  25. package/dist/runtime/server/og-image/fonts.d.ts +1 -1
  26. package/dist/runtime/server/routes/resolve.js +2 -2
  27. package/dist/runtime/server/util/cloudflare.js +2 -2
  28. package/dist/runtime/server/util/eventHandlers.js +3 -3
  29. package/dist/runtime/server/util/ssrf.d.ts +31 -0
  30. package/dist/runtime/server/util/ssrf.js +64 -5
  31. package/dist/runtime/server/utils.d.ts +1 -1
  32. package/dist/runtime/server/utils.js +3 -3
  33. package/dist/runtime/shared.d.ts +10 -1
  34. package/dist/runtime/shared.js +2 -2
  35. package/dist/runtime/types.d.ts +5 -1
  36. package/dist/shared/{nuxt-og-image.BMHzg1Vz.mjs → nuxt-og-image.CTD0ZPNd.mjs} +634 -468
  37. package/package.json +70 -66
  38. package/types/virtual.d.ts +26 -0
@@ -1,17 +1,19 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, rmSync } from 'node:fs';
4
- import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
4
+ import { readFile, mkdir, writeFile, readdir } from 'node:fs/promises';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { resolvePath, useNuxt, addTemplate, updateTemplates, directoryToURL, tryResolveModule, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addPlugin, addComponentsDir, getNuxtVersion, addServerPlugin } from '@nuxt/kit';
6
+ import { resolvePath, useNuxt, getNuxtVersion, addTemplate, updateTemplates, directoryToURL, tryResolveModule, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addServerImports, addPlugin, addComponentsDir, addServerPlugin } from '@nuxt/kit';
7
7
  import { defu } from 'defu';
8
8
  import { fnv1a64Base36 } from 'fnv1a-64';
9
9
  import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
10
10
  import { resolveNitroPreset, renderNitroTypeAugmentations, setupNitroRuntimeCompatibility, getNuxtModuleOptions, isNuxtGenerate } from 'nuxtseo-shared/kit';
11
- import { basename, join, dirname, relative, isAbsolute, resolve } from 'pathe';
11
+ import { join, basename, dirname, relative, isAbsolute, resolve } from 'pathe';
12
12
  import { readPackageJSON } from 'pkg-types';
13
13
  import { isCI, isAgent, hasTTY } from 'std-env';
14
+ import MagicString from 'magic-string';
14
15
  import { parseAndWalk } from 'oxc-walker';
16
+ import { resolveModulePath } from 'exsolve';
15
17
  import { logger } from '../../dist/runtime/logger.js';
16
18
  import { Launcher } from 'chrome-launcher';
17
19
  import { detectPackageManager, addDependency } from 'nypm';
@@ -19,7 +21,6 @@ import { setupDevToolsUI as setupDevToolsUI$1, setupDevToolsRpc } from 'nuxtseo-
19
21
  import { createStorage } from 'unstorage';
20
22
  import fsDriver from 'unstorage/drivers/fs-lite';
21
23
  import { resolveColorMix, GRADIENT_COLOR_SPACE_RE, splitCssDeclarations } from '../../dist/runtime/server/og-image/utils/css.js';
22
- import MagicString from 'magic-string';
23
24
  import { stripLiteral } from 'strip-literal';
24
25
  import { parseURL, parseQuery } from 'ufo';
25
26
  import { createUnplugin } from 'unplugin';
@@ -27,9 +28,81 @@ import { parse } from '@vue/compiler-sfc';
27
28
  import { ofetch } from 'ofetch';
28
29
  import { parse as parse$1, walkSync, ELEMENT_NODE as ELEMENT_NODE$2, renderSync } from 'ultrahtml';
29
30
  import { RE_MATCH_EMOJIS, getEmojiCodePoint, getEmojiIconNames } from '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
30
- import { resolveModulePath } from 'exsolve';
31
31
  import { p as promptFontsMigration } from './nuxt-og-image.CJa2KCie.mjs';
32
32
 
33
+ const valueTypes = { i: 127, p: 127, j: 126, f: 125, d: 124 };
34
+ function encodeLength(value) {
35
+ const bytes = [];
36
+ do {
37
+ const byte = value & 127;
38
+ value >>>= 7;
39
+ bytes.push(value ? byte | 128 : byte);
40
+ } while (value);
41
+ return bytes;
42
+ }
43
+ function createHarfBuzzCallbackModule(signature) {
44
+ const type = (character) => {
45
+ const value = valueTypes[character];
46
+ if (value === void 0)
47
+ throw new Error(`Unsupported HarfBuzz callback type: ${character}`);
48
+ return value;
49
+ };
50
+ const params = [...signature.slice(1)].map(type);
51
+ const results = signature[0] === "v" ? [] : [type(signature[0])];
52
+ const functionType = [1, 96, ...encodeLength(params.length), ...params, results.length, ...results];
53
+ return Uint8Array.from([
54
+ 0,
55
+ 97,
56
+ 115,
57
+ 109,
58
+ 1,
59
+ 0,
60
+ 0,
61
+ 0,
62
+ 1,
63
+ ...encodeLength(functionType.length),
64
+ ...functionType,
65
+ 2,
66
+ 7,
67
+ 1,
68
+ 1,
69
+ 101,
70
+ 1,
71
+ 102,
72
+ 0,
73
+ 0,
74
+ 7,
75
+ 5,
76
+ 1,
77
+ 1,
78
+ 102,
79
+ 0,
80
+ 0
81
+ ]);
82
+ }
83
+ async function prepareHarfBuzzCallbacks(adapterPath, outputDir) {
84
+ const signatures = /* @__PURE__ */ new Set();
85
+ parseAndWalk(await readFile(adapterPath, "utf8"), adapterPath, {
86
+ enter(node) {
87
+ if (node.type !== "CallExpression" || node.callee.type !== "Identifier" || node.callee.name !== "addFunction")
88
+ return;
89
+ const signature = node.arguments[1];
90
+ if (signature?.type === "Literal" && typeof signature.value === "string")
91
+ signatures.add(signature.value);
92
+ }
93
+ });
94
+ await mkdir(outputDir, { recursive: true });
95
+ const entries = await Promise.all([...signatures].sort().map(async (signature) => {
96
+ const path = join(outputDir, `callback-${signature}.wasm`);
97
+ await writeFile(path, createHarfBuzzCallbackModule(signature));
98
+ return { signature, path };
99
+ }));
100
+ return [
101
+ ...entries.map(({ signature, path }) => `import ${signature} from ${JSON.stringify(`${path}?module`)}`),
102
+ `export default { ${entries.map(({ signature }) => signature).join(", ")} }`
103
+ ].join("\n");
104
+ }
105
+
33
106
  const isUndefinedOrTruthy = (v) => typeof v === "undefined" || v !== false;
34
107
  function checkLocalChrome() {
35
108
  if (isCI)
@@ -46,6 +119,16 @@ async function hasResolvableDependency(dep) {
46
119
  return null;
47
120
  }).then((r) => r && r !== dep);
48
121
  }
122
+ const MIN_SATORI_VERSION = "0.33.5";
123
+ function isSupportedSatoriVersion(version) {
124
+ const current = (version || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
125
+ const minimum = MIN_SATORI_VERSION.split(".").map(Number);
126
+ for (let i = 0; i < minimum.length; i++) {
127
+ if ((current[i] ?? 0) !== minimum[i])
128
+ return (current[i] ?? 0) > minimum[i];
129
+ }
130
+ return true;
131
+ }
49
132
  const VALID_RENDERER_SUFFIXES = ["satori", "browser", "takumi"];
50
133
  const RE_RENDERER_SUFFIX_CI = /\.?(satori|browser|takumi)$/i;
51
134
  const RE_RENDERER_SUFFIX = /(Satori|Browser|Takumi)$/;
@@ -119,6 +202,8 @@ const cloudflare = {
119
202
  sharp: false,
120
203
  emoji: "fetch",
121
204
  // edge size limits - use API instead of bundling 24MB icons
205
+ fetch: "web",
206
+ // no DNS API
122
207
  wasm: {
123
208
  esmImport: true
124
209
  // Do NOT set lazy here — Nitro's cloudflare preset uses lazy: false because
@@ -141,8 +226,9 @@ const WebContainer = {
141
226
  satori: "wasm-fs",
142
227
  takumi: "wasm",
143
228
  sharp: false,
144
- emoji: "fetch"
229
+ emoji: "fetch",
145
230
  // webcontainer has size constraints
231
+ fetch: "web"
146
232
  };
147
233
  const RuntimeCompatibility = {
148
234
  "nitro-dev": NodeDevRuntime,
@@ -183,6 +269,7 @@ const RuntimeCompatibility = {
183
269
  sharp: false,
184
270
  emoji: "fetch",
185
271
  // edge size limits
272
+ fetch: "web",
186
273
  wasm: {
187
274
  rollup: {
188
275
  targetEnv: "auto-inline",
@@ -203,6 +290,7 @@ const RuntimeCompatibility = {
203
290
  sharp: false,
204
291
  emoji: "fetch",
205
292
  // edge size limits - bundling 24MB icons not viable
293
+ fetch: "web",
206
294
  wasm: {
207
295
  // lowers workers kb size
208
296
  esmImport: true,
@@ -276,6 +364,36 @@ async function applyNitroPresetCompatibility(nitroConfig, options) {
276
364
  await applyBinding("sharp"),
277
365
  nitroConfig.alias || {}
278
366
  );
367
+ if (resolvedCompatibility.satori) {
368
+ const satoriPackage = await readPackageJSON(`${satoriPkgDir}/package.json`);
369
+ if (!isSupportedSatoriVersion(satoriPackage.version))
370
+ throw new Error(`[Nuxt OG Image] Satori ${satoriPackage.version} is not supported. Satori ${MIN_SATORI_VERSION} or later is required (GHSA-wx4j-mvgx-mqwp). Run: npm install satori@latest`);
371
+ const harfbuzzWasmPath = resolveModulePath("harfbuzzjs/hb.wasm", { from: `${satoriPkgDir}/package.json` });
372
+ if (resolvedCompatibility.satori === "wasm" || target.replace(RE_LEGACY_SUFFIX, "") === "netlify-edge") {
373
+ const harfbuzzDir = harfbuzzWasmPath.slice(0, -"hb.wasm".length);
374
+ nitroConfig.virtual["#og-image/harfbuzz-callbacks"] = resolvedCompatibility.satori === "wasm" ? await prepareHarfBuzzCallbacks(`${harfbuzzDir}hbjs.js`, join(useNuxt().options.buildDir, "cache/og-image/harfbuzz")) : "export default undefined";
375
+ Object.assign(nitroConfig.alias, {
376
+ "harfbuzzjs": await resolve.resolvePath("./runtime/server/og-image/bindings/satori/harfbuzz"),
377
+ "#og-image/harfbuzz-factory": `${harfbuzzDir}hb.js`,
378
+ "#og-image/harfbuzz-adapter": `${harfbuzzDir}hbjs.js`
379
+ });
380
+ if (resolvedCompatibility.satori === "wasm") {
381
+ nitroConfig.alias["#og-image/harfbuzz-wasm"] = `${harfbuzzWasmPath}?module`;
382
+ } else {
383
+ const base64 = (await readFile(harfbuzzWasmPath)).toString("base64");
384
+ nitroConfig.virtual["#og-image/harfbuzz-wasm"] = `export default imports => WebAssembly.instantiate(Uint8Array.from(atob(${JSON.stringify(base64)}), c => c.charCodeAt(0)), imports)`;
385
+ }
386
+ } else if (Number.parseInt(getNuxtVersion(useNuxt()), 10) >= 5) {
387
+ const config = nitroConfig;
388
+ config.traceDeps = [.../* @__PURE__ */ new Set([...config.traceDeps || [], "harfbuzzjs*"])];
389
+ } else {
390
+ nitroConfig.externals = nitroConfig.externals || {};
391
+ nitroConfig.externals.traceInclude = [.../* @__PURE__ */ new Set([
392
+ ...nitroConfig.externals.traceInclude || [],
393
+ harfbuzzWasmPath
394
+ ])];
395
+ }
396
+ }
279
397
  if (Object.values(resolvedCompatibility).includes("wasm")) {
280
398
  nitroConfig.experimental = nitroConfig.experimental || {};
281
399
  nitroConfig.experimental.wasm = true;
@@ -334,6 +452,27 @@ async function setupBuildHandler(config, resolve, getDetectedRenderers, getCompa
334
452
  detectedRenderers: getDetectedRenderers(),
335
453
  metadata: getCompatibilityMeta()
336
454
  });
455
+ const rollupConfig = nitro.options.rollupConfig ||= { output: {} };
456
+ rollupConfig.plugins = [rollupConfig.plugins, {
457
+ name: "og-image-harfbuzz-worker",
458
+ transform(code, id) {
459
+ if (!nitro.options.alias["#og-image/harfbuzz-factory"] || !id.endsWith("/harfbuzzjs/hb.js"))
460
+ return;
461
+ const transformed = new MagicString(code);
462
+ parseAndWalk(code, id, {
463
+ enter(node) {
464
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.id.name === "convertJsFunctionToWasm" && node.init) {
465
+ transformed.prependLeft(node.init.start, 'Module["convertJsFunctionToWasm"] || (');
466
+ transformed.appendRight(node.init.end, ")");
467
+ }
468
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && ["ENVIRONMENT_IS_NODE", "ENVIRONMENT_IS_WEB", "ENVIRONMENT_IS_WORKER"].includes(node.id.name) && node.init) {
469
+ transformed.overwrite(node.init.start, node.init.end, "false");
470
+ }
471
+ }
472
+ });
473
+ return { code: transformed.toString(), map: transformed.generateMap({ hires: true }).toString() };
474
+ }
475
+ }];
337
476
  const target = resolveOgImagePreset(nitro.options);
338
477
  const normalizedTarget = target.replace(RE_LEGACY_SUFFIX, "");
339
478
  const isEdgePreset = ["cloudflare", "cloudflare-pages", "cloudflare-pages-static", "cloudflare-module", "cloudflare-durable", "vercel-edge", "netlify-edge"].includes(normalizedTarget);
@@ -2086,8 +2225,7 @@ async function initFontless(options) {
2086
2225
  {
2087
2226
  dev: false,
2088
2227
  renderedFontURLs,
2089
- assetsBaseURL: FONTS_URL_PREFIX,
2090
- callback: (filename, url) => renderedFontURLs.set(filename, url)
2228
+ assetsBaseURL: FONTS_URL_PREFIX
2091
2229
  },
2092
2230
  faces
2093
2231
  ),
@@ -2111,7 +2249,7 @@ async function initFontless(options) {
2111
2249
  providers
2112
2250
  });
2113
2251
  options.logger?.debug(`fontless initialized with formats: ['woff', 'ttf'], subsets: ${JSON.stringify(options.fontSubsets || ["latin"])}, priority: ${JSON.stringify(nuxtFontsConfig?.priority || ["google", "bunny", "fontsource"])}`);
2114
- options.nuxt._ogImageFontless = { resolver, renderedFontURLs, providerNames: priority };
2252
+ options.nuxt._ogImageFontless = { resolver, providerNames: priority };
2115
2253
  }
2116
2254
  function persistFontUrlMapping(options) {
2117
2255
  if (!options.fontContext?.renderedFontURLs.size)
@@ -2757,11 +2895,10 @@ function isVue(id, opts = {}) {
2757
2895
  }
2758
2896
  return true;
2759
2897
  }
2760
- const JS_RE = /\.(?:[cm]?j|t)sx?$/;
2761
- function isJS(id) {
2762
- const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
2763
- return JS_RE.test(pathname);
2764
- }
2898
+ const VUE_RE = /\.vue(?:\?|$)/;
2899
+ const JS_RE = /\.[cm]?[jt]sx?(?:\?|$)/;
2900
+ const ISLANDS_RE = /components\.islands\.mjs(?:\?|$)/;
2901
+ const COMPOSABLE_CODE_MARKER = "defineOgImage";
2765
2902
  const TreeShakeComposablesPlugin = createUnplugin(() => {
2766
2903
  const composableNames = [
2767
2904
  "defineOgImage",
@@ -2774,21 +2911,26 @@ const TreeShakeComposablesPlugin = createUnplugin(() => {
2774
2911
  return {
2775
2912
  name: "nuxt-og-image:zero-runtime:transform",
2776
2913
  enforce: "pre",
2777
- transformInclude(id) {
2778
- return isVue(id, { type: ["script"] }) || isJS(id);
2779
- },
2780
- transform(code, id) {
2781
- const s = new MagicString(code);
2782
- if (!id.endsWith("components.islands.mjs")) {
2783
- const strippedCode = stripLiteral(code);
2914
+ transform: {
2915
+ filter: {
2916
+ // @todo re-implement composable tree-shaking for island files
2917
+ id: { include: [VUE_RE, JS_RE], exclude: [ISLANDS_RE] },
2918
+ code: COMPOSABLE_CODE_MARKER
2919
+ },
2920
+ handler(code, id) {
2921
+ if (VUE_RE.test(id) && !isVue(id, { type: ["script"] })) {
2922
+ return;
2923
+ }
2784
2924
  if (!COMPOSABLE_RE.test(code)) {
2785
2925
  return;
2786
2926
  }
2787
- for (const match of strippedCode.matchAll(COMPOSABLE_RE_GLOBAL)) {
2927
+ const s = new MagicString(code);
2928
+ for (const match of stripLiteral(code).matchAll(COMPOSABLE_RE_GLOBAL)) {
2788
2929
  s.overwrite(match.index, match.index + match[0].length, `${match[1]} import.meta.prerender && ${match[2]}`);
2789
2930
  }
2790
- }
2791
- if (s.hasChanged()) {
2931
+ if (!s.hasChanged()) {
2932
+ return;
2933
+ }
2792
2934
  return {
2793
2935
  code: s.toString(),
2794
2936
  map: s.generateMap({ hires: true })
@@ -3304,6 +3446,7 @@ const RE_SVG_ID_ATTR = /\bid="([^"]+)"/g;
3304
3446
  const RE_SVG_VIEWBOX = /viewBox="([^"]*)"/;
3305
3447
  const RE_SVG_BODY = /<svg[^>]*>([\s\S]*)<\/svg>/;
3306
3448
  const RE_OG_IMAGE_QUERY$1 = /\?og-image(?:-depth=\d+)?$/;
3449
+ const RE_VUE_ID$1 = /\.vue(?:\?|$)/;
3307
3450
  const RE_TEXT_BETWEEN_TAGS = />([^<]*)</g;
3308
3451
  const RE_SPECIAL_REGEX_CHARS = /[.*+?^${}()|[\]\\]/g;
3309
3452
  const RE_WRAPPER_DIV_START = /^<div>/;
@@ -3542,419 +3685,424 @@ const AssetTransformPlugin = createUnplugin((options) => {
3542
3685
  return {
3543
3686
  name: "nuxt-og-image:asset-transform",
3544
3687
  enforce: "pre",
3545
- transformInclude(id) {
3546
- if (id.includes("?og-image"))
3547
- return true;
3548
- if (!id.endsWith(".vue"))
3549
- return false;
3550
- return options.ogComponentPaths.some((dir) => id.startsWith(`${dir}/`) || id.startsWith(`${dir}\\`));
3551
- },
3552
- async transform(code, rawId) {
3553
- const id = rawId.replace(RE_OG_IMAGE_QUERY$1, "");
3554
- if (rawId.includes("?og-image"))
3555
- options.onNestedTransform?.(id);
3556
- const bounds = getOuterTemplateBounds(code);
3557
- if (!bounds)
3558
- return;
3559
- const s = new MagicString(code);
3560
- const { templateStart, templateEnd } = bounds;
3561
- let template = code.slice(templateStart, templateEnd);
3562
- let hasChanges = false;
3563
- if (options.emojiSet && RE_MATCH_EMOJIS.test(template)) {
3564
- if (!emojiIcons) {
3565
- emojiIcons = options.emojiIcons ?? await import(`@iconify-json/${options.emojiSet}/icons.json`, { with: { type: 'json' } }).then((m) => m.default).catch(() => {
3566
- return null;
3567
- });
3568
- }
3569
- RE_MATCH_EMOJIS.lastIndex = 0;
3570
- const allEmojis = /* @__PURE__ */ new Set();
3571
- for (const match of template.matchAll(RE_MATCH_EMOJIS)) {
3572
- allEmojis.add(match[0]);
3688
+ transform: {
3689
+ filter: {
3690
+ id: RE_VUE_ID$1
3691
+ },
3692
+ async handler(code, rawId) {
3693
+ const isNested = RE_OG_IMAGE_QUERY$1.test(rawId);
3694
+ if (!isNested) {
3695
+ if (!rawId.endsWith(".vue"))
3696
+ return;
3697
+ if (!options.ogComponentPaths.some((dir) => rawId.startsWith(`${dir}/`) || rawId.startsWith(`${dir}\\`)))
3698
+ return;
3573
3699
  }
3574
- const emojiSvgMap = /* @__PURE__ */ new Map();
3575
- await Promise.all(Array.from(allEmojis, async (emoji) => {
3576
- let svg = null;
3577
- if (emojiIcons) {
3578
- svg = buildEmojiSvg(emoji, emojiIcons, options.emojiSet);
3700
+ const id = rawId.replace(RE_OG_IMAGE_QUERY$1, "");
3701
+ if (isNested)
3702
+ options.onNestedTransform?.(id);
3703
+ const bounds = getOuterTemplateBounds(code);
3704
+ if (!bounds)
3705
+ return;
3706
+ const s = new MagicString(code);
3707
+ const { templateStart, templateEnd } = bounds;
3708
+ let template = code.slice(templateStart, templateEnd);
3709
+ let hasChanges = false;
3710
+ if (options.emojiSet && RE_MATCH_EMOJIS.test(template)) {
3711
+ if (!emojiIcons) {
3712
+ emojiIcons = options.emojiIcons ?? await import(`@iconify-json/${options.emojiSet}/icons.json`, { with: { type: 'json' } }).then((m) => m.default).catch(() => {
3713
+ return null;
3714
+ });
3579
3715
  }
3580
- if (!svg) {
3581
- svg = await fetchEmojiSvg(emoji, options.emojiSet);
3716
+ RE_MATCH_EMOJIS.lastIndex = 0;
3717
+ const allEmojis = /* @__PURE__ */ new Set();
3718
+ for (const match of template.matchAll(RE_MATCH_EMOJIS)) {
3719
+ allEmojis.add(match[0]);
3582
3720
  }
3583
- if (svg) {
3584
- emojiSvgMap.set(emoji, svg);
3721
+ const emojiSvgMap = /* @__PURE__ */ new Map();
3722
+ await Promise.all(Array.from(allEmojis, async (emoji) => {
3723
+ let svg = null;
3724
+ if (emojiIcons) {
3725
+ svg = buildEmojiSvg(emoji, emojiIcons, options.emojiSet);
3726
+ }
3727
+ if (!svg) {
3728
+ svg = await fetchEmojiSvg(emoji, options.emojiSet);
3729
+ }
3730
+ if (svg) {
3731
+ emojiSvgMap.set(emoji, svg);
3732
+ }
3733
+ }));
3734
+ if (emojiSvgMap.size > 0) {
3735
+ RE_TEXT_BETWEEN_TAGS.lastIndex = 0;
3736
+ template = template.replace(RE_TEXT_BETWEEN_TAGS, (fullMatch, textContent) => {
3737
+ if (!textContent)
3738
+ return fullMatch;
3739
+ RE_MATCH_EMOJIS.lastIndex = 0;
3740
+ const emojiMatches = [...textContent.matchAll(RE_MATCH_EMOJIS)];
3741
+ if (!emojiMatches.length)
3742
+ return fullMatch;
3743
+ let newTextContent = textContent;
3744
+ for (const match of emojiMatches) {
3745
+ const emoji = match[0];
3746
+ const svg = emojiSvgMap.get(emoji);
3747
+ if (svg) {
3748
+ hasChanges = true;
3749
+ const escaped = emoji.replace(RE_SPECIAL_REGEX_CHARS, "\\$&");
3750
+ newTextContent = newTextContent.replace(new RegExp(escaped, "g"), svg);
3751
+ }
3752
+ }
3753
+ return `>${newTextContent}<`;
3754
+ });
3585
3755
  }
3586
- }));
3587
- if (emojiSvgMap.size > 0) {
3588
- RE_TEXT_BETWEEN_TAGS.lastIndex = 0;
3589
- template = template.replace(RE_TEXT_BETWEEN_TAGS, (fullMatch, textContent) => {
3590
- if (!textContent)
3591
- return fullMatch;
3592
- RE_MATCH_EMOJIS.lastIndex = 0;
3593
- const emojiMatches = [...textContent.matchAll(RE_MATCH_EMOJIS)];
3594
- if (!emojiMatches.length)
3595
- return fullMatch;
3596
- let newTextContent = textContent;
3597
- for (const match of emojiMatches) {
3598
- const emoji = match[0];
3599
- const svg = emojiSvgMap.get(emoji);
3600
- if (svg) {
3601
- hasChanges = true;
3602
- const escaped = emoji.replace(RE_SPECIAL_REGEX_CHARS, "\\$&");
3603
- newTextContent = newTextContent.replace(new RegExp(escaped, "g"), svg);
3756
+ }
3757
+ if (template.includes("<Icon") || template.includes("<UIcon")) {
3758
+ const prefixes = /* @__PURE__ */ new Set();
3759
+ const iconElements = [];
3760
+ const wrappedTemplate = `<div>${template}</div>`;
3761
+ const doc = parse$1(wrappedTemplate);
3762
+ walkSync(doc, (node, parent, index) => {
3763
+ if (node.type === ELEMENT_NODE$2) {
3764
+ const el = node;
3765
+ if (el.name === "Icon" || el.name === "UIcon") {
3766
+ const iconName = el.attributes.name;
3767
+ if (iconName && !iconName.includes("{")) {
3768
+ const [prefix] = iconName.split(":");
3769
+ if (prefix)
3770
+ prefixes.add(prefix);
3771
+ iconElements.push({ node: el, parent, index });
3772
+ }
3604
3773
  }
3605
3774
  }
3606
- return `>${newTextContent}<`;
3607
3775
  });
3608
- }
3609
- }
3610
- if (template.includes("<Icon") || template.includes("<UIcon")) {
3611
- const prefixes = /* @__PURE__ */ new Set();
3612
- const iconElements = [];
3613
- const wrappedTemplate = `<div>${template}</div>`;
3614
- const doc = parse$1(wrappedTemplate);
3615
- walkSync(doc, (node, parent, index) => {
3616
- if (node.type === ELEMENT_NODE$2) {
3617
- const el = node;
3618
- if (el.name === "Icon" || el.name === "UIcon") {
3776
+ const iconSets = /* @__PURE__ */ new Map();
3777
+ for (const prefix of prefixes) {
3778
+ const icons = await loadIconSet(prefix);
3779
+ if (icons)
3780
+ iconSets.set(prefix, icons);
3781
+ }
3782
+ if (iconElements.length > 0) {
3783
+ let anyReplaced = false;
3784
+ for (const { node: el, parent, index } of iconElements) {
3619
3785
  const iconName = el.attributes.name;
3620
- if (iconName && !iconName.includes("{")) {
3621
- const [prefix] = iconName.split(":");
3622
- if (prefix)
3623
- prefixes.add(prefix);
3624
- iconElements.push({ node: el, parent, index });
3786
+ if (!iconName)
3787
+ continue;
3788
+ const [prefix, name] = iconName.split(":");
3789
+ if (!prefix || !name)
3790
+ continue;
3791
+ const icons = iconSets.get(prefix);
3792
+ const iconData = icons?.icons?.[name];
3793
+ let svgHtml;
3794
+ if (iconData) {
3795
+ svgHtml = buildIconSvg(iconData, icons.width || 24, icons.height || 24, el.attributes);
3796
+ } else if (options.cssProvider?.resolveIcon) {
3797
+ const resolved = await options.cssProvider.resolveIcon(prefix, name);
3798
+ if (resolved)
3799
+ svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, el.attributes);
3800
+ }
3801
+ if (!svgHtml)
3802
+ continue;
3803
+ anyReplaced = true;
3804
+ hasChanges = true;
3805
+ const svgDoc = parse$1(svgHtml);
3806
+ if ("children" in parent && Array.isArray(parent.children)) {
3807
+ parent.children[index] = svgDoc.children[0];
3625
3808
  }
3626
3809
  }
3810
+ if (anyReplaced) {
3811
+ const rendered = renderSync(doc);
3812
+ template = rendered.replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
3813
+ }
3627
3814
  }
3628
- });
3629
- const iconSets = /* @__PURE__ */ new Map();
3630
- for (const prefix of prefixes) {
3631
- const icons = await loadIconSet(prefix);
3632
- if (icons)
3633
- iconSets.set(prefix, icons);
3634
3815
  }
3635
- if (iconElements.length > 0) {
3636
- let anyReplaced = false;
3637
- for (const { node: el, parent, index } of iconElements) {
3638
- const iconName = el.attributes.name;
3639
- if (!iconName)
3640
- continue;
3641
- const [prefix, name] = iconName.split(":");
3642
- if (!prefix || !name)
3816
+ if (options.cssProvider?.resolveIcon) {
3817
+ const replacements = [];
3818
+ RE_ICON_ELEMENT.lastIndex = 0;
3819
+ let elMatch;
3820
+ while ((elMatch = RE_ICON_ELEMENT.exec(template)) !== null) {
3821
+ const [fullMatch, , attrsStr, classValue] = elMatch;
3822
+ if (!classValue)
3643
3823
  continue;
3644
- const icons = iconSets.get(prefix);
3645
- const iconData = icons?.icons?.[name];
3646
- let svgHtml;
3647
- if (iconData) {
3648
- svgHtml = buildIconSvg(iconData, icons.width || 24, icons.height || 24, el.attributes);
3649
- } else if (options.cssProvider?.resolveIcon) {
3650
- const resolved = await options.cssProvider.resolveIcon(prefix, name);
3651
- if (resolved)
3652
- svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, el.attributes);
3824
+ const classes = classValue.split(RE_WHITESPACE).filter(Boolean);
3825
+ let iconPrefix;
3826
+ let iconName;
3827
+ for (const cls of classes) {
3828
+ const im = cls.match(ICON_CLASS_RE);
3829
+ if (im?.[1] && im[2]) {
3830
+ iconPrefix = im[1];
3831
+ iconName = im[2];
3832
+ break;
3833
+ }
3653
3834
  }
3654
- if (!svgHtml)
3835
+ if (!iconPrefix || !iconName)
3655
3836
  continue;
3656
- anyReplaced = true;
3657
- hasChanges = true;
3658
- const svgDoc = parse$1(svgHtml);
3659
- if ("children" in parent && Array.isArray(parent.children)) {
3660
- parent.children[index] = svgDoc.children[0];
3837
+ const resolved = await options.cssProvider.resolveIcon(iconPrefix, iconName);
3838
+ if (!resolved)
3839
+ continue;
3840
+ const attrs = {};
3841
+ RE_HTML_ATTR.lastIndex = 0;
3842
+ let am;
3843
+ while ((am = RE_HTML_ATTR.exec(attrsStr)) !== null) {
3844
+ if (am[1] && am[1] !== "class" && am[1] !== ":style" && am[1] !== "v-bind:style")
3845
+ attrs[am[1]] = am[2] ?? "";
3661
3846
  }
3847
+ const otherClasses = classes.filter((c) => !isIconClass(c));
3848
+ if (otherClasses.length > 0)
3849
+ attrs.class = otherClasses.join(" ");
3850
+ const svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, attrs);
3851
+ replacements.push({ from: fullMatch, to: svgHtml });
3662
3852
  }
3663
- if (anyReplaced) {
3664
- const rendered = renderSync(doc);
3665
- template = rendered.replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
3853
+ for (const { from, to } of replacements) {
3854
+ template = template.replace(from, to);
3855
+ hasChanges = true;
3666
3856
  }
3667
3857
  }
3668
- }
3669
- if (options.cssProvider?.resolveIcon) {
3670
- const replacements = [];
3671
- RE_ICON_ELEMENT.lastIndex = 0;
3672
- let elMatch;
3673
- while ((elMatch = RE_ICON_ELEMENT.exec(template)) !== null) {
3674
- const [fullMatch, , attrsStr, classValue] = elMatch;
3675
- if (!classValue)
3676
- continue;
3677
- const classes = classValue.split(RE_WHITESPACE).filter(Boolean);
3678
- let iconPrefix;
3679
- let iconName;
3680
- for (const cls of classes) {
3681
- const im = cls.match(ICON_CLASS_RE);
3682
- if (im?.[1] && im[2]) {
3683
- iconPrefix = im[1];
3684
- iconName = im[2];
3685
- break;
3686
- }
3687
- }
3688
- if (!iconPrefix || !iconName)
3689
- continue;
3690
- const resolved = await options.cssProvider.resolveIcon(iconPrefix, iconName);
3691
- if (!resolved)
3692
- continue;
3693
- const attrs = {};
3694
- RE_HTML_ATTR.lastIndex = 0;
3695
- let am;
3696
- while ((am = RE_HTML_ATTR.exec(attrsStr)) !== null) {
3697
- if (am[1] && am[1] !== "class" && am[1] !== ":style" && am[1] !== "v-bind:style")
3698
- attrs[am[1]] = am[2] ?? "";
3858
+ const rootAttrs = {};
3859
+ const rootAttrMatch = template.match(RE_ROOT_ELEMENT);
3860
+ if (rootAttrMatch?.[2]) {
3861
+ RE_DATA_ATTR.lastIndex = 0;
3862
+ for (const m of rootAttrMatch[2].matchAll(RE_DATA_ATTR)) {
3863
+ if (m[1] && m[2])
3864
+ rootAttrs[`data-${m[1]}`] = m[2];
3699
3865
  }
3700
- const otherClasses = classes.filter((c) => !isIconClass(c));
3701
- if (otherClasses.length > 0)
3702
- attrs.class = otherClasses.join(" ");
3703
- const svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, attrs);
3704
- replacements.push({ from: fullMatch, to: svgHtml });
3705
- }
3706
- for (const { from, to } of replacements) {
3707
- template = template.replace(from, to);
3708
- hasChanges = true;
3709
- }
3710
- }
3711
- const rootAttrs = {};
3712
- const rootAttrMatch = template.match(RE_ROOT_ELEMENT);
3713
- if (rootAttrMatch?.[2]) {
3714
- RE_DATA_ATTR.lastIndex = 0;
3715
- for (const m of rootAttrMatch[2].matchAll(RE_DATA_ATTR)) {
3716
- if (m[1] && m[2])
3717
- rootAttrs[`data-${m[1]}`] = m[2];
3718
3866
  }
3719
- }
3720
- if (!rootAttrs["data-theme"] && options.colorPreference)
3721
- rootAttrs["data-theme"] = options.colorPreference;
3722
- const hasDynamicDataTheme = !!(rootAttrMatch?.[2] && RE_DYNAMIC_DATA_THEME.test(` ${rootAttrMatch[2]}`));
3723
- if (options.cssProvider) {
3724
- options.beforeCssResolve?.();
3725
- try {
3726
- const fullCode = code.slice(0, templateStart) + template + code.slice(templateEnd);
3727
- const result = await transformVueTemplate(fullCode, {
3728
- resolveStyles: async (classes) => {
3729
- const supported = classes.filter((cls) => !isUnsupportedClass(cls) && !isIconClass(cls));
3730
- const unsupported = classes.filter((cls) => isUnsupportedClass(cls));
3731
- if (unsupported.length > 0) {
3732
- const componentName2 = id.split("/").pop();
3733
- logger.warn(`[nuxt-og-image] ${componentName2}: Filtered unsupported classes: ${unsupported.join(", ")}`);
3867
+ if (!rootAttrs["data-theme"] && options.colorPreference)
3868
+ rootAttrs["data-theme"] = options.colorPreference;
3869
+ const hasDynamicDataTheme = !!(rootAttrMatch?.[2] && RE_DYNAMIC_DATA_THEME.test(` ${rootAttrMatch[2]}`));
3870
+ if (options.cssProvider) {
3871
+ options.beforeCssResolve?.();
3872
+ try {
3873
+ const fullCode = code.slice(0, templateStart) + template + code.slice(templateEnd);
3874
+ const result = await transformVueTemplate(fullCode, {
3875
+ resolveStyles: async (classes) => {
3876
+ const supported = classes.filter((cls) => !isUnsupportedClass(cls) && !isIconClass(cls));
3877
+ const unsupported = classes.filter((cls) => isUnsupportedClass(cls));
3878
+ if (unsupported.length > 0) {
3879
+ const componentName2 = id.split("/").pop();
3880
+ logger.warn(`[nuxt-og-image] ${componentName2}: Filtered unsupported classes: ${unsupported.join(", ")}`);
3881
+ }
3882
+ const componentName = id.split("/").pop()?.replace(RE_FILE_EXTENSION, "");
3883
+ if (hasDynamicDataTheme) {
3884
+ const lightAttrs = { ...rootAttrs, "data-theme": "light" };
3885
+ const darkAttrs = { ...rootAttrs, "data-theme": "dark" };
3886
+ const [lightResolved, darkResolved] = await Promise.all([
3887
+ options.cssProvider.resolveClassesToStyles(supported, componentName, lightAttrs),
3888
+ options.cssProvider.resolveClassesToStyles(supported, componentName, darkAttrs)
3889
+ ]);
3890
+ return mergeThemePairs(supported, lightResolved, darkResolved);
3891
+ }
3892
+ return options.cssProvider.resolveClassesToStyles(supported, componentName, rootAttrs);
3734
3893
  }
3735
- const componentName = id.split("/").pop()?.replace(RE_FILE_EXTENSION, "");
3736
- if (hasDynamicDataTheme) {
3737
- const lightAttrs = { ...rootAttrs, "data-theme": "light" };
3738
- const darkAttrs = { ...rootAttrs, "data-theme": "dark" };
3739
- const [lightResolved, darkResolved] = await Promise.all([
3740
- options.cssProvider.resolveClassesToStyles(supported, componentName, lightAttrs),
3741
- options.cssProvider.resolveClassesToStyles(supported, componentName, darkAttrs)
3742
- ]);
3743
- return mergeThemePairs(supported, lightResolved, darkResolved);
3894
+ });
3895
+ if (result) {
3896
+ const newBounds = getOuterTemplateBounds(result.code);
3897
+ if (newBounds) {
3898
+ template = result.code.slice(newBounds.templateStart, newBounds.templateEnd);
3899
+ hasChanges = true;
3744
3900
  }
3745
- return options.cssProvider.resolveClassesToStyles(supported, componentName, rootAttrs);
3746
- }
3747
- });
3748
- if (result) {
3749
- const newBounds = getOuterTemplateBounds(result.code);
3750
- if (newBounds) {
3751
- template = result.code.slice(newBounds.templateStart, newBounds.templateEnd);
3752
- hasChanges = true;
3753
3901
  }
3902
+ } catch (err) {
3903
+ const componentName = id.split("/").pop();
3904
+ logger.warn(`[nuxt-og-image] ${componentName}: CSS template transform failed, using original template`, err.message);
3754
3905
  }
3755
- } catch (err) {
3756
- const componentName = id.split("/").pop();
3757
- logger.warn(`[nuxt-og-image] ${componentName}: CSS template transform failed, using original template`, err.message);
3758
3906
  }
3759
- }
3760
- if (template.includes("var(")) {
3761
- const vars = options.cssProvider?.getVars ? await options.cssProvider.getVars(rootAttrs) : /* @__PURE__ */ new Map();
3762
- RE_INLINE_STYLE.lastIndex = 0;
3763
- for (const m of template.matchAll(RE_INLINE_STYLE)) {
3764
- RE_CSS_VAR_DEF.lastIndex = 0;
3765
- for (const def of m[1].matchAll(RE_CSS_VAR_DEF)) {
3766
- const name = `--${def[1]}`;
3767
- if (!vars.has(name))
3768
- vars.set(name, def[2].trim());
3769
- }
3770
- }
3771
- if (vars.size > 0) {
3772
- const replacements = [];
3773
- RE_NON_STYLE_VAR_ATTR.lastIndex = 0;
3774
- template.replace(RE_NON_STYLE_VAR_ATTR, (match, attr, value) => {
3775
- const resolved = resolveCssVars(value, vars);
3776
- if (resolved !== value) {
3777
- replacements.push({ from: match, to: "", attr, value: resolved });
3907
+ if (template.includes("var(")) {
3908
+ const vars = options.cssProvider?.getVars ? await options.cssProvider.getVars(rootAttrs) : /* @__PURE__ */ new Map();
3909
+ RE_INLINE_STYLE.lastIndex = 0;
3910
+ for (const m of template.matchAll(RE_INLINE_STYLE)) {
3911
+ RE_CSS_VAR_DEF.lastIndex = 0;
3912
+ for (const def of m[1].matchAll(RE_CSS_VAR_DEF)) {
3913
+ const name = `--${def[1]}`;
3914
+ if (!vars.has(name))
3915
+ vars.set(name, def[2].trim());
3778
3916
  }
3779
- return match;
3780
- });
3781
- for (const r of replacements) {
3782
- let value = r.value;
3783
- if (RE_COLOR_ATTR_NAME.test(r.attr)) {
3784
- value = await downlevelColor(r.attr, value);
3785
- }
3786
- r.to = `${r.attr}="${escapeAttrValue(value)}"`;
3787
- template = template.replace(r.from, r.to);
3788
- hasChanges = true;
3789
3917
  }
3790
- const styleReplacements = [];
3791
- RE_STYLE_VAR_ATTR.lastIndex = 0;
3792
- template.replace(RE_STYLE_VAR_ATTR, (match, styleValue) => {
3793
- const resolved = resolveCssVars(styleValue, vars);
3794
- if (resolved !== styleValue)
3795
- styleReplacements.push({ from: match, content: resolved });
3796
- return match;
3797
- });
3798
- for (const r of styleReplacements) {
3799
- const declarations = r.content.split(";").filter(Boolean);
3800
- const downleveled = [];
3801
- for (const decl of declarations) {
3802
- const colonIdx = decl.indexOf(":");
3803
- if (colonIdx === -1) {
3804
- downleveled.push(decl);
3805
- continue;
3918
+ if (vars.size > 0) {
3919
+ const replacements = [];
3920
+ RE_NON_STYLE_VAR_ATTR.lastIndex = 0;
3921
+ template.replace(RE_NON_STYLE_VAR_ATTR, (match, attr, value) => {
3922
+ const resolved = resolveCssVars(value, vars);
3923
+ if (resolved !== value) {
3924
+ replacements.push({ from: match, to: "", attr, value: resolved });
3806
3925
  }
3807
- const prop = decl.slice(0, colonIdx).trim();
3808
- let value = decl.slice(colonIdx + 1).trim();
3809
- if (RE_COLOR_PROP_NAME.test(prop) && !value.includes("var(")) {
3810
- value = await downlevelColor(prop, value);
3811
- if (value.includes("color-mix("))
3812
- value = resolveColorMix(value);
3926
+ return match;
3927
+ });
3928
+ for (const r of replacements) {
3929
+ let value = r.value;
3930
+ if (RE_COLOR_ATTR_NAME.test(r.attr)) {
3931
+ value = await downlevelColor(r.attr, value);
3813
3932
  }
3814
- downleveled.push(`${prop}: ${value}`);
3933
+ r.to = `${r.attr}="${escapeAttrValue(value)}"`;
3934
+ template = template.replace(r.from, r.to);
3935
+ hasChanges = true;
3815
3936
  }
3816
- const to = `style="${escapeAttrValue(downleveled.join("; "))}"`;
3817
- template = template.replace(r.from, to);
3818
- hasChanges = true;
3819
- }
3820
- RE_DYNAMIC_STYLE_VAR.lastIndex = 0;
3821
- const dynamicReplacements = [];
3822
- template.replace(RE_DYNAMIC_STYLE_VAR, (match, styleExpr) => {
3823
- const resolved = resolveCssVars(styleExpr, vars);
3824
- if (resolved !== styleExpr)
3825
- dynamicReplacements.push({ from: match, to: `:style="${resolved}"` });
3826
- return match;
3827
- });
3828
- for (const r of dynamicReplacements) {
3829
- template = template.replace(r.from, r.to);
3830
- hasChanges = true;
3831
- }
3832
- }
3833
- }
3834
- if (!template.includes("data-no-inline")) {
3835
- RE_IMAGE_SRC.lastIndex = 0;
3836
- if (RE_IMAGE_SRC.test(template)) {
3837
- RE_IMAGE_SRC.lastIndex = 0;
3838
- const componentDir = dirname(id);
3839
- const replacements = [];
3840
- let match;
3841
- while ((match = RE_IMAGE_SRC.exec(template)) !== null) {
3842
- const [fullMatch, srcPath, ext] = match;
3843
- if (!srcPath || !ext)
3844
- continue;
3845
- if (srcPath.startsWith("data:") || srcPath.startsWith("http"))
3846
- continue;
3847
- let resolvedPath;
3848
- if (srcPath.startsWith("/")) {
3849
- resolvedPath = join(options.publicDir, srcPath);
3850
- } else if (srcPath.startsWith("~/") || srcPath.startsWith("@/")) {
3851
- resolvedPath = join(options.srcDir, srcPath.slice(2));
3852
- } else if (!isAbsolute(srcPath)) {
3853
- resolvedPath = resolve(componentDir, srcPath);
3854
- } else {
3855
- continue;
3937
+ const styleReplacements = [];
3938
+ RE_STYLE_VAR_ATTR.lastIndex = 0;
3939
+ template.replace(RE_STYLE_VAR_ATTR, (match, styleValue) => {
3940
+ const resolved = resolveCssVars(styleValue, vars);
3941
+ if (resolved !== styleValue)
3942
+ styleReplacements.push({ from: match, content: resolved });
3943
+ return match;
3944
+ });
3945
+ for (const r of styleReplacements) {
3946
+ const declarations = r.content.split(";").filter(Boolean);
3947
+ const downleveled = [];
3948
+ for (const decl of declarations) {
3949
+ const colonIdx = decl.indexOf(":");
3950
+ if (colonIdx === -1) {
3951
+ downleveled.push(decl);
3952
+ continue;
3953
+ }
3954
+ const prop = decl.slice(0, colonIdx).trim();
3955
+ let value = decl.slice(colonIdx + 1).trim();
3956
+ if (RE_COLOR_PROP_NAME.test(prop) && !value.includes("var(")) {
3957
+ value = await downlevelColor(prop, value);
3958
+ if (value.includes("color-mix("))
3959
+ value = resolveColorMix(value);
3960
+ }
3961
+ downleveled.push(`${prop}: ${value}`);
3962
+ }
3963
+ const to = `style="${escapeAttrValue(downleveled.join("; "))}"`;
3964
+ template = template.replace(r.from, to);
3965
+ hasChanges = true;
3856
3966
  }
3857
- const fileBuffer = await readFile(resolvedPath).catch(() => {
3858
- return null;
3967
+ RE_DYNAMIC_STYLE_VAR.lastIndex = 0;
3968
+ const dynamicReplacements = [];
3969
+ template.replace(RE_DYNAMIC_STYLE_VAR, (match, styleExpr) => {
3970
+ const resolved = resolveCssVars(styleExpr, vars);
3971
+ if (resolved !== styleExpr)
3972
+ dynamicReplacements.push({ from: match, to: `:style="${resolved}"` });
3973
+ return match;
3859
3974
  });
3860
- if (!fileBuffer)
3861
- continue;
3862
- const mimeType = getMimeType(ext);
3863
- let dataUri;
3864
- if (ext === "svg") {
3865
- const svgContent = fileBuffer.toString("utf-8");
3866
- dataUri = `data:${mimeType},${encodeURIComponent(svgContent)}`;
3867
- } else {
3868
- const base64 = fileBuffer.toString("base64");
3869
- dataUri = `data:${mimeType};base64,${base64}`;
3975
+ for (const r of dynamicReplacements) {
3976
+ template = template.replace(r.from, r.to);
3977
+ hasChanges = true;
3870
3978
  }
3871
- replacements.push({ from: fullMatch, to: `src="${dataUri}"` });
3872
- hasChanges = true;
3873
- }
3874
- for (const { from, to } of replacements) {
3875
- template = template.replace(from, to);
3876
3979
  }
3877
3980
  }
3878
- }
3879
- {
3880
- const { descriptor } = parse(code);
3881
- if (descriptor.styles.length > 0) {
3882
- for (const styleBlock of descriptor.styles) {
3883
- const rawCss = styleBlock.content;
3884
- const scopeId = styleBlock.scoped ? rawCss.match(RE_SCOPED_DATA_V)?.[0] ?? null : null;
3885
- const cleanCss = scopeId ? rawCss.replaceAll(scopeId, "") : rawCss;
3886
- const simplified = await simplifyCss(cleanCss);
3887
- const classMap = extractClassStyles(simplified, { skipPrefixes: [] });
3888
- if (classMap.size > 0) {
3889
- const wrappedTemplate = `<div>${template}</div>`;
3890
- const doc = parse$1(wrappedTemplate);
3891
- walkSync(doc, (node) => {
3892
- if (node.type !== ELEMENT_NODE$2)
3893
- return;
3894
- const el = node;
3895
- const classAttr = el.attributes?.class;
3896
- if (!classAttr)
3897
- return;
3898
- const elClasses = classAttr.split(RE_WHITESPACE).filter(Boolean);
3899
- const matchedStyles = {};
3900
- const consumedClasses = /* @__PURE__ */ new Set();
3901
- for (const cls of elClasses) {
3902
- const styles = classMap.get(cls);
3903
- if (styles) {
3904
- Object.assign(matchedStyles, styles);
3905
- consumedClasses.add(cls);
3906
- }
3907
- }
3908
- if (Object.keys(matchedStyles).length === 0)
3909
- return;
3910
- const existingStyle = el.attributes?.style || "";
3911
- const existingProps = {};
3912
- for (const decl of splitCssDeclarations(existingStyle)) {
3913
- const [prop, ...valParts] = decl.split(":");
3914
- const value = valParts.join(":").trim();
3915
- if (prop?.trim() && value)
3916
- existingProps[prop.trim()] = value;
3917
- }
3918
- const merged = { ...matchedStyles, ...existingProps };
3919
- el.attributes.style = escapeAttrValue(
3920
- Object.entries(merged).map(([p, v]) => `${p}:${v}`).join(";")
3921
- );
3922
- const remaining = elClasses.filter((c) => !consumedClasses.has(c));
3923
- if (remaining.length > 0)
3924
- el.attributes.class = remaining.join(" ");
3925
- else
3926
- delete el.attributes.class;
3927
- if (el.attributes.viewbox) {
3928
- el.attributes.viewBox = el.attributes.viewbox;
3929
- delete el.attributes.viewbox;
3930
- }
3981
+ if (!template.includes("data-no-inline")) {
3982
+ RE_IMAGE_SRC.lastIndex = 0;
3983
+ if (RE_IMAGE_SRC.test(template)) {
3984
+ RE_IMAGE_SRC.lastIndex = 0;
3985
+ const componentDir = dirname(id);
3986
+ const replacements = [];
3987
+ let match;
3988
+ while ((match = RE_IMAGE_SRC.exec(template)) !== null) {
3989
+ const [fullMatch, srcPath, ext] = match;
3990
+ if (!srcPath || !ext)
3991
+ continue;
3992
+ if (srcPath.startsWith("data:") || srcPath.startsWith("http"))
3993
+ continue;
3994
+ let resolvedPath;
3995
+ if (srcPath.startsWith("/")) {
3996
+ resolvedPath = join(options.publicDir, srcPath);
3997
+ } else if (srcPath.startsWith("~/") || srcPath.startsWith("@/")) {
3998
+ resolvedPath = join(options.srcDir, srcPath.slice(2));
3999
+ } else if (!isAbsolute(srcPath)) {
4000
+ resolvedPath = resolve(componentDir, srcPath);
4001
+ } else {
4002
+ continue;
4003
+ }
4004
+ const fileBuffer = await readFile(resolvedPath).catch(() => {
4005
+ return null;
3931
4006
  });
3932
- template = renderSync(doc).replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
4007
+ if (!fileBuffer)
4008
+ continue;
4009
+ const mimeType = getMimeType(ext);
4010
+ let dataUri;
4011
+ if (ext === "svg") {
4012
+ const svgContent = fileBuffer.toString("utf-8");
4013
+ dataUri = `data:${mimeType},${encodeURIComponent(svgContent)}`;
4014
+ } else {
4015
+ const base64 = fileBuffer.toString("base64");
4016
+ dataUri = `data:${mimeType};base64,${base64}`;
4017
+ }
4018
+ replacements.push({ from: fullMatch, to: `src="${dataUri}"` });
3933
4019
  hasChanges = true;
3934
4020
  }
4021
+ for (const { from, to } of replacements) {
4022
+ template = template.replace(from, to);
4023
+ }
3935
4024
  }
3936
- for (const styleBlock of descriptor.styles) {
3937
- const styleStart = code.indexOf(styleBlock.loc.source) - "<style".length;
3938
- const fullTagStart = code.lastIndexOf("<style", styleStart + "<style".length);
3939
- const fullTagEnd = code.indexOf("</style>", styleStart) + "</style>".length;
3940
- if (fullTagStart >= 0 && fullTagEnd > fullTagStart) {
3941
- s.remove(fullTagStart, fullTagEnd);
4025
+ }
4026
+ {
4027
+ const { descriptor } = parse(code);
4028
+ if (descriptor.styles.length > 0) {
4029
+ for (const styleBlock of descriptor.styles) {
4030
+ const rawCss = styleBlock.content;
4031
+ const scopeId = styleBlock.scoped ? rawCss.match(RE_SCOPED_DATA_V)?.[0] ?? null : null;
4032
+ const cleanCss = scopeId ? rawCss.replaceAll(scopeId, "") : rawCss;
4033
+ const simplified = await simplifyCss(cleanCss);
4034
+ const classMap = extractClassStyles(simplified, { skipPrefixes: [] });
4035
+ if (classMap.size > 0) {
4036
+ const wrappedTemplate = `<div>${template}</div>`;
4037
+ const doc = parse$1(wrappedTemplate);
4038
+ walkSync(doc, (node) => {
4039
+ if (node.type !== ELEMENT_NODE$2)
4040
+ return;
4041
+ const el = node;
4042
+ const classAttr = el.attributes?.class;
4043
+ if (!classAttr)
4044
+ return;
4045
+ const elClasses = classAttr.split(RE_WHITESPACE).filter(Boolean);
4046
+ const matchedStyles = {};
4047
+ const consumedClasses = /* @__PURE__ */ new Set();
4048
+ for (const cls of elClasses) {
4049
+ const styles = classMap.get(cls);
4050
+ if (styles) {
4051
+ Object.assign(matchedStyles, styles);
4052
+ consumedClasses.add(cls);
4053
+ }
4054
+ }
4055
+ if (Object.keys(matchedStyles).length === 0)
4056
+ return;
4057
+ const existingStyle = el.attributes?.style || "";
4058
+ const existingProps = {};
4059
+ for (const decl of splitCssDeclarations(existingStyle)) {
4060
+ const [prop, ...valParts] = decl.split(":");
4061
+ const value = valParts.join(":").trim();
4062
+ if (prop?.trim() && value)
4063
+ existingProps[prop.trim()] = value;
4064
+ }
4065
+ const merged = { ...matchedStyles, ...existingProps };
4066
+ el.attributes.style = escapeAttrValue(
4067
+ Object.entries(merged).map(([p, v]) => `${p}:${v}`).join(";")
4068
+ );
4069
+ const remaining = elClasses.filter((c) => !consumedClasses.has(c));
4070
+ if (remaining.length > 0)
4071
+ el.attributes.class = remaining.join(" ");
4072
+ else
4073
+ delete el.attributes.class;
4074
+ if (el.attributes.viewbox) {
4075
+ el.attributes.viewBox = el.attributes.viewbox;
4076
+ delete el.attributes.viewbox;
4077
+ }
4078
+ });
4079
+ template = renderSync(doc).replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
4080
+ hasChanges = true;
4081
+ }
4082
+ }
4083
+ for (const styleBlock of descriptor.styles) {
4084
+ const styleStart = code.indexOf(styleBlock.loc.source) - "<style".length;
4085
+ const fullTagStart = code.lastIndexOf("<style", styleStart + "<style".length);
4086
+ const fullTagEnd = code.indexOf("</style>", styleStart) + "</style>".length;
4087
+ if (fullTagStart >= 0 && fullTagEnd > fullTagStart) {
4088
+ s.remove(fullTagStart, fullTagEnd);
4089
+ }
3942
4090
  }
3943
4091
  }
3944
4092
  }
4093
+ if (options.onFontRequirements) {
4094
+ extractFontRequirementsFromVue(code).then((reqs) => {
4095
+ options.onFontRequirements(id, reqs);
4096
+ });
4097
+ }
4098
+ if (!hasChanges)
4099
+ return;
4100
+ s.overwrite(templateStart, templateEnd, template);
4101
+ return {
4102
+ code: s.toString(),
4103
+ map: s.generateMap({ hires: true })
4104
+ };
3945
4105
  }
3946
- if (options.onFontRequirements) {
3947
- extractFontRequirementsFromVue(code).then((reqs) => {
3948
- options.onFontRequirements(id, reqs);
3949
- });
3950
- }
3951
- if (!hasChanges)
3952
- return;
3953
- s.overwrite(templateStart, templateEnd, template);
3954
- return {
3955
- code: s.toString(),
3956
- map: s.generateMap({ hires: true })
3957
- };
3958
4106
  }
3959
4107
  };
3960
4108
  });
@@ -4145,94 +4293,99 @@ const BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
4145
4293
  const MAX_IMPORT_REWRITE_DEPTH = 5;
4146
4294
  const RE_OG_IMAGE_QUERY = /\?og-image(?:-depth=(\d+))?$/;
4147
4295
  const RE_VUE_IMPORT = /import\s+(\w+)\s+from\s+(['"])(.+?\.vue)\2/g;
4296
+ const RE_VUE_ID = /\.vue(?:\?|$)/;
4148
4297
  const ComponentImportRewritePlugin = createUnplugin((options) => {
4149
4298
  return {
4150
4299
  name: "nuxt-og-image:component-import-rewrite",
4151
4300
  enforce: "pre",
4152
- transformInclude(id) {
4153
- const depthMatch = id.match(RE_OG_IMAGE_QUERY);
4154
- if (depthMatch) {
4155
- const depth = depthMatch[1] ? Number.parseInt(depthMatch[1]) : 0;
4156
- return depth < MAX_IMPORT_REWRITE_DEPTH;
4157
- }
4158
- if (!id.endsWith(".vue"))
4159
- return false;
4160
- return options.ogComponentPaths.some((dir) => id.startsWith(`${dir}/`) || id.startsWith(`${dir}\\`));
4161
- },
4162
- transform(code, rawId) {
4163
- const depthMatch = rawId.match(RE_OG_IMAGE_QUERY);
4164
- const currentDepth = depthMatch?.[1] ? Number.parseInt(depthMatch[1]) : 0;
4165
- const nextQuery = `?og-image-depth=${currentDepth + 1}`;
4166
- const { descriptor } = parse(code);
4167
- if (!descriptor.template?.ast) {
4168
- return;
4169
- }
4170
- const usedComponents = /* @__PURE__ */ new Set();
4171
- walkTemplateAst(descriptor.template.ast.children, (node) => {
4172
- if (node.type === 1 && !HTML_ELEMENTS.has(node.tag) && !BUILTIN_COMPONENTS.has(node.tag)) {
4173
- usedComponents.add(node.tag);
4301
+ transform: {
4302
+ filter: {
4303
+ id: RE_VUE_ID
4304
+ },
4305
+ handler(code, rawId) {
4306
+ const depthMatch = rawId.match(RE_OG_IMAGE_QUERY);
4307
+ const currentDepth = depthMatch?.[1] ? Number.parseInt(depthMatch[1]) : 0;
4308
+ if (depthMatch) {
4309
+ if (currentDepth >= MAX_IMPORT_REWRITE_DEPTH)
4310
+ return;
4311
+ } else {
4312
+ if (!rawId.endsWith(".vue"))
4313
+ return;
4314
+ if (!options.ogComponentPaths.some((dir) => rawId.startsWith(`${dir}/`) || rawId.startsWith(`${dir}\\`)))
4315
+ return;
4174
4316
  }
4175
- });
4176
- if (usedComponents.size === 0) {
4177
- return;
4178
- }
4179
- const components = options.getComponents();
4180
- const newImports = [];
4181
- for (const tag of usedComponents) {
4182
- if (tag === "Icon" || tag === "UIcon")
4183
- continue;
4184
- const match = components.find((c) => c.pascalName === tag || c.kebabName === tag);
4185
- if (match?.filePath && match.filePath.endsWith(".vue")) {
4186
- newImports.push({ name: tag, filePath: match.filePath });
4317
+ const nextQuery = `?og-image-depth=${currentDepth + 1}`;
4318
+ const { descriptor } = parse(code);
4319
+ if (!descriptor.template?.ast) {
4320
+ return;
4187
4321
  }
4188
- }
4189
- if (newImports.length === 0) {
4190
- return;
4191
- }
4192
- const s = new MagicString(code);
4193
- const rewritten = /* @__PURE__ */ new Set();
4194
- const scriptBlock = descriptor.scriptSetup || descriptor.script;
4195
- if (scriptBlock) {
4196
- const scriptStart = scriptBlock.loc.start.offset;
4197
- const scriptContent = scriptBlock.content;
4198
- for (const m of scriptContent.matchAll(RE_VUE_IMPORT)) {
4199
- const importName = m[1];
4200
- if (newImports.some((i) => i.name === importName)) {
4201
- const source = m[3];
4202
- const matchStart = scriptStart + m.index + m[0].indexOf(source);
4203
- s.overwrite(matchStart, matchStart + source.length, `${source}${nextQuery}`);
4204
- rewritten.add(importName);
4322
+ const usedComponents = /* @__PURE__ */ new Set();
4323
+ walkTemplateAst(descriptor.template.ast.children, (node) => {
4324
+ if (node.type === 1 && !HTML_ELEMENTS.has(node.tag) && !BUILTIN_COMPONENTS.has(node.tag)) {
4325
+ usedComponents.add(node.tag);
4205
4326
  }
4327
+ });
4328
+ if (usedComponents.size === 0) {
4329
+ return;
4206
4330
  }
4207
- }
4208
- const imports = newImports.filter((i) => !rewritten.has(i.name));
4209
- if (imports.length === 0) {
4210
- return {
4211
- code: s.toString(),
4212
- map: s.generateMap({ hires: true })
4213
- };
4214
- }
4215
- const importStatements = imports.map((i) => `import ${i.name} from '${i.filePath}${nextQuery}'`).join("\n");
4216
- if (descriptor.scriptSetup) {
4217
- const scriptSetupStart = code.indexOf("<script setup");
4218
- const tagEnd = code.indexOf(">", scriptSetupStart) + 1;
4219
- s.appendRight(tagEnd, `
4331
+ const components = options.getComponents();
4332
+ const newImports = [];
4333
+ for (const tag of usedComponents) {
4334
+ if (tag === "Icon" || tag === "UIcon")
4335
+ continue;
4336
+ const match = components.find((c) => c.pascalName === tag || c.kebabName === tag);
4337
+ if (match?.filePath && match.filePath.endsWith(".vue")) {
4338
+ newImports.push({ name: tag, filePath: match.filePath });
4339
+ }
4340
+ }
4341
+ if (newImports.length === 0) {
4342
+ return;
4343
+ }
4344
+ const s = new MagicString(code);
4345
+ const rewritten = /* @__PURE__ */ new Set();
4346
+ const scriptBlock = descriptor.scriptSetup || descriptor.script;
4347
+ if (scriptBlock) {
4348
+ const scriptStart = scriptBlock.loc.start.offset;
4349
+ const scriptContent = scriptBlock.content;
4350
+ for (const m of scriptContent.matchAll(RE_VUE_IMPORT)) {
4351
+ const importName = m[1];
4352
+ if (newImports.some((i) => i.name === importName)) {
4353
+ const source = m[3];
4354
+ const matchStart = scriptStart + m.index + m[0].indexOf(source);
4355
+ s.overwrite(matchStart, matchStart + source.length, `${source}${nextQuery}`);
4356
+ rewritten.add(importName);
4357
+ }
4358
+ }
4359
+ }
4360
+ const imports = newImports.filter((i) => !rewritten.has(i.name));
4361
+ if (imports.length === 0) {
4362
+ return {
4363
+ code: s.toString(),
4364
+ map: s.generateMap({ hires: true })
4365
+ };
4366
+ }
4367
+ const importStatements = imports.map((i) => `import ${i.name} from '${i.filePath}${nextQuery}'`).join("\n");
4368
+ if (descriptor.scriptSetup) {
4369
+ const scriptSetupStart = code.indexOf("<script setup");
4370
+ const tagEnd = code.indexOf(">", scriptSetupStart) + 1;
4371
+ s.appendRight(tagEnd, `
4220
4372
  ${importStatements}`);
4221
- } else if (descriptor.script) {
4222
- const scriptStart = code.indexOf("<script");
4223
- const tagEnd = code.indexOf(">", scriptStart) + 1;
4224
- s.appendRight(tagEnd, `
4373
+ } else if (descriptor.script) {
4374
+ const scriptStart = code.indexOf("<script");
4375
+ const tagEnd = code.indexOf(">", scriptStart) + 1;
4376
+ s.appendRight(tagEnd, `
4225
4377
  ${importStatements}`);
4226
- } else {
4227
- s.prepend(`<script setup>
4378
+ } else {
4379
+ s.prepend(`<script setup>
4228
4380
  ${importStatements}
4229
4381
  <\/script>
4230
4382
  `);
4383
+ }
4384
+ return {
4385
+ code: s.toString(),
4386
+ map: s.generateMap({ hires: true })
4387
+ };
4231
4388
  }
4232
- return {
4233
- code: s.toString(),
4234
- map: s.generateMap({ hires: true })
4235
- };
4236
4389
  }
4237
4390
  };
4238
4391
  });
@@ -4790,6 +4943,10 @@ declare module '#og-image/renderers/takumi' {
4790
4943
  export default _default
4791
4944
  }
4792
4945
 
4946
+ declare module '#og-image/bindings/fetch' {
4947
+ export { guardedFetch } from '${typesPath.replace("/types", "")}/server/og-image/bindings/fetch/web'
4948
+ }
4949
+
4793
4950
  declare module '#og-image/emoji-transform' {
4794
4951
  import type { OgImageRenderEventContext } from '${typesPath}'
4795
4952
  export function getEmojiSvg(ctx: OgImageRenderEventContext, emoji: string): Promise<string | null>
@@ -4948,6 +5105,8 @@ const module$1 = defineNuxtModule({
4948
5105
  return;
4949
5106
  }
4950
5107
  const nitroCompatibility = setupNitroRuntimeCompatibility(nuxt);
5108
+ if (nuxt.options.dev && nitroCompatibility._tag === "nitro-v2")
5109
+ nuxt.options.nitro.noExternals ??= false;
4951
5110
  const setRuntimeAlias = (id, path) => {
4952
5111
  nuxt.options.alias[id] = path;
4953
5112
  nuxt.options.nitro.alias ||= {};
@@ -5032,6 +5191,7 @@ Or pin to a specific version in package.json:
5032
5191
  setRuntimeAlias("#og-image-cache", resolve("./runtime/server/og-image/cache/lru"));
5033
5192
  const preset = resolveOgImagePreset(nuxt.options.nitro);
5034
5193
  const targetCompatibility = getPresetNitroPresetCompatibility(preset);
5194
+ setRuntimeAlias("#og-image/bindings/fetch", resolve(`./runtime/server/og-image/bindings/fetch/${targetCompatibility.fetch ?? "node"}`));
5035
5195
  const runtimeCompatibilityMeta = {};
5036
5196
  const normalizedPreset = preset.replace(RE_LEGACY_SUFFIX, "");
5037
5197
  const isCloudflareWorkers = ["cloudflare", "cloudflare-module", "cloudflare-durable"].includes(normalizedPreset);
@@ -5284,7 +5444,7 @@ ${familyInfo}`);
5284
5444
  }
5285
5445
  });
5286
5446
  if (!nuxt.options.dev) {
5287
- addBuildPlugin(TreeShakeComposablesPlugin, { server: true, client: true, build: true });
5447
+ addBuildPlugin(TreeShakeComposablesPlugin, { server: true, client: true });
5288
5448
  setRuntimeAlias("#og-image-cache", resolve("./runtime/server/og-image/cache/mock"));
5289
5449
  }
5290
5450
  }
@@ -5447,6 +5607,9 @@ ${familyInfo}`);
5447
5607
  name: "getOgImagePath",
5448
5608
  from: resolve("./runtime/app/utils")
5449
5609
  });
5610
+ addServerImports([
5611
+ { name: "getOgImageUrl", from: resolve("./runtime/server/nitro") }
5612
+ ]);
5450
5613
  const basePluginPath = `./runtime/app/plugins${config.zeroRuntime ? "/__zero-runtime" : ""}`;
5451
5614
  addPlugin({ mode: "server", src: resolve(`${basePluginPath}/route-rule-og-image.server`) });
5452
5615
  addPlugin({ mode: "server", src: resolve(`${basePluginPath}/og-image-canonical-urls.server`) });
@@ -5893,6 +6056,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5893
6056
  colorPreference = colorModeOptions.fallback;
5894
6057
  if (!colorPreference || colorPreference === "system")
5895
6058
  colorPreference = "light";
6059
+ const includeTwitter = config.includeTwitter ?? nuxt.options.seo?.automaticTwitterTags ?? true;
5896
6060
  const runtimeConfig = {
5897
6061
  version,
5898
6062
  // binding options
@@ -5902,6 +6066,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5902
6066
  publicStoragePath: `root${publicDirAbs.replace(nuxt.options.rootDir, "").replaceAll("/", ":")}`,
5903
6067
  defaults: config.defaults,
5904
6068
  debug: config.debug,
6069
+ includeTwitter,
5905
6070
  // avoid adding credentials
5906
6071
  baseCacheKey,
5907
6072
  buildCacheDir,
@@ -5943,6 +6108,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5943
6108
  ...nuxt.options.runtimeConfig.public,
5944
6109
  "nuxt-og-image": {
5945
6110
  defaults: runtimeConfig.defaults,
6111
+ includeTwitter: runtimeConfig.includeTwitter,
5946
6112
  hasServerRuntime: !nuxt.options._generate && !nuxt.options.nitro?.static
5947
6113
  }
5948
6114
  };