nuxt-og-image 6.7.7 → 6.8.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.
@@ -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, 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)
@@ -276,6 +349,36 @@ async function applyNitroPresetCompatibility(nitroConfig, options) {
276
349
  await applyBinding("sharp"),
277
350
  nitroConfig.alias || {}
278
351
  );
352
+ if (resolvedCompatibility.satori) {
353
+ const satoriPackage = await readPackageJSON(`${satoriPkgDir}/package.json`);
354
+ if (satoriPackage.dependencies?.harfbuzzjs) {
355
+ const harfbuzzWasmPath = resolveModulePath("harfbuzzjs/hb.wasm", { from: `${satoriPkgDir}/package.json` });
356
+ if (resolvedCompatibility.satori === "wasm" || target.replace(RE_LEGACY_SUFFIX, "") === "netlify-edge") {
357
+ const harfbuzzDir = harfbuzzWasmPath.slice(0, -"hb.wasm".length);
358
+ 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";
359
+ Object.assign(nitroConfig.alias, {
360
+ "harfbuzzjs": await resolve.resolvePath("./runtime/server/og-image/bindings/satori/harfbuzz"),
361
+ "#og-image/harfbuzz-factory": `${harfbuzzDir}hb.js`,
362
+ "#og-image/harfbuzz-adapter": `${harfbuzzDir}hbjs.js`
363
+ });
364
+ if (resolvedCompatibility.satori === "wasm") {
365
+ nitroConfig.alias["#og-image/harfbuzz-wasm"] = `${harfbuzzWasmPath}?module`;
366
+ } else {
367
+ const base64 = (await readFile(harfbuzzWasmPath)).toString("base64");
368
+ nitroConfig.virtual["#og-image/harfbuzz-wasm"] = `export default imports => WebAssembly.instantiate(Uint8Array.from(atob(${JSON.stringify(base64)}), c => c.charCodeAt(0)), imports)`;
369
+ }
370
+ } else if (Number.parseInt(getNuxtVersion(useNuxt()), 10) >= 5) {
371
+ const config = nitroConfig;
372
+ config.traceDeps = [.../* @__PURE__ */ new Set([...config.traceDeps || [], "harfbuzzjs*"])];
373
+ } else {
374
+ nitroConfig.externals = nitroConfig.externals || {};
375
+ nitroConfig.externals.traceInclude = [.../* @__PURE__ */ new Set([
376
+ ...nitroConfig.externals.traceInclude || [],
377
+ harfbuzzWasmPath
378
+ ])];
379
+ }
380
+ }
381
+ }
279
382
  if (Object.values(resolvedCompatibility).includes("wasm")) {
280
383
  nitroConfig.experimental = nitroConfig.experimental || {};
281
384
  nitroConfig.experimental.wasm = true;
@@ -334,6 +437,27 @@ async function setupBuildHandler(config, resolve, getDetectedRenderers, getCompa
334
437
  detectedRenderers: getDetectedRenderers(),
335
438
  metadata: getCompatibilityMeta()
336
439
  });
440
+ const rollupConfig = nitro.options.rollupConfig ||= { output: {} };
441
+ rollupConfig.plugins = [rollupConfig.plugins, {
442
+ name: "og-image-harfbuzz-worker",
443
+ transform(code, id) {
444
+ if (!nitro.options.alias["#og-image/harfbuzz-factory"] || !id.endsWith("/harfbuzzjs/hb.js"))
445
+ return;
446
+ const transformed = new MagicString(code);
447
+ parseAndWalk(code, id, {
448
+ enter(node) {
449
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && node.id.name === "convertJsFunctionToWasm" && node.init) {
450
+ transformed.prependLeft(node.init.start, 'Module["convertJsFunctionToWasm"] || (');
451
+ transformed.appendRight(node.init.end, ")");
452
+ }
453
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier" && ["ENVIRONMENT_IS_NODE", "ENVIRONMENT_IS_WEB", "ENVIRONMENT_IS_WORKER"].includes(node.id.name) && node.init) {
454
+ transformed.overwrite(node.init.start, node.init.end, "false");
455
+ }
456
+ }
457
+ });
458
+ return { code: transformed.toString(), map: transformed.generateMap({ hires: true }).toString() };
459
+ }
460
+ }];
337
461
  const target = resolveOgImagePreset(nitro.options);
338
462
  const normalizedTarget = target.replace(RE_LEGACY_SUFFIX, "");
339
463
  const isEdgePreset = ["cloudflare", "cloudflare-pages", "cloudflare-pages-static", "cloudflare-module", "cloudflare-durable", "vercel-edge", "netlify-edge"].includes(normalizedTarget);
@@ -2086,8 +2210,7 @@ async function initFontless(options) {
2086
2210
  {
2087
2211
  dev: false,
2088
2212
  renderedFontURLs,
2089
- assetsBaseURL: FONTS_URL_PREFIX,
2090
- callback: (filename, url) => renderedFontURLs.set(filename, url)
2213
+ assetsBaseURL: FONTS_URL_PREFIX
2091
2214
  },
2092
2215
  faces
2093
2216
  ),
@@ -2111,7 +2234,7 @@ async function initFontless(options) {
2111
2234
  providers
2112
2235
  });
2113
2236
  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 };
2237
+ options.nuxt._ogImageFontless = { resolver, providerNames: priority };
2115
2238
  }
2116
2239
  function persistFontUrlMapping(options) {
2117
2240
  if (!options.fontContext?.renderedFontURLs.size)
@@ -2757,11 +2880,10 @@ function isVue(id, opts = {}) {
2757
2880
  }
2758
2881
  return true;
2759
2882
  }
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
- }
2883
+ const VUE_RE = /\.vue(?:\?|$)/;
2884
+ const JS_RE = /\.[cm]?[jt]sx?(?:\?|$)/;
2885
+ const ISLANDS_RE = /components\.islands\.mjs(?:\?|$)/;
2886
+ const COMPOSABLE_CODE_MARKER = "defineOgImage";
2765
2887
  const TreeShakeComposablesPlugin = createUnplugin(() => {
2766
2888
  const composableNames = [
2767
2889
  "defineOgImage",
@@ -2774,21 +2896,26 @@ const TreeShakeComposablesPlugin = createUnplugin(() => {
2774
2896
  return {
2775
2897
  name: "nuxt-og-image:zero-runtime:transform",
2776
2898
  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);
2899
+ transform: {
2900
+ filter: {
2901
+ // @todo re-implement composable tree-shaking for island files
2902
+ id: { include: [VUE_RE, JS_RE], exclude: [ISLANDS_RE] },
2903
+ code: COMPOSABLE_CODE_MARKER
2904
+ },
2905
+ handler(code, id) {
2906
+ if (VUE_RE.test(id) && !isVue(id, { type: ["script"] })) {
2907
+ return;
2908
+ }
2784
2909
  if (!COMPOSABLE_RE.test(code)) {
2785
2910
  return;
2786
2911
  }
2787
- for (const match of strippedCode.matchAll(COMPOSABLE_RE_GLOBAL)) {
2912
+ const s = new MagicString(code);
2913
+ for (const match of stripLiteral(code).matchAll(COMPOSABLE_RE_GLOBAL)) {
2788
2914
  s.overwrite(match.index, match.index + match[0].length, `${match[1]} import.meta.prerender && ${match[2]}`);
2789
2915
  }
2790
- }
2791
- if (s.hasChanged()) {
2916
+ if (!s.hasChanged()) {
2917
+ return;
2918
+ }
2792
2919
  return {
2793
2920
  code: s.toString(),
2794
2921
  map: s.generateMap({ hires: true })
@@ -3304,6 +3431,7 @@ const RE_SVG_ID_ATTR = /\bid="([^"]+)"/g;
3304
3431
  const RE_SVG_VIEWBOX = /viewBox="([^"]*)"/;
3305
3432
  const RE_SVG_BODY = /<svg[^>]*>([\s\S]*)<\/svg>/;
3306
3433
  const RE_OG_IMAGE_QUERY$1 = /\?og-image(?:-depth=\d+)?$/;
3434
+ const RE_VUE_ID$1 = /\.vue(?:\?|$)/;
3307
3435
  const RE_TEXT_BETWEEN_TAGS = />([^<]*)</g;
3308
3436
  const RE_SPECIAL_REGEX_CHARS = /[.*+?^${}()|[\]\\]/g;
3309
3437
  const RE_WRAPPER_DIV_START = /^<div>/;
@@ -3542,419 +3670,424 @@ const AssetTransformPlugin = createUnplugin((options) => {
3542
3670
  return {
3543
3671
  name: "nuxt-og-image:asset-transform",
3544
3672
  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]);
3673
+ transform: {
3674
+ filter: {
3675
+ id: RE_VUE_ID$1
3676
+ },
3677
+ async handler(code, rawId) {
3678
+ const isNested = RE_OG_IMAGE_QUERY$1.test(rawId);
3679
+ if (!isNested) {
3680
+ if (!rawId.endsWith(".vue"))
3681
+ return;
3682
+ if (!options.ogComponentPaths.some((dir) => rawId.startsWith(`${dir}/`) || rawId.startsWith(`${dir}\\`)))
3683
+ return;
3573
3684
  }
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);
3685
+ const id = rawId.replace(RE_OG_IMAGE_QUERY$1, "");
3686
+ if (isNested)
3687
+ options.onNestedTransform?.(id);
3688
+ const bounds = getOuterTemplateBounds(code);
3689
+ if (!bounds)
3690
+ return;
3691
+ const s = new MagicString(code);
3692
+ const { templateStart, templateEnd } = bounds;
3693
+ let template = code.slice(templateStart, templateEnd);
3694
+ let hasChanges = false;
3695
+ if (options.emojiSet && RE_MATCH_EMOJIS.test(template)) {
3696
+ if (!emojiIcons) {
3697
+ emojiIcons = options.emojiIcons ?? await import(`@iconify-json/${options.emojiSet}/icons.json`, { with: { type: 'json' } }).then((m) => m.default).catch(() => {
3698
+ return null;
3699
+ });
3579
3700
  }
3580
- if (!svg) {
3581
- svg = await fetchEmojiSvg(emoji, options.emojiSet);
3701
+ RE_MATCH_EMOJIS.lastIndex = 0;
3702
+ const allEmojis = /* @__PURE__ */ new Set();
3703
+ for (const match of template.matchAll(RE_MATCH_EMOJIS)) {
3704
+ allEmojis.add(match[0]);
3582
3705
  }
3583
- if (svg) {
3584
- emojiSvgMap.set(emoji, svg);
3706
+ const emojiSvgMap = /* @__PURE__ */ new Map();
3707
+ await Promise.all(Array.from(allEmojis, async (emoji) => {
3708
+ let svg = null;
3709
+ if (emojiIcons) {
3710
+ svg = buildEmojiSvg(emoji, emojiIcons, options.emojiSet);
3711
+ }
3712
+ if (!svg) {
3713
+ svg = await fetchEmojiSvg(emoji, options.emojiSet);
3714
+ }
3715
+ if (svg) {
3716
+ emojiSvgMap.set(emoji, svg);
3717
+ }
3718
+ }));
3719
+ if (emojiSvgMap.size > 0) {
3720
+ RE_TEXT_BETWEEN_TAGS.lastIndex = 0;
3721
+ template = template.replace(RE_TEXT_BETWEEN_TAGS, (fullMatch, textContent) => {
3722
+ if (!textContent)
3723
+ return fullMatch;
3724
+ RE_MATCH_EMOJIS.lastIndex = 0;
3725
+ const emojiMatches = [...textContent.matchAll(RE_MATCH_EMOJIS)];
3726
+ if (!emojiMatches.length)
3727
+ return fullMatch;
3728
+ let newTextContent = textContent;
3729
+ for (const match of emojiMatches) {
3730
+ const emoji = match[0];
3731
+ const svg = emojiSvgMap.get(emoji);
3732
+ if (svg) {
3733
+ hasChanges = true;
3734
+ const escaped = emoji.replace(RE_SPECIAL_REGEX_CHARS, "\\$&");
3735
+ newTextContent = newTextContent.replace(new RegExp(escaped, "g"), svg);
3736
+ }
3737
+ }
3738
+ return `>${newTextContent}<`;
3739
+ });
3585
3740
  }
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);
3741
+ }
3742
+ if (template.includes("<Icon") || template.includes("<UIcon")) {
3743
+ const prefixes = /* @__PURE__ */ new Set();
3744
+ const iconElements = [];
3745
+ const wrappedTemplate = `<div>${template}</div>`;
3746
+ const doc = parse$1(wrappedTemplate);
3747
+ walkSync(doc, (node, parent, index) => {
3748
+ if (node.type === ELEMENT_NODE$2) {
3749
+ const el = node;
3750
+ if (el.name === "Icon" || el.name === "UIcon") {
3751
+ const iconName = el.attributes.name;
3752
+ if (iconName && !iconName.includes("{")) {
3753
+ const [prefix] = iconName.split(":");
3754
+ if (prefix)
3755
+ prefixes.add(prefix);
3756
+ iconElements.push({ node: el, parent, index });
3757
+ }
3604
3758
  }
3605
3759
  }
3606
- return `>${newTextContent}<`;
3607
3760
  });
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") {
3761
+ const iconSets = /* @__PURE__ */ new Map();
3762
+ for (const prefix of prefixes) {
3763
+ const icons = await loadIconSet(prefix);
3764
+ if (icons)
3765
+ iconSets.set(prefix, icons);
3766
+ }
3767
+ if (iconElements.length > 0) {
3768
+ let anyReplaced = false;
3769
+ for (const { node: el, parent, index } of iconElements) {
3619
3770
  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 });
3771
+ if (!iconName)
3772
+ continue;
3773
+ const [prefix, name] = iconName.split(":");
3774
+ if (!prefix || !name)
3775
+ continue;
3776
+ const icons = iconSets.get(prefix);
3777
+ const iconData = icons?.icons?.[name];
3778
+ let svgHtml;
3779
+ if (iconData) {
3780
+ svgHtml = buildIconSvg(iconData, icons.width || 24, icons.height || 24, el.attributes);
3781
+ } else if (options.cssProvider?.resolveIcon) {
3782
+ const resolved = await options.cssProvider.resolveIcon(prefix, name);
3783
+ if (resolved)
3784
+ svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, el.attributes);
3785
+ }
3786
+ if (!svgHtml)
3787
+ continue;
3788
+ anyReplaced = true;
3789
+ hasChanges = true;
3790
+ const svgDoc = parse$1(svgHtml);
3791
+ if ("children" in parent && Array.isArray(parent.children)) {
3792
+ parent.children[index] = svgDoc.children[0];
3625
3793
  }
3626
3794
  }
3795
+ if (anyReplaced) {
3796
+ const rendered = renderSync(doc);
3797
+ template = rendered.replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
3798
+ }
3627
3799
  }
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
3800
  }
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)
3801
+ if (options.cssProvider?.resolveIcon) {
3802
+ const replacements = [];
3803
+ RE_ICON_ELEMENT.lastIndex = 0;
3804
+ let elMatch;
3805
+ while ((elMatch = RE_ICON_ELEMENT.exec(template)) !== null) {
3806
+ const [fullMatch, , attrsStr, classValue] = elMatch;
3807
+ if (!classValue)
3643
3808
  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);
3809
+ const classes = classValue.split(RE_WHITESPACE).filter(Boolean);
3810
+ let iconPrefix;
3811
+ let iconName;
3812
+ for (const cls of classes) {
3813
+ const im = cls.match(ICON_CLASS_RE);
3814
+ if (im?.[1] && im[2]) {
3815
+ iconPrefix = im[1];
3816
+ iconName = im[2];
3817
+ break;
3818
+ }
3653
3819
  }
3654
- if (!svgHtml)
3820
+ if (!iconPrefix || !iconName)
3655
3821
  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];
3822
+ const resolved = await options.cssProvider.resolveIcon(iconPrefix, iconName);
3823
+ if (!resolved)
3824
+ continue;
3825
+ const attrs = {};
3826
+ RE_HTML_ATTR.lastIndex = 0;
3827
+ let am;
3828
+ while ((am = RE_HTML_ATTR.exec(attrsStr)) !== null) {
3829
+ if (am[1] && am[1] !== "class" && am[1] !== ":style" && am[1] !== "v-bind:style")
3830
+ attrs[am[1]] = am[2] ?? "";
3661
3831
  }
3832
+ const otherClasses = classes.filter((c) => !isIconClass(c));
3833
+ if (otherClasses.length > 0)
3834
+ attrs.class = otherClasses.join(" ");
3835
+ const svgHtml = buildIconSvg(resolved, resolved.width, resolved.height, attrs);
3836
+ replacements.push({ from: fullMatch, to: svgHtml });
3662
3837
  }
3663
- if (anyReplaced) {
3664
- const rendered = renderSync(doc);
3665
- template = rendered.replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
3838
+ for (const { from, to } of replacements) {
3839
+ template = template.replace(from, to);
3840
+ hasChanges = true;
3666
3841
  }
3667
3842
  }
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] ?? "";
3843
+ const rootAttrs = {};
3844
+ const rootAttrMatch = template.match(RE_ROOT_ELEMENT);
3845
+ if (rootAttrMatch?.[2]) {
3846
+ RE_DATA_ATTR.lastIndex = 0;
3847
+ for (const m of rootAttrMatch[2].matchAll(RE_DATA_ATTR)) {
3848
+ if (m[1] && m[2])
3849
+ rootAttrs[`data-${m[1]}`] = m[2];
3699
3850
  }
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
3851
  }
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(", ")}`);
3852
+ if (!rootAttrs["data-theme"] && options.colorPreference)
3853
+ rootAttrs["data-theme"] = options.colorPreference;
3854
+ const hasDynamicDataTheme = !!(rootAttrMatch?.[2] && RE_DYNAMIC_DATA_THEME.test(` ${rootAttrMatch[2]}`));
3855
+ if (options.cssProvider) {
3856
+ options.beforeCssResolve?.();
3857
+ try {
3858
+ const fullCode = code.slice(0, templateStart) + template + code.slice(templateEnd);
3859
+ const result = await transformVueTemplate(fullCode, {
3860
+ resolveStyles: async (classes) => {
3861
+ const supported = classes.filter((cls) => !isUnsupportedClass(cls) && !isIconClass(cls));
3862
+ const unsupported = classes.filter((cls) => isUnsupportedClass(cls));
3863
+ if (unsupported.length > 0) {
3864
+ const componentName2 = id.split("/").pop();
3865
+ logger.warn(`[nuxt-og-image] ${componentName2}: Filtered unsupported classes: ${unsupported.join(", ")}`);
3866
+ }
3867
+ const componentName = id.split("/").pop()?.replace(RE_FILE_EXTENSION, "");
3868
+ if (hasDynamicDataTheme) {
3869
+ const lightAttrs = { ...rootAttrs, "data-theme": "light" };
3870
+ const darkAttrs = { ...rootAttrs, "data-theme": "dark" };
3871
+ const [lightResolved, darkResolved] = await Promise.all([
3872
+ options.cssProvider.resolveClassesToStyles(supported, componentName, lightAttrs),
3873
+ options.cssProvider.resolveClassesToStyles(supported, componentName, darkAttrs)
3874
+ ]);
3875
+ return mergeThemePairs(supported, lightResolved, darkResolved);
3876
+ }
3877
+ return options.cssProvider.resolveClassesToStyles(supported, componentName, rootAttrs);
3734
3878
  }
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);
3879
+ });
3880
+ if (result) {
3881
+ const newBounds = getOuterTemplateBounds(result.code);
3882
+ if (newBounds) {
3883
+ template = result.code.slice(newBounds.templateStart, newBounds.templateEnd);
3884
+ hasChanges = true;
3744
3885
  }
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
3886
  }
3887
+ } catch (err) {
3888
+ const componentName = id.split("/").pop();
3889
+ logger.warn(`[nuxt-og-image] ${componentName}: CSS template transform failed, using original template`, err.message);
3754
3890
  }
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
3891
  }
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 });
3892
+ if (template.includes("var(")) {
3893
+ const vars = options.cssProvider?.getVars ? await options.cssProvider.getVars(rootAttrs) : /* @__PURE__ */ new Map();
3894
+ RE_INLINE_STYLE.lastIndex = 0;
3895
+ for (const m of template.matchAll(RE_INLINE_STYLE)) {
3896
+ RE_CSS_VAR_DEF.lastIndex = 0;
3897
+ for (const def of m[1].matchAll(RE_CSS_VAR_DEF)) {
3898
+ const name = `--${def[1]}`;
3899
+ if (!vars.has(name))
3900
+ vars.set(name, def[2].trim());
3778
3901
  }
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
3902
  }
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;
3903
+ if (vars.size > 0) {
3904
+ const replacements = [];
3905
+ RE_NON_STYLE_VAR_ATTR.lastIndex = 0;
3906
+ template.replace(RE_NON_STYLE_VAR_ATTR, (match, attr, value) => {
3907
+ const resolved = resolveCssVars(value, vars);
3908
+ if (resolved !== value) {
3909
+ replacements.push({ from: match, to: "", attr, value: resolved });
3910
+ }
3911
+ return match;
3912
+ });
3913
+ for (const r of replacements) {
3914
+ let value = r.value;
3915
+ if (RE_COLOR_ATTR_NAME.test(r.attr)) {
3916
+ value = await downlevelColor(r.attr, value);
3806
3917
  }
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);
3918
+ r.to = `${r.attr}="${escapeAttrValue(value)}"`;
3919
+ template = template.replace(r.from, r.to);
3920
+ hasChanges = true;
3921
+ }
3922
+ const styleReplacements = [];
3923
+ RE_STYLE_VAR_ATTR.lastIndex = 0;
3924
+ template.replace(RE_STYLE_VAR_ATTR, (match, styleValue) => {
3925
+ const resolved = resolveCssVars(styleValue, vars);
3926
+ if (resolved !== styleValue)
3927
+ styleReplacements.push({ from: match, content: resolved });
3928
+ return match;
3929
+ });
3930
+ for (const r of styleReplacements) {
3931
+ const declarations = r.content.split(";").filter(Boolean);
3932
+ const downleveled = [];
3933
+ for (const decl of declarations) {
3934
+ const colonIdx = decl.indexOf(":");
3935
+ if (colonIdx === -1) {
3936
+ downleveled.push(decl);
3937
+ continue;
3938
+ }
3939
+ const prop = decl.slice(0, colonIdx).trim();
3940
+ let value = decl.slice(colonIdx + 1).trim();
3941
+ if (RE_COLOR_PROP_NAME.test(prop) && !value.includes("var(")) {
3942
+ value = await downlevelColor(prop, value);
3943
+ if (value.includes("color-mix("))
3944
+ value = resolveColorMix(value);
3945
+ }
3946
+ downleveled.push(`${prop}: ${value}`);
3813
3947
  }
3814
- downleveled.push(`${prop}: ${value}`);
3948
+ const to = `style="${escapeAttrValue(downleveled.join("; "))}"`;
3949
+ template = template.replace(r.from, to);
3950
+ hasChanges = true;
3951
+ }
3952
+ RE_DYNAMIC_STYLE_VAR.lastIndex = 0;
3953
+ const dynamicReplacements = [];
3954
+ template.replace(RE_DYNAMIC_STYLE_VAR, (match, styleExpr) => {
3955
+ const resolved = resolveCssVars(styleExpr, vars);
3956
+ if (resolved !== styleExpr)
3957
+ dynamicReplacements.push({ from: match, to: `:style="${resolved}"` });
3958
+ return match;
3959
+ });
3960
+ for (const r of dynamicReplacements) {
3961
+ template = template.replace(r.from, r.to);
3962
+ hasChanges = true;
3815
3963
  }
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
3964
  }
3832
3965
  }
3833
- }
3834
- if (!template.includes("data-no-inline")) {
3835
- RE_IMAGE_SRC.lastIndex = 0;
3836
- if (RE_IMAGE_SRC.test(template)) {
3966
+ if (!template.includes("data-no-inline")) {
3837
3967
  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;
3968
+ if (RE_IMAGE_SRC.test(template)) {
3969
+ RE_IMAGE_SRC.lastIndex = 0;
3970
+ const componentDir = dirname(id);
3971
+ const replacements = [];
3972
+ let match;
3973
+ while ((match = RE_IMAGE_SRC.exec(template)) !== null) {
3974
+ const [fullMatch, srcPath, ext] = match;
3975
+ if (!srcPath || !ext)
3976
+ continue;
3977
+ if (srcPath.startsWith("data:") || srcPath.startsWith("http"))
3978
+ continue;
3979
+ let resolvedPath;
3980
+ if (srcPath.startsWith("/")) {
3981
+ resolvedPath = join(options.publicDir, srcPath);
3982
+ } else if (srcPath.startsWith("~/") || srcPath.startsWith("@/")) {
3983
+ resolvedPath = join(options.srcDir, srcPath.slice(2));
3984
+ } else if (!isAbsolute(srcPath)) {
3985
+ resolvedPath = resolve(componentDir, srcPath);
3986
+ } else {
3987
+ continue;
3988
+ }
3989
+ const fileBuffer = await readFile(resolvedPath).catch(() => {
3990
+ return null;
3991
+ });
3992
+ if (!fileBuffer)
3993
+ continue;
3994
+ const mimeType = getMimeType(ext);
3995
+ let dataUri;
3996
+ if (ext === "svg") {
3997
+ const svgContent = fileBuffer.toString("utf-8");
3998
+ dataUri = `data:${mimeType},${encodeURIComponent(svgContent)}`;
3999
+ } else {
4000
+ const base64 = fileBuffer.toString("base64");
4001
+ dataUri = `data:${mimeType};base64,${base64}`;
4002
+ }
4003
+ replacements.push({ from: fullMatch, to: `src="${dataUri}"` });
4004
+ hasChanges = true;
3856
4005
  }
3857
- const fileBuffer = await readFile(resolvedPath).catch(() => {
3858
- return null;
3859
- });
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}`;
4006
+ for (const { from, to } of replacements) {
4007
+ template = template.replace(from, to);
3870
4008
  }
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
4009
  }
3877
4010
  }
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);
4011
+ {
4012
+ const { descriptor } = parse(code);
4013
+ if (descriptor.styles.length > 0) {
4014
+ for (const styleBlock of descriptor.styles) {
4015
+ const rawCss = styleBlock.content;
4016
+ const scopeId = styleBlock.scoped ? rawCss.match(RE_SCOPED_DATA_V)?.[0] ?? null : null;
4017
+ const cleanCss = scopeId ? rawCss.replaceAll(scopeId, "") : rawCss;
4018
+ const simplified = await simplifyCss(cleanCss);
4019
+ const classMap = extractClassStyles(simplified, { skipPrefixes: [] });
4020
+ if (classMap.size > 0) {
4021
+ const wrappedTemplate = `<div>${template}</div>`;
4022
+ const doc = parse$1(wrappedTemplate);
4023
+ walkSync(doc, (node) => {
4024
+ if (node.type !== ELEMENT_NODE$2)
4025
+ return;
4026
+ const el = node;
4027
+ const classAttr = el.attributes?.class;
4028
+ if (!classAttr)
4029
+ return;
4030
+ const elClasses = classAttr.split(RE_WHITESPACE).filter(Boolean);
4031
+ const matchedStyles = {};
4032
+ const consumedClasses = /* @__PURE__ */ new Set();
4033
+ for (const cls of elClasses) {
4034
+ const styles = classMap.get(cls);
4035
+ if (styles) {
4036
+ Object.assign(matchedStyles, styles);
4037
+ consumedClasses.add(cls);
4038
+ }
3906
4039
  }
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
- }
3931
- });
3932
- template = renderSync(doc).replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
3933
- hasChanges = true;
4040
+ if (Object.keys(matchedStyles).length === 0)
4041
+ return;
4042
+ const existingStyle = el.attributes?.style || "";
4043
+ const existingProps = {};
4044
+ for (const decl of splitCssDeclarations(existingStyle)) {
4045
+ const [prop, ...valParts] = decl.split(":");
4046
+ const value = valParts.join(":").trim();
4047
+ if (prop?.trim() && value)
4048
+ existingProps[prop.trim()] = value;
4049
+ }
4050
+ const merged = { ...matchedStyles, ...existingProps };
4051
+ el.attributes.style = escapeAttrValue(
4052
+ Object.entries(merged).map(([p, v]) => `${p}:${v}`).join(";")
4053
+ );
4054
+ const remaining = elClasses.filter((c) => !consumedClasses.has(c));
4055
+ if (remaining.length > 0)
4056
+ el.attributes.class = remaining.join(" ");
4057
+ else
4058
+ delete el.attributes.class;
4059
+ if (el.attributes.viewbox) {
4060
+ el.attributes.viewBox = el.attributes.viewbox;
4061
+ delete el.attributes.viewbox;
4062
+ }
4063
+ });
4064
+ template = renderSync(doc).replace(RE_WRAPPER_DIV_START, "").replace(RE_WRAPPER_DIV_END, "");
4065
+ hasChanges = true;
4066
+ }
3934
4067
  }
3935
- }
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);
4068
+ for (const styleBlock of descriptor.styles) {
4069
+ const styleStart = code.indexOf(styleBlock.loc.source) - "<style".length;
4070
+ const fullTagStart = code.lastIndexOf("<style", styleStart + "<style".length);
4071
+ const fullTagEnd = code.indexOf("</style>", styleStart) + "</style>".length;
4072
+ if (fullTagStart >= 0 && fullTagEnd > fullTagStart) {
4073
+ s.remove(fullTagStart, fullTagEnd);
4074
+ }
3942
4075
  }
3943
4076
  }
3944
4077
  }
4078
+ if (options.onFontRequirements) {
4079
+ extractFontRequirementsFromVue(code).then((reqs) => {
4080
+ options.onFontRequirements(id, reqs);
4081
+ });
4082
+ }
4083
+ if (!hasChanges)
4084
+ return;
4085
+ s.overwrite(templateStart, templateEnd, template);
4086
+ return {
4087
+ code: s.toString(),
4088
+ map: s.generateMap({ hires: true })
4089
+ };
3945
4090
  }
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
4091
  }
3959
4092
  };
3960
4093
  });
@@ -4145,94 +4278,99 @@ const BUILTIN_COMPONENTS = /* @__PURE__ */ new Set([
4145
4278
  const MAX_IMPORT_REWRITE_DEPTH = 5;
4146
4279
  const RE_OG_IMAGE_QUERY = /\?og-image(?:-depth=(\d+))?$/;
4147
4280
  const RE_VUE_IMPORT = /import\s+(\w+)\s+from\s+(['"])(.+?\.vue)\2/g;
4281
+ const RE_VUE_ID = /\.vue(?:\?|$)/;
4148
4282
  const ComponentImportRewritePlugin = createUnplugin((options) => {
4149
4283
  return {
4150
4284
  name: "nuxt-og-image:component-import-rewrite",
4151
4285
  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);
4286
+ transform: {
4287
+ filter: {
4288
+ id: RE_VUE_ID
4289
+ },
4290
+ handler(code, rawId) {
4291
+ const depthMatch = rawId.match(RE_OG_IMAGE_QUERY);
4292
+ const currentDepth = depthMatch?.[1] ? Number.parseInt(depthMatch[1]) : 0;
4293
+ if (depthMatch) {
4294
+ if (currentDepth >= MAX_IMPORT_REWRITE_DEPTH)
4295
+ return;
4296
+ } else {
4297
+ if (!rawId.endsWith(".vue"))
4298
+ return;
4299
+ if (!options.ogComponentPaths.some((dir) => rawId.startsWith(`${dir}/`) || rawId.startsWith(`${dir}\\`)))
4300
+ return;
4174
4301
  }
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 });
4302
+ const nextQuery = `?og-image-depth=${currentDepth + 1}`;
4303
+ const { descriptor } = parse(code);
4304
+ if (!descriptor.template?.ast) {
4305
+ return;
4187
4306
  }
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);
4307
+ const usedComponents = /* @__PURE__ */ new Set();
4308
+ walkTemplateAst(descriptor.template.ast.children, (node) => {
4309
+ if (node.type === 1 && !HTML_ELEMENTS.has(node.tag) && !BUILTIN_COMPONENTS.has(node.tag)) {
4310
+ usedComponents.add(node.tag);
4205
4311
  }
4312
+ });
4313
+ if (usedComponents.size === 0) {
4314
+ return;
4206
4315
  }
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, `
4316
+ const components = options.getComponents();
4317
+ const newImports = [];
4318
+ for (const tag of usedComponents) {
4319
+ if (tag === "Icon" || tag === "UIcon")
4320
+ continue;
4321
+ const match = components.find((c) => c.pascalName === tag || c.kebabName === tag);
4322
+ if (match?.filePath && match.filePath.endsWith(".vue")) {
4323
+ newImports.push({ name: tag, filePath: match.filePath });
4324
+ }
4325
+ }
4326
+ if (newImports.length === 0) {
4327
+ return;
4328
+ }
4329
+ const s = new MagicString(code);
4330
+ const rewritten = /* @__PURE__ */ new Set();
4331
+ const scriptBlock = descriptor.scriptSetup || descriptor.script;
4332
+ if (scriptBlock) {
4333
+ const scriptStart = scriptBlock.loc.start.offset;
4334
+ const scriptContent = scriptBlock.content;
4335
+ for (const m of scriptContent.matchAll(RE_VUE_IMPORT)) {
4336
+ const importName = m[1];
4337
+ if (newImports.some((i) => i.name === importName)) {
4338
+ const source = m[3];
4339
+ const matchStart = scriptStart + m.index + m[0].indexOf(source);
4340
+ s.overwrite(matchStart, matchStart + source.length, `${source}${nextQuery}`);
4341
+ rewritten.add(importName);
4342
+ }
4343
+ }
4344
+ }
4345
+ const imports = newImports.filter((i) => !rewritten.has(i.name));
4346
+ if (imports.length === 0) {
4347
+ return {
4348
+ code: s.toString(),
4349
+ map: s.generateMap({ hires: true })
4350
+ };
4351
+ }
4352
+ const importStatements = imports.map((i) => `import ${i.name} from '${i.filePath}${nextQuery}'`).join("\n");
4353
+ if (descriptor.scriptSetup) {
4354
+ const scriptSetupStart = code.indexOf("<script setup");
4355
+ const tagEnd = code.indexOf(">", scriptSetupStart) + 1;
4356
+ s.appendRight(tagEnd, `
4220
4357
  ${importStatements}`);
4221
- } else if (descriptor.script) {
4222
- const scriptStart = code.indexOf("<script");
4223
- const tagEnd = code.indexOf(">", scriptStart) + 1;
4224
- s.appendRight(tagEnd, `
4358
+ } else if (descriptor.script) {
4359
+ const scriptStart = code.indexOf("<script");
4360
+ const tagEnd = code.indexOf(">", scriptStart) + 1;
4361
+ s.appendRight(tagEnd, `
4225
4362
  ${importStatements}`);
4226
- } else {
4227
- s.prepend(`<script setup>
4363
+ } else {
4364
+ s.prepend(`<script setup>
4228
4365
  ${importStatements}
4229
4366
  <\/script>
4230
4367
  `);
4368
+ }
4369
+ return {
4370
+ code: s.toString(),
4371
+ map: s.generateMap({ hires: true })
4372
+ };
4231
4373
  }
4232
- return {
4233
- code: s.toString(),
4234
- map: s.generateMap({ hires: true })
4235
- };
4236
4374
  }
4237
4375
  };
4238
4376
  });
@@ -4948,6 +5086,8 @@ const module$1 = defineNuxtModule({
4948
5086
  return;
4949
5087
  }
4950
5088
  const nitroCompatibility = setupNitroRuntimeCompatibility(nuxt);
5089
+ if (nuxt.options.dev && nitroCompatibility._tag === "nitro-v2")
5090
+ nuxt.options.nitro.noExternals ??= false;
4951
5091
  const setRuntimeAlias = (id, path) => {
4952
5092
  nuxt.options.alias[id] = path;
4953
5093
  nuxt.options.nitro.alias ||= {};
@@ -5284,7 +5424,7 @@ ${familyInfo}`);
5284
5424
  }
5285
5425
  });
5286
5426
  if (!nuxt.options.dev) {
5287
- addBuildPlugin(TreeShakeComposablesPlugin, { server: true, client: true, build: true });
5427
+ addBuildPlugin(TreeShakeComposablesPlugin, { server: true, client: true });
5288
5428
  setRuntimeAlias("#og-image-cache", resolve("./runtime/server/og-image/cache/mock"));
5289
5429
  }
5290
5430
  }
@@ -5893,6 +6033,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5893
6033
  colorPreference = colorModeOptions.fallback;
5894
6034
  if (!colorPreference || colorPreference === "system")
5895
6035
  colorPreference = "light";
6036
+ const includeTwitter = config.includeTwitter ?? nuxt.options.seo?.automaticTwitterTags ?? true;
5896
6037
  const runtimeConfig = {
5897
6038
  version,
5898
6039
  // binding options
@@ -5902,6 +6043,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5902
6043
  publicStoragePath: `root${publicDirAbs.replace(nuxt.options.rootDir, "").replaceAll("/", ":")}`,
5903
6044
  defaults: config.defaults,
5904
6045
  debug: config.debug,
6046
+ includeTwitter,
5905
6047
  // avoid adding credentials
5906
6048
  baseCacheKey,
5907
6049
  buildCacheDir,
@@ -5943,6 +6085,7 @@ export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.op
5943
6085
  ...nuxt.options.runtimeConfig.public,
5944
6086
  "nuxt-og-image": {
5945
6087
  defaults: runtimeConfig.defaults,
6088
+ includeTwitter: runtimeConfig.includeTwitter,
5946
6089
  hasServerRuntime: !nuxt.options._generate && !nuxt.options.nitro?.static
5947
6090
  }
5948
6091
  };