nuxt-og-image 5.1.13 → 6.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/cli.mjs +2 -0
- package/dist/chunks/tw4-classes.cjs +116 -0
- package/dist/chunks/tw4-classes.mjs +113 -0
- package/dist/chunks/tw4-generator.cjs +118 -0
- package/dist/chunks/tw4-generator.mjs +110 -0
- package/dist/cli.cjs +333 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +330 -0
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_fonts/0xp3SbCWC1OhX7q1-uF6kilMZFm-alJNkUtkLTPCy_A-tN9KwPUWhhXvtqh74sU9FIkI4W6hsbm85r0X24hjOfM.woff2 +0 -0
- package/dist/client/_fonts/1ZTlEDqU4DtwDJiND8f6qaugUpa0RIDvQl-v7iM6l54-D6hedAgqRfOCLZzaShnyeAvlEnMzk4Wm7g9WDKWFHIc.woff +0 -0
- package/dist/client/_fonts/4HA9tc4y8BVQeLXvLn3JgQqilAj1xrAnUSprQGHIPSw-ZPswEL_UDOYaxTLQDUySPjoOHDxhD83pD19HMfKfK9s.woff2 +0 -0
- package/dist/client/_fonts/Bmul3LaKlc7BUKqJHE_UmEoF40Sg_2ga52yJjwyDcKs-TnYmYl1DNYkiWMu0Vx49DakCPBuiCCj9zoLIuQjUdKY.woff2 +0 -0
- package/dist/client/_fonts/DfgmjWGpWte3Q3a54Nevr_BYmMM5YEJXRI1CdI2VwO0-ox5RadQfCyVTmKl_hubTaIJjtRw9oaQz2GDBeZR6l1M.woff2 +0 -0
- package/dist/client/_fonts/Lc_5lWuBuZcZ166p1-s-mnGkMJwIYJE_QDCkws8iCkI-r45Qbm2hCykrfOZ0kowz__uTTTUOPDN9hz34QcRNTY4.woff2 +0 -0
- package/dist/client/_fonts/iEvApgDRmzKzNqOYocBTrmcHZmuGAJloawKDP1S0nyE-T3oc_9We24QGwfw5naik4cM0g7VxylWVaQwKm4dy3cw.woff2 +0 -0
- package/dist/client/_nuxt/0kYTU2a7.js +2 -0
- package/dist/client/_nuxt/B0QDx5EE.js +2 -0
- package/dist/client/_nuxt/BKqzYw6x.js +3 -0
- package/dist/client/_nuxt/C-Ivr2Rl.js +1 -0
- package/dist/client/_nuxt/CLgn8DCr.js +1 -0
- package/dist/client/_nuxt/CwWm-XE3.js +1 -0
- package/dist/client/_nuxt/D9eL2h5z.js +1 -0
- package/dist/client/_nuxt/DJXHIJSq.js +1 -0
- package/dist/client/_nuxt/DVnX3Z-O.js +1 -0
- package/dist/client/_nuxt/DXObZt09.js +184 -0
- package/dist/client/_nuxt/Dxi6QG7I.js +1 -0
- package/dist/client/_nuxt/E8AZ6HoH.js +1 -0
- package/dist/client/_nuxt/IFrameLoader.CGrV1TpP.css +1 -0
- package/dist/client/_nuxt/JAMwWy1K.js +3864 -0
- package/dist/client/_nuxt/OSectionBlock.BVHnMsIr.css +1 -0
- package/dist/client/_nuxt/PONEy9N-.js +1 -0
- package/dist/client/_nuxt/UdkqSAsD.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/5205806b-8e5a-41ea-90c0-9cd83a75fc0a.json +1 -0
- package/dist/client/_nuxt/entry.BEExJd9R.css +2 -0
- package/dist/client/_nuxt/error-404.B79WD2X-.css +1 -0
- package/dist/client/_nuxt/error-500.DT3Sd0Wu.css +1 -0
- package/dist/client/_nuxt/pages.eW3hi7XF.css +1 -0
- package/dist/client/_nuxt/templates.dUiUBaip.css +1 -0
- package/dist/client/_payload.json +1 -0
- package/dist/client/debug/_payload.json +1 -0
- package/dist/client/debug/index.html +1 -0
- package/dist/client/docs/_payload.json +1 -0
- package/dist/client/docs/index.html +1 -0
- package/dist/client/fonts/HubotSans-Regular.woff2 +0 -0
- package/dist/client/index.html +1 -1
- package/dist/client/templates/_payload.json +1 -0
- package/dist/client/templates/index.html +1 -0
- package/dist/module.cjs +36 -1027
- package/dist/module.d.cts +63 -25
- package/dist/module.d.mts +63 -25
- package/dist/module.d.ts +63 -25
- package/dist/module.json +1 -1
- package/dist/module.mjs +32 -1009
- package/dist/runtime/app/components/Templates/Community/Brutalist.satori.d.vue.ts +14 -0
- package/dist/runtime/app/components/Templates/Community/Brutalist.satori.vue +51 -0
- package/dist/runtime/app/components/Templates/Community/Brutalist.satori.vue.d.ts +14 -0
- package/dist/runtime/app/components/Templates/Community/{Frame.vue.d.ts → Frame.satori.d.vue.ts} +2 -2
- package/dist/runtime/app/components/Templates/Community/Frame.satori.vue +71 -0
- package/dist/runtime/app/components/Templates/Community/{Frame.d.vue.ts → Frame.satori.vue.d.ts} +2 -2
- package/dist/runtime/app/components/Templates/Community/Newspaper.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/Newspaper.satori.vue +70 -0
- package/dist/runtime/app/components/Templates/Community/Newspaper.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/Nuxt.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/{Nuxt.vue → Nuxt.satori.vue} +3 -3
- package/dist/runtime/app/components/Templates/Community/Nuxt.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.satori.vue +69 -0
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/Pergel.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/{Pergel.vue → Pergel.satori.vue} +14 -11
- package/dist/runtime/app/components/Templates/Community/Pergel.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/Retro.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/Retro.satori.vue +64 -0
- package/dist/runtime/app/components/Templates/Community/Retro.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/SaaS.satori.d.vue.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/SaaS.satori.vue +60 -0
- package/dist/runtime/app/components/Templates/Community/SaaS.satori.vue.d.ts +12 -0
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.d.vue.ts +9 -0
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.vue +38 -0
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.vue.d.ts +9 -0
- package/dist/runtime/app/components/Templates/Community/{UnJs.d.vue.ts → UnJs.satori.d.vue.ts} +2 -2
- package/dist/runtime/app/components/Templates/Community/{UnJs.vue → UnJs.satori.vue} +41 -33
- package/dist/runtime/app/components/Templates/Community/{UnJs.vue.d.ts → UnJs.satori.vue.d.ts} +2 -2
- package/dist/runtime/app/components/Templates/Community/WithEmoji.satori.d.vue.ts +13 -0
- package/dist/runtime/app/components/Templates/Community/WithEmoji.satori.vue +27 -0
- package/dist/runtime/app/components/Templates/Community/WithEmoji.satori.vue.d.ts +13 -0
- package/dist/runtime/app/composables/_defineOgImageRaw.d.ts +6 -0
- package/dist/runtime/app/composables/_defineOgImageRaw.js +70 -0
- package/dist/runtime/app/composables/defineOgImage.d.ts +14 -2
- package/dist/runtime/app/composables/defineOgImage.js +13 -42
- package/dist/runtime/app/composables/defineOgImageComponent.d.ts +5 -1
- package/dist/runtime/app/composables/defineOgImageComponent.js +4 -5
- package/dist/runtime/app/composables/defineOgImageScreenshot.d.ts +1 -1
- package/dist/runtime/app/composables/defineOgImageScreenshot.js +2 -2
- package/dist/runtime/app/composables/mock.d.ts +7 -4
- package/dist/runtime/app/composables/mock.js +4 -4
- package/dist/runtime/app/utils/plugins.js +22 -28
- package/dist/runtime/app/utils.d.ts +15 -1
- package/dist/runtime/app/utils.js +74 -46
- package/dist/runtime/pure.d.ts +7 -0
- package/dist/runtime/pure.js +105 -0
- package/dist/runtime/server/og-image/bindings/css-inline/wasm-fs.d.ts +3 -2
- package/dist/runtime/server/og-image/bindings/css-inline/wasm.d.ts +3 -2
- package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.d.ts +3 -0
- package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.js +22 -0
- package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.d.ts +3 -0
- package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +49 -0
- package/dist/runtime/server/og-image/bindings/font-assets/node.d.ts +3 -0
- package/dist/runtime/server/og-image/bindings/font-assets/node.js +14 -0
- package/dist/runtime/server/og-image/bindings/resvg/node-dev.d.ts +5 -0
- package/dist/runtime/server/og-image/bindings/resvg/node-dev.js +70 -0
- package/dist/runtime/server/og-image/bindings/takumi/node.d.ts +6 -0
- package/dist/runtime/server/og-image/bindings/takumi/node.js +5 -0
- package/dist/runtime/server/og-image/bindings/takumi/wasm.d.ts +6 -0
- package/dist/runtime/server/og-image/bindings/takumi/wasm.js +6 -0
- package/dist/runtime/server/og-image/cache/buildCache.d.ts +16 -0
- package/dist/runtime/server/og-image/cache/buildCache.js +48 -0
- package/dist/runtime/server/og-image/cache/lru.d.ts +2 -2
- package/dist/runtime/server/og-image/cache/lru.js +3 -3
- package/dist/runtime/server/og-image/cache/mock.d.ts +1 -2
- package/dist/runtime/server/og-image/cache/mock.js +0 -1
- package/dist/runtime/server/og-image/chromium/screenshot.js +4 -3
- package/dist/runtime/server/og-image/context.d.ts +2 -3
- package/dist/runtime/server/og-image/context.js +55 -193
- package/dist/runtime/server/og-image/devtools.d.ts +10 -0
- package/dist/runtime/server/og-image/devtools.js +74 -0
- package/dist/runtime/server/og-image/fonts.d.ts +6 -0
- package/dist/runtime/server/og-image/fonts.js +41 -0
- package/dist/runtime/server/og-image/instances.d.ts +1 -0
- package/dist/runtime/server/og-image/instances.js +5 -0
- package/dist/runtime/server/og-image/satori/instances.d.ts +1 -36
- package/dist/runtime/server/og-image/satori/plugins/emojis.js +83 -4
- package/dist/runtime/server/og-image/satori/plugins/encoding.js +11 -1
- package/dist/runtime/server/og-image/satori/plugins/imageSrc.js +5 -1
- package/dist/runtime/server/og-image/satori/plugins/twClasses.js +35 -0
- package/dist/runtime/server/og-image/satori/renderer.js +16 -53
- package/dist/runtime/server/og-image/satori/transforms/emojis/emoji-names-minimal.d.ts +1 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/emoji-names-minimal.js +223 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/emoji-utils.d.ts +45 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/emoji-utils.js +13 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/fetch.d.ts +6 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/fetch.js +38 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/index.d.ts +7 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/index.js +64 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/local.d.ts +7 -0
- package/dist/runtime/server/og-image/satori/transforms/emojis/local.js +32 -0
- package/dist/runtime/server/og-image/satori/utils.js +5 -4
- package/dist/runtime/server/og-image/satori/vnodes.js +7 -6
- package/dist/runtime/server/og-image/takumi/instances.d.ts +1 -0
- package/dist/runtime/server/og-image/takumi/instances.js +6 -0
- package/dist/runtime/server/og-image/takumi/nodes.d.ts +12 -0
- package/dist/runtime/server/og-image/takumi/nodes.js +86 -0
- package/dist/runtime/server/og-image/takumi/renderer.d.ts +3 -0
- package/dist/runtime/server/og-image/takumi/renderer.js +45 -0
- package/dist/runtime/server/og-image/templates/html.js +32 -23
- package/dist/runtime/server/plugins/prerender.d.ts +1 -1
- package/dist/runtime/server/plugins/prerender.js +17 -7
- package/dist/runtime/server/util/auto-eject.d.ts +2 -0
- package/dist/runtime/server/util/auto-eject.js +30 -0
- package/dist/runtime/server/util/eventHandlers.d.ts +0 -1
- package/dist/runtime/server/util/eventHandlers.js +15 -85
- package/dist/runtime/server/util/options.d.ts +7 -2
- package/dist/runtime/server/util/options.js +40 -6
- package/dist/runtime/server/utils.d.ts +6 -2
- package/dist/runtime/server/utils.js +12 -8
- package/dist/runtime/shared/urlEncoding.d.ts +64 -0
- package/dist/runtime/shared/urlEncoding.js +194 -0
- package/dist/runtime/shared.d.ts +4 -9
- package/dist/runtime/shared.js +31 -50
- package/dist/runtime/types.d.ts +71 -25
- package/dist/shared/nuxt-og-image.DroaQ3v-.cjs +2852 -0
- package/dist/shared/nuxt-og-image.HMyihp-D.mjs +2825 -0
- package/dist/types.d.mts +2 -0
- package/package.json +105 -43
- package/types/virtual.d.ts +7 -1
- package/dist/client/_nuxt/B3LgXoKV.js +0 -2
- package/dist/client/_nuxt/B8PEiB0p.js +0 -1
- package/dist/client/_nuxt/CPsbVDfV.js +0 -1
- package/dist/client/_nuxt/CVO1_9PV.js +0 -1
- package/dist/client/_nuxt/CjQm5wk3.js +0 -4029
- package/dist/client/_nuxt/Cp-IABpG.js +0 -1
- package/dist/client/_nuxt/D0TMZt8T.js +0 -1
- package/dist/client/_nuxt/D0r3Knsf.js +0 -1
- package/dist/client/_nuxt/builds/meta/eb2c0390-3125-4af7-b189-e76a7dfe3017.json +0 -1
- package/dist/client/_nuxt/entry.cdy4VsCK.css +0 -1
- package/dist/client/_nuxt/error-404.Cu4JbXd7.css +0 -1
- package/dist/client/_nuxt/error-500.B79uceR7.css +0 -1
- package/dist/runtime/app/components/OgImage/OgImage.d.ts +0 -3
- package/dist/runtime/app/components/OgImage/OgImage.js +0 -10
- package/dist/runtime/app/components/Templates/Community/BrandedLogo.d.vue.ts +0 -13
- package/dist/runtime/app/components/Templates/Community/BrandedLogo.vue +0 -22
- package/dist/runtime/app/components/Templates/Community/BrandedLogo.vue.d.ts +0 -13
- package/dist/runtime/app/components/Templates/Community/Frame.vue +0 -58
- package/dist/runtime/app/components/Templates/Community/Nuxt.d.vue.ts +0 -12
- package/dist/runtime/app/components/Templates/Community/Nuxt.vue.d.ts +0 -12
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.d.vue.ts +0 -15
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.vue +0 -103
- package/dist/runtime/app/components/Templates/Community/NuxtSeo.vue.d.ts +0 -15
- package/dist/runtime/app/components/Templates/Community/Pergel.d.vue.ts +0 -12
- package/dist/runtime/app/components/Templates/Community/Pergel.vue.d.ts +0 -12
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.d.vue.ts +0 -9
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.vue +0 -27
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.vue.d.ts +0 -9
- package/dist/runtime/app/components/Templates/Community/Wave.d.vue.ts +0 -11
- package/dist/runtime/app/components/Templates/Community/Wave.vue +0 -28
- package/dist/runtime/app/components/Templates/Community/Wave.vue.d.ts +0 -11
- package/dist/runtime/app/components/Templates/Community/WithEmoji.d.vue.ts +0 -13
- package/dist/runtime/app/components/Templates/Community/WithEmoji.vue +0 -21
- package/dist/runtime/app/components/Templates/Community/WithEmoji.vue.d.ts +0 -13
- package/dist/runtime/assets/Inter-normal-400.ttf.base64 +0 -1
- package/dist/runtime/assets/Inter-normal-700.ttf.base64 +0 -1
- package/dist/runtime/server/og-image/satori/font.d.ts +0 -3
- package/dist/runtime/server/og-image/satori/font.js +0 -40
- package/dist/runtime/server/og-image/satori/plugins/unocss.js +0 -55
- package/dist/runtime/server/og-image/satori/transforms/emojis.d.ts +0 -3
- package/dist/runtime/server/og-image/satori/transforms/emojis.js +0 -3595
- package/dist/runtime/server/plugins/__zero-runtime/nuxt-content-v2.d.ts +0 -2
- package/dist/runtime/server/plugins/__zero-runtime/nuxt-content-v2.js +0 -9
- package/dist/runtime/server/plugins/nuxt-content-v2.d.ts +0 -2
- package/dist/runtime/server/plugins/nuxt-content-v2.js +0 -5
- package/dist/runtime/server/routes/__zero-runtime/font.d.ts +0 -2
- package/dist/runtime/server/routes/__zero-runtime/font.js +0 -8
- package/dist/runtime/server/routes/font.d.ts +0 -2
- package/dist/runtime/server/routes/font.js +0 -3
- package/dist/runtime/server/util/plugins.d.ts +0 -2
- package/dist/runtime/server/util/plugins.js +0 -56
- /package/dist/runtime/server/og-image/satori/plugins/{unocss.d.ts → twClasses.d.ts} +0 -0
|
@@ -0,0 +1,2825 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, rmSync } from 'node:fs';
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { resolvePath, useNuxt, addTemplate, updateTemplates, loadNuxtModuleInstance, addTypeTemplate, defineNuxtModule, createResolver, addImports, addVitePlugin, addBuildPlugin, addServerHandler, addComponentsDir, addComponent, addPlugin, hasNuxtModule, addServerPlugin } from '@nuxt/kit';
|
|
5
|
+
import { defu } from 'defu';
|
|
6
|
+
import { createJiti } from 'jiti';
|
|
7
|
+
import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
|
|
8
|
+
import { hash } from 'ohash';
|
|
9
|
+
import { join, dirname, isAbsolute, resolve, relative as relative$1 } from 'pathe';
|
|
10
|
+
import { readPackageJSON } from 'pkg-types';
|
|
11
|
+
import { isCI, provider, env, isDevelopment } from 'std-env';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { ensureDependencyInstalled, detectPackageManager, addDependency } from 'nypm';
|
|
14
|
+
import { logger } from '../../dist/runtime/logger.js';
|
|
15
|
+
import { Launcher } from 'chrome-launcher';
|
|
16
|
+
import { relative } from 'node:path';
|
|
17
|
+
import { onDevToolsInitialized, extendServerRpc, addCustomTab } from '@nuxt/devtools-kit';
|
|
18
|
+
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import MagicString from 'magic-string';
|
|
20
|
+
import { stripLiteral } from 'strip-literal';
|
|
21
|
+
import { parseURL, parseQuery } from 'ufo';
|
|
22
|
+
import { createUnplugin } from 'unplugin';
|
|
23
|
+
import postcss from 'postcss';
|
|
24
|
+
import postcssCalc from 'postcss-calc';
|
|
25
|
+
import { compile } from 'tailwindcss';
|
|
26
|
+
import { toGamut, parse, formatHex } from 'culori';
|
|
27
|
+
import { resolveModulePath } from 'exsolve';
|
|
28
|
+
import twColors from 'tailwindcss/colors';
|
|
29
|
+
import { RE_MATCH_EMOJIS, getEmojiCodePoint, getEmojiIconNames } from '../../dist/runtime/server/og-image/satori/transforms/emojis/emoji-utils.js';
|
|
30
|
+
import { parse as parse$1 } from '@vue/compiler-sfc';
|
|
31
|
+
import { loadFile, writeFile as writeFile$1 } from 'magicast';
|
|
32
|
+
import { addNuxtModule } from 'magicast/helpers';
|
|
33
|
+
|
|
34
|
+
const isUndefinedOrTruthy = (v) => typeof v === "undefined" || v !== false;
|
|
35
|
+
function checkLocalChrome() {
|
|
36
|
+
if (isCI)
|
|
37
|
+
return false;
|
|
38
|
+
let hasChromeLocally = false;
|
|
39
|
+
try {
|
|
40
|
+
hasChromeLocally = !!Launcher.getFirstInstallation();
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return hasChromeLocally;
|
|
44
|
+
}
|
|
45
|
+
async function hasResolvableDependency(dep) {
|
|
46
|
+
return await resolvePath(dep, { fallbackToOriginal: true }).catch(() => null).then((r) => r && r !== dep);
|
|
47
|
+
}
|
|
48
|
+
const VALID_RENDERER_SUFFIXES = ["satori", "chromium", "takumi"];
|
|
49
|
+
function getRendererFromFilename(filepath) {
|
|
50
|
+
const filename = filepath.split("/").pop()?.replace(".vue", "") || "";
|
|
51
|
+
for (const suffix of VALID_RENDERER_SUFFIXES) {
|
|
52
|
+
if (filename.endsWith(`.${suffix}`))
|
|
53
|
+
return suffix;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function stripRendererSuffix(name) {
|
|
58
|
+
for (const suffix of VALID_RENDERER_SUFFIXES) {
|
|
59
|
+
if (name.endsWith(`.${suffix}`) || name.endsWith(suffix.charAt(0).toUpperCase() + suffix.slice(1)))
|
|
60
|
+
return name.replace(new RegExp(`[.]?${suffix}$`, "i"), "");
|
|
61
|
+
}
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const autodetectableProviders = {
|
|
66
|
+
azure_static: "azure",
|
|
67
|
+
cloudflare_pages: "cloudflare-pages",
|
|
68
|
+
netlify: "netlify",
|
|
69
|
+
stormkit: "stormkit",
|
|
70
|
+
vercel: "vercel",
|
|
71
|
+
cleavr: "cleavr",
|
|
72
|
+
stackblitz: "stackblitz"
|
|
73
|
+
};
|
|
74
|
+
const autodetectableStaticProviders = {
|
|
75
|
+
netlify: "netlify-static",
|
|
76
|
+
vercel: "vercel-static"
|
|
77
|
+
};
|
|
78
|
+
const NodeRuntime = {
|
|
79
|
+
// node-server runtime
|
|
80
|
+
"chromium": "on-demand",
|
|
81
|
+
// this gets changed build start
|
|
82
|
+
"css-inline": "node",
|
|
83
|
+
"resvg": "node",
|
|
84
|
+
"satori": "node",
|
|
85
|
+
"takumi": "node",
|
|
86
|
+
"sharp": "node",
|
|
87
|
+
// will be disabled if they're missing the dependency
|
|
88
|
+
"emoji": "local"
|
|
89
|
+
// can bundle icons, no size constraints
|
|
90
|
+
};
|
|
91
|
+
const NodeDevRuntime = {
|
|
92
|
+
...NodeRuntime,
|
|
93
|
+
resvg: "node-dev"
|
|
94
|
+
// use worker to prevent crashes from killing process
|
|
95
|
+
};
|
|
96
|
+
const cloudflare = {
|
|
97
|
+
"chromium": false,
|
|
98
|
+
"css-inline": false,
|
|
99
|
+
"resvg": "wasm",
|
|
100
|
+
"satori": "node",
|
|
101
|
+
"takumi": "wasm",
|
|
102
|
+
"sharp": false,
|
|
103
|
+
"emoji": "fetch",
|
|
104
|
+
// edge size limits - use API instead of bundling 24MB icons
|
|
105
|
+
"wasm": {
|
|
106
|
+
esmImport: true,
|
|
107
|
+
lazy: true
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const awsLambda = {
|
|
111
|
+
"chromium": false,
|
|
112
|
+
"css-inline": "wasm",
|
|
113
|
+
"resvg": "node",
|
|
114
|
+
"satori": "node",
|
|
115
|
+
"takumi": "node",
|
|
116
|
+
"sharp": false,
|
|
117
|
+
// 0.33.x has issues
|
|
118
|
+
"emoji": "local"
|
|
119
|
+
// serverless has larger size limits
|
|
120
|
+
};
|
|
121
|
+
const WebContainer = {
|
|
122
|
+
"chromium": false,
|
|
123
|
+
"css-inline": "wasm-fs",
|
|
124
|
+
"resvg": "wasm-fs",
|
|
125
|
+
"satori": "wasm-fs",
|
|
126
|
+
"takumi": "wasm",
|
|
127
|
+
"sharp": false,
|
|
128
|
+
"emoji": "fetch"
|
|
129
|
+
// webcontainer has size constraints
|
|
130
|
+
};
|
|
131
|
+
const RuntimeCompatibility = {
|
|
132
|
+
"nitro-dev": NodeDevRuntime,
|
|
133
|
+
"nitro-prerender": NodeRuntime,
|
|
134
|
+
"node-server": NodeRuntime,
|
|
135
|
+
"stackblitz": WebContainer,
|
|
136
|
+
"codesandbox": WebContainer,
|
|
137
|
+
"aws-lambda": awsLambda,
|
|
138
|
+
"netlify": awsLambda,
|
|
139
|
+
"netlify-edge": {
|
|
140
|
+
"chromium": false,
|
|
141
|
+
"css-inline": "wasm",
|
|
142
|
+
"resvg": "wasm",
|
|
143
|
+
"satori": "node",
|
|
144
|
+
"takumi": "wasm",
|
|
145
|
+
"sharp": false,
|
|
146
|
+
"emoji": "fetch",
|
|
147
|
+
// edge size limits
|
|
148
|
+
"wasm": {
|
|
149
|
+
rollup: {
|
|
150
|
+
targetEnv: "auto-inline",
|
|
151
|
+
sync: ["@resvg/resvg-wasm/index_bg.wasm"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"firebase": awsLambda,
|
|
156
|
+
"vercel": awsLambda,
|
|
157
|
+
"vercel-edge": {
|
|
158
|
+
"chromium": false,
|
|
159
|
+
"css-inline": false,
|
|
160
|
+
// size constraint (2mb is max)
|
|
161
|
+
"resvg": "wasm",
|
|
162
|
+
"satori": "node",
|
|
163
|
+
"takumi": "wasm",
|
|
164
|
+
"sharp": false,
|
|
165
|
+
"emoji": "fetch",
|
|
166
|
+
// edge size limits - bundling 24MB icons not viable
|
|
167
|
+
"wasm": {
|
|
168
|
+
// lowers workers kb size
|
|
169
|
+
esmImport: true,
|
|
170
|
+
lazy: true
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"cloudflare-pages": cloudflare,
|
|
174
|
+
"cloudflare": cloudflare,
|
|
175
|
+
"cloudflare-module": cloudflare
|
|
176
|
+
};
|
|
177
|
+
function detectTarget(options = {}) {
|
|
178
|
+
return options?.static ? autodetectableStaticProviders[provider] : autodetectableProviders[provider];
|
|
179
|
+
}
|
|
180
|
+
function resolveNitroPreset(nitroConfig) {
|
|
181
|
+
if (provider === "stackblitz" || provider === "codesandbox")
|
|
182
|
+
return provider;
|
|
183
|
+
const nuxt = useNuxt();
|
|
184
|
+
if (nuxt.options.dev)
|
|
185
|
+
return "nitro-dev";
|
|
186
|
+
if (nuxt.options.nitro.static)
|
|
187
|
+
return "nitro-prerender";
|
|
188
|
+
let preset;
|
|
189
|
+
if (nitroConfig && nitroConfig?.preset)
|
|
190
|
+
preset = nitroConfig.preset;
|
|
191
|
+
if (!preset)
|
|
192
|
+
preset = env.NITRO_PRESET || env.SERVER_PRESET || detectTarget() || "node-server";
|
|
193
|
+
return preset.replace("_", "-");
|
|
194
|
+
}
|
|
195
|
+
function getPresetNitroPresetCompatibility(target) {
|
|
196
|
+
const normalizedTarget = target.replace(/-legacy$/, "");
|
|
197
|
+
let compatibility = RuntimeCompatibility[normalizedTarget];
|
|
198
|
+
if (!compatibility)
|
|
199
|
+
compatibility = RuntimeCompatibility["nitro-dev"];
|
|
200
|
+
return compatibility;
|
|
201
|
+
}
|
|
202
|
+
async function applyNitroPresetCompatibility(nitroConfig, options) {
|
|
203
|
+
const target = resolveNitroPreset(nitroConfig);
|
|
204
|
+
const compatibility = getPresetNitroPresetCompatibility(target);
|
|
205
|
+
const hasCssInlineNode = await hasResolvableDependency("@css-inline/css-inline");
|
|
206
|
+
const hasCssInlineWasm = await hasResolvableDependency("@css-inline/css-inline-wasm");
|
|
207
|
+
const { resolve, detectedRenderers } = options;
|
|
208
|
+
const satoriEnabled = detectedRenderers.has("satori");
|
|
209
|
+
const chromiumEnabled = detectedRenderers.has("chromium");
|
|
210
|
+
const takumiEnabled = detectedRenderers.has("takumi");
|
|
211
|
+
for (const renderer of detectedRenderers) {
|
|
212
|
+
if (!compatibility[renderer]) {
|
|
213
|
+
logger.warn(`Renderer "${renderer}" detected but not supported on "${target}" preset. OG images using .${renderer}.vue components may fail.`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const emptyMock = await resolve.resolvePath("./runtime/mock/empty");
|
|
217
|
+
nitroConfig.alias["#og-image/renderers/satori"] = satoriEnabled ? await resolve.resolvePath("./runtime/server/og-image/satori/renderer") : emptyMock;
|
|
218
|
+
nitroConfig.alias["#og-image/renderers/chromium"] = chromiumEnabled ? await resolve.resolvePath("./runtime/server/og-image/chromium/renderer") : emptyMock;
|
|
219
|
+
nitroConfig.alias["#og-image/renderers/takumi"] = takumiEnabled ? await resolve.resolvePath("./runtime/server/og-image/takumi/renderer") : emptyMock;
|
|
220
|
+
const resolvedCompatibility = {};
|
|
221
|
+
async function applyBinding(key) {
|
|
222
|
+
let binding = options.compatibility?.[key];
|
|
223
|
+
if (typeof binding === "undefined")
|
|
224
|
+
binding = compatibility[key];
|
|
225
|
+
if (key === "css-inline" && typeof binding === "string") {
|
|
226
|
+
if (binding === "node" && !hasCssInlineNode || ["wasm", "wasm-fs"].includes(binding) && !hasCssInlineWasm) {
|
|
227
|
+
binding = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
resolvedCompatibility[key] = binding;
|
|
231
|
+
return {
|
|
232
|
+
[`#og-image/bindings/${key}`]: binding === false ? emptyMock : await resolve.resolvePath(`./runtime/server/og-image/bindings/${key}/${binding}`)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
nitroConfig.alias = defu(
|
|
236
|
+
await applyBinding("chromium"),
|
|
237
|
+
await applyBinding("satori"),
|
|
238
|
+
await applyBinding("takumi"),
|
|
239
|
+
await applyBinding("resvg"),
|
|
240
|
+
await applyBinding("sharp"),
|
|
241
|
+
await applyBinding("css-inline"),
|
|
242
|
+
nitroConfig.alias || {}
|
|
243
|
+
);
|
|
244
|
+
if (Object.values(compatibility).includes("wasm")) {
|
|
245
|
+
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
246
|
+
nitroConfig.experimental.wasm = true;
|
|
247
|
+
}
|
|
248
|
+
nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
|
|
249
|
+
nitroConfig.wasm = defu(compatibility.wasm, nitroConfig.wasm);
|
|
250
|
+
const normalizedTarget = target.replace(/-legacy$/, "");
|
|
251
|
+
const isEdgePreset = ["cloudflare", "cloudflare-pages", "cloudflare-module", "vercel-edge", "netlify-edge"].includes(normalizedTarget);
|
|
252
|
+
if (isEdgePreset) {
|
|
253
|
+
const mockCode = `import proxy from 'mocked-exports/proxy';export default proxy;export * from 'mocked-exports/proxy';`;
|
|
254
|
+
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
255
|
+
nitroConfig.virtual.canvas = mockCode;
|
|
256
|
+
}
|
|
257
|
+
nitroConfig.virtual["#og-image/compatibility"] = () => `export default ${JSON.stringify(resolvedCompatibility)}`;
|
|
258
|
+
addTemplate({
|
|
259
|
+
filename: "nuxt-og-image/compatibility.mjs",
|
|
260
|
+
getContents() {
|
|
261
|
+
return `export default ${JSON.stringify(resolvedCompatibility)}`;
|
|
262
|
+
},
|
|
263
|
+
options: { mode: "server" }
|
|
264
|
+
});
|
|
265
|
+
return resolvedCompatibility;
|
|
266
|
+
}
|
|
267
|
+
function ensureDependencies(dep, nuxt = useNuxt()) {
|
|
268
|
+
return Promise.all(dep.map((d) => {
|
|
269
|
+
return ensureDependencyInstalled(d, {
|
|
270
|
+
cwd: nuxt.options.rootDir,
|
|
271
|
+
dev: true
|
|
272
|
+
});
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function setupBuildHandler(config, resolve, getDetectedRenderers, nuxt = useNuxt()) {
|
|
277
|
+
nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
|
|
278
|
+
if (typeof config.runtimeCacheStorage === "object")
|
|
279
|
+
nuxt.options.nitro.storage["nuxt-og-image"] = config.runtimeCacheStorage;
|
|
280
|
+
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
|
|
281
|
+
const mockCode = `import proxy from 'mocked-exports/proxy';export default proxy;export * from 'mocked-exports/proxy';`;
|
|
282
|
+
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
283
|
+
nitroConfig.virtual["playwright-core"] = mockCode;
|
|
284
|
+
nitroConfig.virtual.electron = mockCode;
|
|
285
|
+
nitroConfig.virtual["electron/index"] = mockCode;
|
|
286
|
+
nitroConfig.virtual["electron/index.js"] = mockCode;
|
|
287
|
+
nitroConfig.virtual.bufferutil = mockCode;
|
|
288
|
+
nitroConfig.virtual["utf-8-validate"] = mockCode;
|
|
289
|
+
nitroConfig.virtual["chromium-bidi"] = mockCode;
|
|
290
|
+
nitroConfig.virtual["chromium-bidi/lib/cjs/bidiMapper/BidiMapper"] = mockCode;
|
|
291
|
+
nitroConfig.virtual["chromium-bidi/lib/cjs/bidiMapper/BidiMapper.js"] = mockCode;
|
|
292
|
+
nitroConfig.virtual.queue = mockCode;
|
|
293
|
+
});
|
|
294
|
+
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
295
|
+
const renderers = getDetectedRenderers();
|
|
296
|
+
await applyNitroPresetCompatibility(nitro.options, { compatibility: config.compatibility?.runtime, resolve, detectedRenderers: renderers });
|
|
297
|
+
const target = resolveNitroPreset(nitro.options);
|
|
298
|
+
const normalizedTarget = target.replace(/-legacy$/, "");
|
|
299
|
+
const isCloudflarePagesOrModule = normalizedTarget === "cloudflare-pages" || normalizedTarget === "cloudflare-module";
|
|
300
|
+
if (isCloudflarePagesOrModule) {
|
|
301
|
+
nitro.options.cloudflare = nitro.options.cloudflare || {};
|
|
302
|
+
nitro.options.cloudflare.pages = nitro.options.cloudflare.pages || {};
|
|
303
|
+
nitro.options.cloudflare.pages.routes = nitro.options.cloudflare.pages.routes || { exclude: [] };
|
|
304
|
+
nitro.options.cloudflare.pages.routes.exclude = nitro.options.cloudflare.pages.routes.exclude || [];
|
|
305
|
+
nitro.options.cloudflare.pages.routes.exclude.push("/_og/s/*");
|
|
306
|
+
}
|
|
307
|
+
nitro.hooks.hook("compiled", async (_nitro) => {
|
|
308
|
+
const compatibility = getPresetNitroPresetCompatibility(target);
|
|
309
|
+
if (compatibility.wasm?.esmImport !== true)
|
|
310
|
+
return;
|
|
311
|
+
const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
|
|
312
|
+
const serverEntry = join(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
|
|
313
|
+
const wasmEntries = [serverEntry];
|
|
314
|
+
if (isCloudflarePagesOrModule) {
|
|
315
|
+
wasmEntries.push(join(dirname(serverEntry), "chunks/wasm.mjs"));
|
|
316
|
+
wasmEntries.push(join(dirname(serverEntry), "chunks/_/wasm.mjs"));
|
|
317
|
+
wasmEntries.push(join(dirname(serverEntry), "chunks/index_bg.mjs"));
|
|
318
|
+
}
|
|
319
|
+
const resvgHash = await resolveFilePathSha1("@resvg/resvg-wasm/index_bg.wasm");
|
|
320
|
+
const yogaHash = await resolveFilePathSha1("yoga-wasm-web/dist/yoga.wasm");
|
|
321
|
+
const cssInlineHash = await resolveFilePathSha1("@css-inline/css-inline-wasm/index_bg.wasm");
|
|
322
|
+
for (const entry of wasmEntries) {
|
|
323
|
+
if (!existsSync(entry))
|
|
324
|
+
continue;
|
|
325
|
+
const contents = await readFile(entry, "utf-8");
|
|
326
|
+
const postfix = target === "vercel-edge" ? "?module" : "";
|
|
327
|
+
const wasmPath = isCloudflarePagesOrModule ? `../wasm/` : `./wasm/`;
|
|
328
|
+
await writeFile(entry, contents.replaceAll('"@resvg/resvg-wasm/index_bg.wasm?module"', `"${wasmPath}index_bg-${resvgHash}.wasm${postfix}"`).replaceAll('"@css-inline/css-inline-wasm/index_bg.wasm?module"', `"${wasmPath}index_bg-${cssInlineHash}.wasm${postfix}"`).replaceAll('"yoga-wasm-web/dist/yoga.wasm?module"', `"${wasmPath}yoga-${yogaHash}.wasm${postfix}"`), { encoding: "utf-8" });
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async function resolveFilePathSha1(path) {
|
|
334
|
+
const _path = await resolvePath(path);
|
|
335
|
+
return sha1(existsSync(_path) ? await readFile(_path) : Buffer.from(path));
|
|
336
|
+
}
|
|
337
|
+
function sha1(source) {
|
|
338
|
+
return createHash("sha1").update(source).digest("hex").slice(0, 16);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function setupDevHandler(options, resolve, getDetectedRenderers, nuxt = useNuxt()) {
|
|
342
|
+
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
343
|
+
await applyNitroPresetCompatibility(nitro.options, { compatibility: options.compatibility?.dev, resolve, detectedRenderers: getDetectedRenderers() });
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const DEVTOOLS_UI_ROUTE = "/__nuxt-og-image";
|
|
348
|
+
const DEVTOOLS_UI_LOCAL_PORT = 3030;
|
|
349
|
+
function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
|
|
350
|
+
const clientPath = resolve("./client");
|
|
351
|
+
const isProductionBuild = existsSync(clientPath);
|
|
352
|
+
if (isProductionBuild) {
|
|
353
|
+
nuxt.hook("vite:serverCreated", async (server) => {
|
|
354
|
+
const sirv = await import('sirv').then((r) => r.default || r);
|
|
355
|
+
server.middlewares.use(
|
|
356
|
+
DEVTOOLS_UI_ROUTE,
|
|
357
|
+
sirv(clientPath, { dev: true, single: true })
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
nuxt.hook("vite:extendConfig", (config) => {
|
|
362
|
+
if (!config.server) {
|
|
363
|
+
config.server = {};
|
|
364
|
+
}
|
|
365
|
+
config.server.proxy ||= {};
|
|
366
|
+
config.server.proxy[DEVTOOLS_UI_ROUTE] = {
|
|
367
|
+
target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
|
|
368
|
+
changeOrigin: true,
|
|
369
|
+
followRedirects: true,
|
|
370
|
+
rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const useNitro = new Promise((resolve2) => {
|
|
375
|
+
nuxt.hooks.hook("nitro:init", resolve2);
|
|
376
|
+
});
|
|
377
|
+
onDevToolsInitialized(async () => {
|
|
378
|
+
const rpc = extendServerRpc("nuxt-og-image", {
|
|
379
|
+
async ejectCommunityTemplate(path) {
|
|
380
|
+
const [dirName, componentName] = path.split("/");
|
|
381
|
+
const dir = resolve(nuxt.options.srcDir, "components", dirName || "");
|
|
382
|
+
if (!existsSync(dir)) {
|
|
383
|
+
mkdirSync(dir);
|
|
384
|
+
}
|
|
385
|
+
const newPath = resolve(dir, componentName || "");
|
|
386
|
+
const templatePath = resolve(`./runtime/app/components/Templates/Community/${componentName}`);
|
|
387
|
+
const template = (await readFile(templatePath, "utf-8")).replace("{{ title }}", `{{ title }} - Ejected!`);
|
|
388
|
+
await writeFile(newPath, template, { encoding: "utf-8" });
|
|
389
|
+
await updateTemplates({ filter: (t) => t.filename.includes("nuxt-og-image/components.mjs") });
|
|
390
|
+
const nitro = await useNitro;
|
|
391
|
+
await nitro.hooks.callHook("rollup:reload");
|
|
392
|
+
return newPath;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
nuxt.hook("builder:watch", (e, path) => {
|
|
396
|
+
path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path));
|
|
397
|
+
if ((e === "change" || e.includes("link")) && (path.startsWith("pages") || path.startsWith("content"))) {
|
|
398
|
+
rpc.broadcast.refreshRouteData(path).catch(() => {
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (options.componentDirs.some((dir) => path.includes(dir))) {
|
|
402
|
+
if (e === "change") {
|
|
403
|
+
rpc.broadcast.refresh().catch(() => {
|
|
404
|
+
});
|
|
405
|
+
} else {
|
|
406
|
+
rpc.broadcast.refreshGlobalData().catch(() => {
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
addCustomTab({
|
|
413
|
+
name: "nuxt-og-image",
|
|
414
|
+
title: "OG Image",
|
|
415
|
+
icon: "carbon:image-search",
|
|
416
|
+
view: {
|
|
417
|
+
type: "iframe",
|
|
418
|
+
src: DEVTOOLS_UI_ROUTE
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function setupGenerateHandler(_options, resolve, _getDetectedRenderers, nuxt = useNuxt()) {
|
|
424
|
+
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
|
|
425
|
+
await applyNitroPresetCompatibility(nitroConfig, {
|
|
426
|
+
compatibility: {
|
|
427
|
+
"chromium": false,
|
|
428
|
+
"satori": false,
|
|
429
|
+
"css-inline": false,
|
|
430
|
+
"resvg": false,
|
|
431
|
+
"sharp": false
|
|
432
|
+
},
|
|
433
|
+
resolve,
|
|
434
|
+
detectedRenderers: /* @__PURE__ */ new Set()
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
440
|
+
function setupPrerenderHandler(options, resolve, getDetectedRenderers, nuxt = useNuxt()) {
|
|
441
|
+
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
442
|
+
nitro.hooks.hook("prerender:config", async (nitroConfig) => {
|
|
443
|
+
await applyNitroPresetCompatibility(nitroConfig, { compatibility: options.compatibility?.prerender, resolve, detectedRenderers: getDetectedRenderers() });
|
|
444
|
+
nitroConfig.wasm = nitroConfig.wasm || {};
|
|
445
|
+
nitroConfig.wasm.esmImport = false;
|
|
446
|
+
});
|
|
447
|
+
nitro.hooks.hook("prerender:done", async () => {
|
|
448
|
+
const buildCachePath = typeof options.buildCache === "object" && options.buildCache.base ? options.buildCache.base : "node_modules/.cache/nuxt-seo/og-image";
|
|
449
|
+
const buildCacheDir = options.buildCache ? join(nuxt.options.rootDir, buildCachePath) : null;
|
|
450
|
+
if (!buildCacheDir || !existsSync(buildCacheDir))
|
|
451
|
+
return;
|
|
452
|
+
const files = readdirSync(buildCacheDir);
|
|
453
|
+
const now = Date.now();
|
|
454
|
+
let cleanedCount = 0;
|
|
455
|
+
for (const file of files) {
|
|
456
|
+
if (file.startsWith("."))
|
|
457
|
+
continue;
|
|
458
|
+
const filePath = join(buildCacheDir, file);
|
|
459
|
+
const content = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
460
|
+
const createdAt = content.createdAt || statSync(filePath).mtimeMs;
|
|
461
|
+
const expiresAt = content.expiresAt || 0;
|
|
462
|
+
const isExpired = expiresAt < now;
|
|
463
|
+
const isOld = now - createdAt > ONE_WEEK_MS;
|
|
464
|
+
if (isExpired && isOld) {
|
|
465
|
+
rmSync(filePath);
|
|
466
|
+
cleanedCount++;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (cleanedCount > 0)
|
|
470
|
+
logger.info(`Cleaned ${cleanedCount} orphaned OG image cache file${cleanedCount > 1 ? "s" : ""}.`);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isVue(id, opts = {}) {
|
|
476
|
+
const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
477
|
+
if (id.endsWith(".vue") && !search) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (!search) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
const query = parseQuery(search);
|
|
484
|
+
if (query.nuxt_component) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
const type = "setup" in query ? "script" : query.type;
|
|
491
|
+
if (!("vue" in query) || opts.type && !opts.type.includes(type)) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
const JS_RE = /\.(?:[cm]?j|t)sx?$/;
|
|
497
|
+
function isJS(id) {
|
|
498
|
+
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
499
|
+
return JS_RE.test(pathname);
|
|
500
|
+
}
|
|
501
|
+
const TreeShakeComposablesPlugin = createUnplugin(() => {
|
|
502
|
+
const composableNames = [
|
|
503
|
+
"defineOgImage",
|
|
504
|
+
"defineOgImageComponent",
|
|
505
|
+
"defineOgImageScreenshot"
|
|
506
|
+
];
|
|
507
|
+
const regexp = `(^\\s*)(${composableNames.join("|")})(?=\\((?!\\) \\{))`;
|
|
508
|
+
const COMPOSABLE_RE = new RegExp(regexp, "m");
|
|
509
|
+
const COMPOSABLE_RE_GLOBAL = new RegExp(regexp, "gm");
|
|
510
|
+
return {
|
|
511
|
+
name: "nuxt-og-image:zero-runtime:transform",
|
|
512
|
+
enforce: "pre",
|
|
513
|
+
transformInclude(id) {
|
|
514
|
+
return isVue(id, { type: ["script"] }) || isJS(id);
|
|
515
|
+
},
|
|
516
|
+
transform(code, id) {
|
|
517
|
+
const s = new MagicString(code);
|
|
518
|
+
if (id.endsWith("components.islands.mjs")) ; else {
|
|
519
|
+
const strippedCode = stripLiteral(code);
|
|
520
|
+
if (!COMPOSABLE_RE.test(code)) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
for (const match of strippedCode.matchAll(COMPOSABLE_RE_GLOBAL)) {
|
|
524
|
+
s.overwrite(match.index, match.index + match[0].length, `${match[1]} import.meta.prerender && ${match[2]}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (s.hasChanged()) {
|
|
528
|
+
return {
|
|
529
|
+
code: s.toString(),
|
|
530
|
+
map: s.generateMap({ hires: true })
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const toSrgbGamut = toGamut("rgb", "oklch");
|
|
538
|
+
function decodeCssClassName(selector) {
|
|
539
|
+
let className = selector.startsWith(".") ? selector.slice(1) : selector;
|
|
540
|
+
className = className.replace(/\\([0-9a-f]+)\s?/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)));
|
|
541
|
+
className = className.replace(/\\(.)/g, "$1");
|
|
542
|
+
return className;
|
|
543
|
+
}
|
|
544
|
+
function createStylesheetLoader(baseDir) {
|
|
545
|
+
return async (id, base) => {
|
|
546
|
+
if (id === "tailwindcss") {
|
|
547
|
+
const twPath = resolveModulePath("tailwindcss/index.css", { from: baseDir });
|
|
548
|
+
if (twPath) {
|
|
549
|
+
const content = await readFile(twPath, "utf-8").catch(() => "");
|
|
550
|
+
if (content)
|
|
551
|
+
return { path: twPath, base: dirname(twPath), content };
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
path: "virtual:tailwindcss",
|
|
555
|
+
base,
|
|
556
|
+
content: "@layer theme, base, components, utilities;\n@layer utilities { @tailwind utilities; }"
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (id.startsWith("./") || id.startsWith("../")) {
|
|
560
|
+
const resolved2 = join(base || baseDir, id);
|
|
561
|
+
const content = await readFile(resolved2, "utf-8").catch(() => "");
|
|
562
|
+
return { path: resolved2, base: dirname(resolved2), content };
|
|
563
|
+
}
|
|
564
|
+
const resolved = resolveModulePath(id, { from: base || baseDir, conditions: ["style"] });
|
|
565
|
+
if (resolved) {
|
|
566
|
+
const content = await readFile(resolved, "utf-8").catch(() => "");
|
|
567
|
+
return { path: resolved, base: dirname(resolved), content };
|
|
568
|
+
}
|
|
569
|
+
return { path: id, base, content: "" };
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function convertColorToHex(value) {
|
|
573
|
+
if (!value || value.includes("var("))
|
|
574
|
+
return value;
|
|
575
|
+
const color = parse(value);
|
|
576
|
+
if (!color)
|
|
577
|
+
return value;
|
|
578
|
+
const mapped = toSrgbGamut(color);
|
|
579
|
+
return formatHex(mapped) || value;
|
|
580
|
+
}
|
|
581
|
+
function convertOklchToHex(css) {
|
|
582
|
+
return css.replace(/oklch\([^)]+\)/g, (match) => convertColorToHex(match));
|
|
583
|
+
}
|
|
584
|
+
const COLOR_PROPERTIES = /* @__PURE__ */ new Set([
|
|
585
|
+
"color",
|
|
586
|
+
"background-color",
|
|
587
|
+
"background-image",
|
|
588
|
+
"border-color",
|
|
589
|
+
"border-top-color",
|
|
590
|
+
"border-right-color",
|
|
591
|
+
"border-bottom-color",
|
|
592
|
+
"border-left-color",
|
|
593
|
+
"outline-color",
|
|
594
|
+
"fill",
|
|
595
|
+
"stroke",
|
|
596
|
+
"text-decoration-color",
|
|
597
|
+
"caret-color",
|
|
598
|
+
"accent-color"
|
|
599
|
+
]);
|
|
600
|
+
function buildNuxtUiVars(vars, nuxtUiColors) {
|
|
601
|
+
const colors = twColors;
|
|
602
|
+
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
|
603
|
+
for (const [semantic, colorName] of Object.entries(nuxtUiColors)) {
|
|
604
|
+
for (const shade of shades) {
|
|
605
|
+
const varName = `--ui-color-${semantic}-${shade}`;
|
|
606
|
+
const value = colors[colorName]?.[shade];
|
|
607
|
+
if (value && !vars.has(varName))
|
|
608
|
+
vars.set(varName, value);
|
|
609
|
+
}
|
|
610
|
+
if (!vars.has(`--ui-${semantic}`))
|
|
611
|
+
vars.set(`--ui-${semantic}`, colors[colorName]?.[500] || "");
|
|
612
|
+
}
|
|
613
|
+
const neutral = nuxtUiColors.neutral || "slate";
|
|
614
|
+
const neutralColors = colors[neutral];
|
|
615
|
+
const semanticVars = {
|
|
616
|
+
"--ui-text-dimmed": neutralColors?.[400] || "",
|
|
617
|
+
"--ui-text-muted": neutralColors?.[500] || "",
|
|
618
|
+
"--ui-text-toned": neutralColors?.[600] || "",
|
|
619
|
+
"--ui-text": neutralColors?.[700] || "",
|
|
620
|
+
"--ui-text-highlighted": neutralColors?.[900] || "",
|
|
621
|
+
"--ui-text-inverted": "#ffffff",
|
|
622
|
+
"--ui-bg": "#ffffff",
|
|
623
|
+
"--ui-bg-muted": neutralColors?.[50] || "",
|
|
624
|
+
"--ui-bg-elevated": neutralColors?.[100] || "",
|
|
625
|
+
"--ui-bg-accented": neutralColors?.[200] || "",
|
|
626
|
+
"--ui-bg-inverted": neutralColors?.[900] || "",
|
|
627
|
+
"--ui-border": neutralColors?.[200] || "",
|
|
628
|
+
"--ui-border-muted": neutralColors?.[200] || "",
|
|
629
|
+
"--ui-border-accented": neutralColors?.[300] || "",
|
|
630
|
+
"--ui-border-inverted": neutralColors?.[900] || ""
|
|
631
|
+
};
|
|
632
|
+
for (const [name, value] of Object.entries(semanticVars)) {
|
|
633
|
+
if (value && !vars.has(name))
|
|
634
|
+
vars.set(name, value);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function walkTemplateAst(nodes, visitor) {
|
|
638
|
+
for (const node of nodes) {
|
|
639
|
+
visitor(node);
|
|
640
|
+
if ("children" in node && Array.isArray(node.children))
|
|
641
|
+
walkTemplateAst(node.children, visitor);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
let cachedCompiler = null;
|
|
646
|
+
let cachedCssPath = null;
|
|
647
|
+
let cachedVars = null;
|
|
648
|
+
const resolvedStyleCache = /* @__PURE__ */ new Map();
|
|
649
|
+
async function getCompiler(cssPath, nuxtUiColors) {
|
|
650
|
+
if (cachedCompiler && cachedCssPath === cssPath)
|
|
651
|
+
return { compiler: cachedCompiler, vars: cachedVars };
|
|
652
|
+
const userCss = await readFile(cssPath, "utf-8");
|
|
653
|
+
const baseDir = dirname(cssPath);
|
|
654
|
+
const compiler = await compile(userCss, {
|
|
655
|
+
loadStylesheet: createStylesheetLoader(baseDir)
|
|
656
|
+
});
|
|
657
|
+
const vars = /* @__PURE__ */ new Map();
|
|
658
|
+
if (nuxtUiColors)
|
|
659
|
+
buildNuxtUiVars(vars, nuxtUiColors);
|
|
660
|
+
cachedCompiler = compiler;
|
|
661
|
+
cachedCssPath = cssPath;
|
|
662
|
+
cachedVars = vars;
|
|
663
|
+
resolvedStyleCache.clear();
|
|
664
|
+
return { compiler, vars };
|
|
665
|
+
}
|
|
666
|
+
function parseCssOutput(css, vars) {
|
|
667
|
+
const classes = /* @__PURE__ */ new Map();
|
|
668
|
+
const root = postcss.parse(css);
|
|
669
|
+
root.walkRules((rule) => {
|
|
670
|
+
if (rule.selector.includes(":root") || rule.selector.includes(":host")) {
|
|
671
|
+
rule.walkDecls((decl) => {
|
|
672
|
+
if (decl.prop.startsWith("--") && !vars.has(decl.prop))
|
|
673
|
+
vars.set(decl.prop, decl.value);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
root.walkRules((rule) => {
|
|
678
|
+
const sel = rule.selector;
|
|
679
|
+
if (!sel.startsWith(".") || sel.includes(":") || sel.includes(" ") || sel.includes(">"))
|
|
680
|
+
return;
|
|
681
|
+
const className = decodeCssClassName(sel);
|
|
682
|
+
const styles = {};
|
|
683
|
+
rule.walkDecls((decl) => {
|
|
684
|
+
if (decl.prop.startsWith("--tw-"))
|
|
685
|
+
return;
|
|
686
|
+
styles[decl.prop] = decl.value;
|
|
687
|
+
});
|
|
688
|
+
if (Object.keys(styles).length)
|
|
689
|
+
classes.set(className, styles);
|
|
690
|
+
});
|
|
691
|
+
return classes;
|
|
692
|
+
}
|
|
693
|
+
function evaluateCalc(value) {
|
|
694
|
+
if (!value.includes("calc("))
|
|
695
|
+
return value;
|
|
696
|
+
const fakeCss = `.x{v:${value}}`;
|
|
697
|
+
const result = postcss([postcssCalc({})]).process(fakeCss, { from: void 0 });
|
|
698
|
+
const match = result.css.match(/\.x\{v:(.+)\}/);
|
|
699
|
+
return match?.[1] ?? value;
|
|
700
|
+
}
|
|
701
|
+
function parseVarExpression(value, startIndex) {
|
|
702
|
+
if (!value.startsWith("var(", startIndex))
|
|
703
|
+
return null;
|
|
704
|
+
let depth = 1;
|
|
705
|
+
let i = startIndex + 4;
|
|
706
|
+
let varName = "";
|
|
707
|
+
let fallback = null;
|
|
708
|
+
let inFallback = false;
|
|
709
|
+
while (i < value.length && depth > 0) {
|
|
710
|
+
const char = value[i];
|
|
711
|
+
if (char === "(") {
|
|
712
|
+
depth++;
|
|
713
|
+
if (inFallback)
|
|
714
|
+
fallback += char;
|
|
715
|
+
} else if (char === ")") {
|
|
716
|
+
depth--;
|
|
717
|
+
if (depth > 0 && inFallback)
|
|
718
|
+
fallback += char;
|
|
719
|
+
} else if (char === "," && depth === 1 && !inFallback) {
|
|
720
|
+
inFallback = true;
|
|
721
|
+
fallback = "";
|
|
722
|
+
i++;
|
|
723
|
+
while (i < value.length && value[i] === " ")
|
|
724
|
+
i++;
|
|
725
|
+
continue;
|
|
726
|
+
} else if (inFallback && char) {
|
|
727
|
+
fallback += char;
|
|
728
|
+
} else {
|
|
729
|
+
varName += char;
|
|
730
|
+
}
|
|
731
|
+
i++;
|
|
732
|
+
}
|
|
733
|
+
if (depth !== 0 || !varName.startsWith("--"))
|
|
734
|
+
return null;
|
|
735
|
+
return {
|
|
736
|
+
varName: varName.trim(),
|
|
737
|
+
fallback: fallback?.trim() || null,
|
|
738
|
+
endIndex: i
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function resolveVars(value, vars, depth = 0) {
|
|
742
|
+
if (depth > 10 || !value.includes("var("))
|
|
743
|
+
return evaluateCalc(value);
|
|
744
|
+
const calcMatch = value.match(/calc\(var\((--[\w-]+)\)\s*\*\s*([\d.]+)\)/);
|
|
745
|
+
if (calcMatch?.[1] && calcMatch?.[2]) {
|
|
746
|
+
const varValue = vars.get(calcMatch[1]);
|
|
747
|
+
if (varValue) {
|
|
748
|
+
const numMatch = varValue.match(/([\d.]+)(rem|px|em|%)/);
|
|
749
|
+
if (numMatch?.[1] && numMatch?.[2]) {
|
|
750
|
+
const computed = Number.parseFloat(numMatch[1]) * Number.parseFloat(calcMatch[2]);
|
|
751
|
+
return `${computed}${numMatch[2]}`;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
let result = value;
|
|
756
|
+
let iterations = 0;
|
|
757
|
+
const maxIterations = 50;
|
|
758
|
+
while (result.includes("var(") && iterations < maxIterations) {
|
|
759
|
+
const varIndex = result.indexOf("var(");
|
|
760
|
+
if (varIndex === -1)
|
|
761
|
+
break;
|
|
762
|
+
const parsed = parseVarExpression(result, varIndex);
|
|
763
|
+
if (!parsed) {
|
|
764
|
+
result = result.slice(0, varIndex) + result.slice(varIndex + 4);
|
|
765
|
+
iterations++;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const resolved = vars.get(parsed.varName) ?? parsed.fallback;
|
|
769
|
+
if (resolved) {
|
|
770
|
+
result = result.slice(0, varIndex) + resolved + result.slice(parsed.endIndex);
|
|
771
|
+
} else {
|
|
772
|
+
result = result.slice(0, varIndex) + result.slice(parsed.endIndex);
|
|
773
|
+
}
|
|
774
|
+
iterations++;
|
|
775
|
+
}
|
|
776
|
+
if (result.includes("var(") && depth < 10)
|
|
777
|
+
return resolveVars(result, vars, depth + 1);
|
|
778
|
+
return evaluateCalc(result);
|
|
779
|
+
}
|
|
780
|
+
function convertGradientColors(value) {
|
|
781
|
+
return value.replace(/oklch\([^)]+\)/g, (match) => {
|
|
782
|
+
const hex = convertColorToHex(match);
|
|
783
|
+
return hex.startsWith("#") ? hex : match;
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
const LINEAR_GRADIENT_DIRECTIONS = {
|
|
787
|
+
"bg-gradient-to-t": "to top",
|
|
788
|
+
"bg-gradient-to-tr": "to top right",
|
|
789
|
+
"bg-gradient-to-r": "to right",
|
|
790
|
+
"bg-gradient-to-br": "to bottom right",
|
|
791
|
+
"bg-gradient-to-b": "to bottom",
|
|
792
|
+
"bg-gradient-to-bl": "to bottom left",
|
|
793
|
+
"bg-gradient-to-l": "to left",
|
|
794
|
+
"bg-gradient-to-tl": "to top left"
|
|
795
|
+
};
|
|
796
|
+
const RADIAL_GRADIENT_SHAPES = {
|
|
797
|
+
"bg-radial": "circle at center",
|
|
798
|
+
"bg-radial-at-t": "circle at top",
|
|
799
|
+
"bg-radial-at-tr": "circle at top right",
|
|
800
|
+
"bg-radial-at-r": "circle at right",
|
|
801
|
+
"bg-radial-at-br": "circle at bottom right",
|
|
802
|
+
"bg-radial-at-b": "circle at bottom",
|
|
803
|
+
"bg-radial-at-bl": "circle at bottom left",
|
|
804
|
+
"bg-radial-at-l": "circle at left",
|
|
805
|
+
"bg-radial-at-tl": "circle at top left",
|
|
806
|
+
"bg-radial-at-c": "circle at center"
|
|
807
|
+
};
|
|
808
|
+
function resolveGradientColor(cls, vars) {
|
|
809
|
+
const colorName = cls.replace(/^(from|via|to)-/, "");
|
|
810
|
+
const shadeMatch = colorName.match(/^(.+)-(\d+)$/);
|
|
811
|
+
if (shadeMatch?.[1] && shadeMatch?.[2]) {
|
|
812
|
+
const varName2 = `--color-${shadeMatch[1]}-${shadeMatch[2]}`;
|
|
813
|
+
const value2 = vars.get(varName2);
|
|
814
|
+
if (value2) {
|
|
815
|
+
const resolved = resolveVars(value2, vars);
|
|
816
|
+
if (!resolved.includes("var("))
|
|
817
|
+
return convertColorToHex(resolved);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const varName = `--color-${colorName}`;
|
|
821
|
+
const value = vars.get(varName);
|
|
822
|
+
if (value) {
|
|
823
|
+
const resolved = resolveVars(value, vars);
|
|
824
|
+
if (!resolved.includes("var("))
|
|
825
|
+
return convertColorToHex(resolved);
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
function buildGradient(classes, vars) {
|
|
830
|
+
const linearDir = classes.find((c) => LINEAR_GRADIENT_DIRECTIONS[c]);
|
|
831
|
+
const radialShape = classes.find((c) => RADIAL_GRADIENT_SHAPES[c]);
|
|
832
|
+
const fromClass = classes.find((c) => c.startsWith("from-"));
|
|
833
|
+
const viaClass = classes.find((c) => c.startsWith("via-"));
|
|
834
|
+
const toClass = classes.find((c) => c.startsWith("to-") && !/^to-(?:[tblr]|tl|tr|bl|br)$/.test(c));
|
|
835
|
+
if (!linearDir && !radialShape)
|
|
836
|
+
return null;
|
|
837
|
+
if (!fromClass && !toClass)
|
|
838
|
+
return null;
|
|
839
|
+
const fromColor = fromClass ? resolveGradientColor(fromClass, vars) : null;
|
|
840
|
+
const viaColor = viaClass ? resolveGradientColor(viaClass, vars) : null;
|
|
841
|
+
const toColor = toClass ? resolveGradientColor(toClass, vars) : null;
|
|
842
|
+
if (!fromColor && !toColor)
|
|
843
|
+
return null;
|
|
844
|
+
const stops = [fromColor, viaColor, toColor].filter(Boolean).join(", ");
|
|
845
|
+
const colorClasses = [fromClass, viaClass, toClass].filter((c) => !!c);
|
|
846
|
+
if (linearDir) {
|
|
847
|
+
const direction = LINEAR_GRADIENT_DIRECTIONS[linearDir];
|
|
848
|
+
return {
|
|
849
|
+
gradientClass: linearDir,
|
|
850
|
+
value: `linear-gradient(${direction}, ${stops})`,
|
|
851
|
+
colorClasses
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
if (radialShape) {
|
|
855
|
+
const shape = RADIAL_GRADIENT_SHAPES[radialShape];
|
|
856
|
+
return {
|
|
857
|
+
gradientClass: radialShape,
|
|
858
|
+
value: `radial-gradient(${shape}, ${stops})`,
|
|
859
|
+
colorClasses
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
async function resolveClassesToStyles(classes, options) {
|
|
865
|
+
const { compiler, vars } = await getCompiler(options.cssPath, options.nuxtUiColors);
|
|
866
|
+
const uncached = classes.filter((c) => !resolvedStyleCache.has(c));
|
|
867
|
+
if (uncached.length > 0) {
|
|
868
|
+
const outputCss = compiler.build(uncached);
|
|
869
|
+
const parsedClasses = parseCssOutput(outputCss, vars);
|
|
870
|
+
for (const [className, rawStyles] of parsedClasses) {
|
|
871
|
+
const styles = {};
|
|
872
|
+
for (const [prop, rawValue] of Object.entries(rawStyles)) {
|
|
873
|
+
let value = resolveVars(rawValue, vars);
|
|
874
|
+
if (value.includes("var("))
|
|
875
|
+
continue;
|
|
876
|
+
if (COLOR_PROPERTIES.has(prop)) {
|
|
877
|
+
if (prop === "background-image" && value.includes("gradient")) {
|
|
878
|
+
value = convertGradientColors(value);
|
|
879
|
+
} else {
|
|
880
|
+
value = convertColorToHex(value);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
styles[prop] = value;
|
|
884
|
+
}
|
|
885
|
+
resolvedStyleCache.set(className, Object.keys(styles).length ? styles : null);
|
|
886
|
+
}
|
|
887
|
+
for (const c of uncached) {
|
|
888
|
+
if (!resolvedStyleCache.has(c))
|
|
889
|
+
resolvedStyleCache.set(c, null);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const result = {};
|
|
893
|
+
for (const cls of classes) {
|
|
894
|
+
const resolved = resolvedStyleCache.get(cls);
|
|
895
|
+
if (resolved)
|
|
896
|
+
result[cls] = resolved;
|
|
897
|
+
}
|
|
898
|
+
const gradient = buildGradient(classes, vars);
|
|
899
|
+
if (gradient) {
|
|
900
|
+
result[gradient.gradientClass] = { "background-image": gradient.value };
|
|
901
|
+
for (const colorClass of gradient.colorClasses) {
|
|
902
|
+
result[colorClass] = {};
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
async function extractTw4Metadata(options) {
|
|
908
|
+
const { compiler, vars } = await getCompiler(options.cssPath, options.nuxtUiColors);
|
|
909
|
+
const themeCss = compiler.build([]);
|
|
910
|
+
parseCssOutput(themeCss, vars);
|
|
911
|
+
const fontVars = {};
|
|
912
|
+
const breakpoints = {};
|
|
913
|
+
const colors = {};
|
|
914
|
+
for (const [name, value] of vars) {
|
|
915
|
+
const resolvedValue = resolveVars(value, vars);
|
|
916
|
+
if (name.startsWith("--font-")) {
|
|
917
|
+
fontVars[name.slice(2)] = resolvedValue;
|
|
918
|
+
} else if (name.startsWith("--breakpoint-")) {
|
|
919
|
+
const px = Number.parseInt(resolvedValue.replace("px", ""), 10);
|
|
920
|
+
if (!Number.isNaN(px))
|
|
921
|
+
breakpoints[name.slice(13)] = px;
|
|
922
|
+
} else if (name.startsWith("--color-") && !resolvedValue.includes("var(")) {
|
|
923
|
+
const colorPath = name.slice(8);
|
|
924
|
+
const shadeMatch = colorPath.match(/^(.+)-(\d+)$/);
|
|
925
|
+
const hexValue = convertColorToHex(resolvedValue);
|
|
926
|
+
if (shadeMatch) {
|
|
927
|
+
const [, colorName, shade] = shadeMatch;
|
|
928
|
+
if (colorName) {
|
|
929
|
+
if (!colors[colorName])
|
|
930
|
+
colors[colorName] = {};
|
|
931
|
+
if (typeof colors[colorName] === "object")
|
|
932
|
+
colors[colorName][shade] = hexValue;
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
colors[colorPath] = hexValue;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return { fontVars, breakpoints, colors };
|
|
940
|
+
}
|
|
941
|
+
function clearTw4Cache() {
|
|
942
|
+
cachedCompiler = null;
|
|
943
|
+
cachedCssPath = null;
|
|
944
|
+
cachedVars = null;
|
|
945
|
+
resolvedStyleCache.clear();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const ELEMENT_NODE = 1;
|
|
949
|
+
const ATTRIBUTE_NODE = 6;
|
|
950
|
+
function escapeAttrValue(value) {
|
|
951
|
+
return value.replace(/"/g, """);
|
|
952
|
+
}
|
|
953
|
+
async function transformVueTemplate(code, options) {
|
|
954
|
+
const { descriptor } = parse$1(code);
|
|
955
|
+
if (!descriptor.template?.ast)
|
|
956
|
+
return void 0;
|
|
957
|
+
const s = new MagicString(code);
|
|
958
|
+
const collectors = [];
|
|
959
|
+
walkTemplateAst(descriptor.template.ast.children, (node) => {
|
|
960
|
+
if (node.type !== ELEMENT_NODE)
|
|
961
|
+
return;
|
|
962
|
+
const el = node;
|
|
963
|
+
const collector = {
|
|
964
|
+
classes: [],
|
|
965
|
+
elementLoc: { start: el.loc.start.offset, end: el.loc.end.offset }
|
|
966
|
+
};
|
|
967
|
+
for (const prop of el.props) {
|
|
968
|
+
if (prop.type === ATTRIBUTE_NODE && prop.name === "class" && prop.value) {
|
|
969
|
+
collector.classes = prop.value.content.split(/\s+/).filter(Boolean);
|
|
970
|
+
collector.classLoc = {
|
|
971
|
+
start: prop.loc.start.offset,
|
|
972
|
+
end: prop.loc.end.offset
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
if (prop.type === ATTRIBUTE_NODE && prop.name === "style" && prop.value) {
|
|
976
|
+
collector.existingStyle = prop.value.content;
|
|
977
|
+
collector.styleLoc = {
|
|
978
|
+
start: prop.loc.start.offset,
|
|
979
|
+
end: prop.loc.end.offset
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (collector.classes.length > 0) {
|
|
984
|
+
collectors.push(collector);
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
if (collectors.length === 0)
|
|
988
|
+
return void 0;
|
|
989
|
+
let hasChanges = false;
|
|
990
|
+
for (const collector of collectors.reverse()) {
|
|
991
|
+
const styleMap = await options.resolveStyles(collector.classes);
|
|
992
|
+
const styleProps = {};
|
|
993
|
+
const unresolvedClasses = [];
|
|
994
|
+
for (const cls of collector.classes) {
|
|
995
|
+
const resolved = styleMap[cls];
|
|
996
|
+
if (resolved && Object.keys(resolved).length > 0) {
|
|
997
|
+
Object.assign(styleProps, resolved);
|
|
998
|
+
} else if (!resolved) {
|
|
999
|
+
unresolvedClasses.push(cls);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (collector.existingStyle) {
|
|
1003
|
+
for (const decl of collector.existingStyle.split(";")) {
|
|
1004
|
+
const [prop, ...valParts] = decl.split(":");
|
|
1005
|
+
const value = valParts.join(":").trim();
|
|
1006
|
+
if (prop?.trim() && value) {
|
|
1007
|
+
styleProps[prop.trim()] = value;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const styleStr = Object.entries(styleProps).map(([prop, value]) => `${prop}: ${escapeAttrValue(value)}`).join("; ");
|
|
1012
|
+
const hasResolvedStyles = Object.keys(styleProps).length > 0;
|
|
1013
|
+
const hasUnresolved = unresolvedClasses.length > 0;
|
|
1014
|
+
const resolvedSome = unresolvedClasses.length < collector.classes.length;
|
|
1015
|
+
if (!resolvedSome)
|
|
1016
|
+
continue;
|
|
1017
|
+
hasChanges = true;
|
|
1018
|
+
if (!collector.classLoc)
|
|
1019
|
+
continue;
|
|
1020
|
+
if (hasUnresolved) {
|
|
1021
|
+
s.overwrite(collector.classLoc.start, collector.classLoc.end, `class="${unresolvedClasses.join(" ")}"`);
|
|
1022
|
+
} else {
|
|
1023
|
+
s.remove(collector.classLoc.start, collector.classLoc.end);
|
|
1024
|
+
}
|
|
1025
|
+
if (hasResolvedStyles) {
|
|
1026
|
+
if (collector.styleLoc) {
|
|
1027
|
+
s.overwrite(collector.styleLoc.start, collector.styleLoc.end, `style="${styleStr}"`);
|
|
1028
|
+
} else {
|
|
1029
|
+
s.appendLeft(collector.classLoc.start, `style="${styleStr}" `);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!hasChanges)
|
|
1034
|
+
return void 0;
|
|
1035
|
+
return {
|
|
1036
|
+
code: s.toString(),
|
|
1037
|
+
map: s.generateMap({ hires: true })
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
let svgCounter = 0;
|
|
1042
|
+
function makeIdsUnique(svg) {
|
|
1043
|
+
const prefix = `og${svgCounter++}_`;
|
|
1044
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1045
|
+
svg.replace(/\bid="([^"]+)"/g, (_, id) => {
|
|
1046
|
+
ids.add(id);
|
|
1047
|
+
return "";
|
|
1048
|
+
});
|
|
1049
|
+
let result = svg;
|
|
1050
|
+
for (const id of ids) {
|
|
1051
|
+
result = result.replace(new RegExp(`id="${id}"`, "g"), `id="${prefix}${id}"`).replace(new RegExp(`url\\(#${id}\\)`, "g"), `url(#${prefix}${id})`).replace(new RegExp(`href="#${id}"`, "g"), `href="#${prefix}${id}"`);
|
|
1052
|
+
}
|
|
1053
|
+
return result;
|
|
1054
|
+
}
|
|
1055
|
+
function wrapDefsElements(body) {
|
|
1056
|
+
const defsElements = ["linearGradient", "radialGradient", "filter", "clipPath", "mask", "pattern"];
|
|
1057
|
+
const defsRegex = new RegExp(`<(${defsElements.join("|")})[\\s\\S]*?<\\/\\1>`, "g");
|
|
1058
|
+
if (body.includes("<defs>") || body.includes("<defs "))
|
|
1059
|
+
return body;
|
|
1060
|
+
const foundDefs = [];
|
|
1061
|
+
const result = body.replace(defsRegex, (match) => {
|
|
1062
|
+
foundDefs.push(match);
|
|
1063
|
+
return "";
|
|
1064
|
+
});
|
|
1065
|
+
if (foundDefs.length > 0)
|
|
1066
|
+
return `<defs>${foundDefs.join("")}</defs>${result}`;
|
|
1067
|
+
return body;
|
|
1068
|
+
}
|
|
1069
|
+
function buildIconSvg(iconData, defaultWidth, defaultHeight, attrs) {
|
|
1070
|
+
const body = wrapDefsElements(iconData.body || "");
|
|
1071
|
+
const width = iconData.width || defaultWidth;
|
|
1072
|
+
const height = iconData.height || defaultHeight;
|
|
1073
|
+
const attrPairs = attrs.matchAll(/(\w+(?:-\w+)*)="([^"]*)"/g);
|
|
1074
|
+
const filteredAttrs = [];
|
|
1075
|
+
for (const [, key, value] of attrPairs) {
|
|
1076
|
+
if (key !== "name")
|
|
1077
|
+
filteredAttrs.push(`${key}="${value}"`);
|
|
1078
|
+
}
|
|
1079
|
+
const attrsStr = filteredAttrs.length > 0 ? ` ${filteredAttrs.join(" ")}` : "";
|
|
1080
|
+
let svg = `<span${attrsStr} style="display:flex"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="100%" height="100%" fill="currentColor">${body}</svg></span>`;
|
|
1081
|
+
svg = makeIdsUnique(svg);
|
|
1082
|
+
return svg;
|
|
1083
|
+
}
|
|
1084
|
+
const iconCache = /* @__PURE__ */ new Map();
|
|
1085
|
+
async function loadIconSet(prefix) {
|
|
1086
|
+
if (iconCache.has(prefix))
|
|
1087
|
+
return iconCache.get(prefix);
|
|
1088
|
+
const icons = await import(`@iconify-json/${prefix}/icons.json`, { with: { type: 'json' } }).then((m) => m.default).catch(() => null);
|
|
1089
|
+
iconCache.set(prefix, icons);
|
|
1090
|
+
return icons;
|
|
1091
|
+
}
|
|
1092
|
+
function buildEmojiSvg(emoji, icons, emojiSet) {
|
|
1093
|
+
const codePoint = getEmojiCodePoint(emoji);
|
|
1094
|
+
const possibleNames = getEmojiIconNames(codePoint, emojiSet);
|
|
1095
|
+
for (const iconName of possibleNames) {
|
|
1096
|
+
const iconData = icons.icons?.[iconName];
|
|
1097
|
+
if (iconData) {
|
|
1098
|
+
const body = wrapDefsElements(iconData.body || "");
|
|
1099
|
+
const width = iconData.width || icons.width || 128;
|
|
1100
|
+
const height = iconData.height || icons.height || 128;
|
|
1101
|
+
let svg = `<span style="display:flex"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="1em" height="1em">${body}</svg></span>`;
|
|
1102
|
+
svg = makeIdsUnique(svg);
|
|
1103
|
+
return svg;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
function getMimeType(ext) {
|
|
1109
|
+
const mimeTypes = {
|
|
1110
|
+
png: "image/png",
|
|
1111
|
+
jpg: "image/jpeg",
|
|
1112
|
+
jpeg: "image/jpeg",
|
|
1113
|
+
gif: "image/gif",
|
|
1114
|
+
webp: "image/webp",
|
|
1115
|
+
svg: "image/svg+xml"
|
|
1116
|
+
};
|
|
1117
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
1118
|
+
}
|
|
1119
|
+
const SATORI_UNSUPPORTED_PATTERNS = [
|
|
1120
|
+
/^ring(-|$)/,
|
|
1121
|
+
// Ring utilities - not supported
|
|
1122
|
+
/^divide(-|$)/,
|
|
1123
|
+
// Divide utilities - not supported
|
|
1124
|
+
/^space-(x|y)(-|$)/,
|
|
1125
|
+
// Space utilities - use gap instead
|
|
1126
|
+
/^backdrop-/,
|
|
1127
|
+
// Backdrop utilities - not supported
|
|
1128
|
+
// Note: filter utilities (blur, brightness, etc.) ARE supported by Satori
|
|
1129
|
+
// Note: skew transforms ARE supported by Satori
|
|
1130
|
+
/^transition(-|$)/,
|
|
1131
|
+
// Animation (static image)
|
|
1132
|
+
/^animate(-|$)/,
|
|
1133
|
+
/^duration(-|$)/,
|
|
1134
|
+
/^ease(-|$)/,
|
|
1135
|
+
/^delay(-|$)/,
|
|
1136
|
+
/^scroll(-|$)/,
|
|
1137
|
+
// Scroll utilities
|
|
1138
|
+
/^snap(-|$)/,
|
|
1139
|
+
/^touch(-|$)/,
|
|
1140
|
+
// Touch/pointer
|
|
1141
|
+
/^pointer-events(-|$)/,
|
|
1142
|
+
/^cursor(-|$)/,
|
|
1143
|
+
/^select(-|$)/,
|
|
1144
|
+
// User select
|
|
1145
|
+
/^will-change(-|$)/,
|
|
1146
|
+
/^placeholder(-|$)/,
|
|
1147
|
+
/^caret(-|$)/,
|
|
1148
|
+
/^accent(-|$)/,
|
|
1149
|
+
/^columns(-|$)/,
|
|
1150
|
+
/^break-(before|after|inside)(-|$)/,
|
|
1151
|
+
/^hyphens(-|$)/,
|
|
1152
|
+
/^content-(?!center|start|end|between|around|evenly|stretch)/
|
|
1153
|
+
// content-* except flex alignment
|
|
1154
|
+
];
|
|
1155
|
+
function isSatoriUnsupported(cls) {
|
|
1156
|
+
return SATORI_UNSUPPORTED_PATTERNS.some((p) => p.test(cls));
|
|
1157
|
+
}
|
|
1158
|
+
const AssetTransformPlugin = createUnplugin((options) => {
|
|
1159
|
+
let emojiIcons = null;
|
|
1160
|
+
return {
|
|
1161
|
+
name: "nuxt-og-image:asset-transform",
|
|
1162
|
+
enforce: "pre",
|
|
1163
|
+
transformInclude(id) {
|
|
1164
|
+
if (!id.endsWith(".vue") || id.includes("node_modules"))
|
|
1165
|
+
return false;
|
|
1166
|
+
return options.ogComponentPaths.some((dir) => id.startsWith(`${dir}/`) || id.startsWith(`${dir}\\`));
|
|
1167
|
+
},
|
|
1168
|
+
async transform(code, id) {
|
|
1169
|
+
const templateMatch = code.match(/<template>([\s\S]*?)<\/template>/);
|
|
1170
|
+
if (!templateMatch)
|
|
1171
|
+
return;
|
|
1172
|
+
const s = new MagicString(code);
|
|
1173
|
+
const templateStart = code.indexOf("<template>") + "<template>".length;
|
|
1174
|
+
const templateEnd = code.indexOf("</template>");
|
|
1175
|
+
let template = code.slice(templateStart, templateEnd);
|
|
1176
|
+
let hasChanges = false;
|
|
1177
|
+
if (options.emojiSet && RE_MATCH_EMOJIS.test(template)) {
|
|
1178
|
+
if (!emojiIcons) {
|
|
1179
|
+
emojiIcons = await import(`@iconify-json/${options.emojiSet}/icons.json`, { with: { type: 'json' } }).then((m) => m.default).catch(() => null);
|
|
1180
|
+
}
|
|
1181
|
+
if (emojiIcons) {
|
|
1182
|
+
RE_MATCH_EMOJIS.lastIndex = 0;
|
|
1183
|
+
template = template.replace(/>([^<]*)</g, (fullMatch, textContent) => {
|
|
1184
|
+
if (!textContent)
|
|
1185
|
+
return fullMatch;
|
|
1186
|
+
RE_MATCH_EMOJIS.lastIndex = 0;
|
|
1187
|
+
const emojiMatches = [...textContent.matchAll(RE_MATCH_EMOJIS)];
|
|
1188
|
+
if (!emojiMatches.length)
|
|
1189
|
+
return fullMatch;
|
|
1190
|
+
let newTextContent = textContent;
|
|
1191
|
+
for (const match of emojiMatches) {
|
|
1192
|
+
const emoji = match[0];
|
|
1193
|
+
const svg = buildEmojiSvg(emoji, emojiIcons, options.emojiSet);
|
|
1194
|
+
if (svg) {
|
|
1195
|
+
hasChanges = true;
|
|
1196
|
+
const escaped = emoji.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1197
|
+
newTextContent = newTextContent.replace(new RegExp(escaped, "g"), svg);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return `>${newTextContent}<`;
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
const iconRegex = /<(Icon|UIcon)\s+([^>]*name="([^"]+)"[^>]*)\/?>/g;
|
|
1205
|
+
if (iconRegex.test(template)) {
|
|
1206
|
+
iconRegex.lastIndex = 0;
|
|
1207
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
1208
|
+
let match;
|
|
1209
|
+
while ((match = iconRegex.exec(template)) !== null) {
|
|
1210
|
+
const iconName = match[3];
|
|
1211
|
+
if (!iconName || iconName.includes("{"))
|
|
1212
|
+
continue;
|
|
1213
|
+
const [prefix] = iconName.split(":");
|
|
1214
|
+
if (prefix)
|
|
1215
|
+
prefixes.add(prefix);
|
|
1216
|
+
}
|
|
1217
|
+
const iconSets = /* @__PURE__ */ new Map();
|
|
1218
|
+
for (const prefix of prefixes) {
|
|
1219
|
+
const icons = await loadIconSet(prefix);
|
|
1220
|
+
if (icons)
|
|
1221
|
+
iconSets.set(prefix, icons);
|
|
1222
|
+
}
|
|
1223
|
+
if (iconSets.size > 0) {
|
|
1224
|
+
iconRegex.lastIndex = 0;
|
|
1225
|
+
template = template.replace(iconRegex, (fullMatch, _tag, attrs, iconName) => {
|
|
1226
|
+
if (!iconName || iconName.includes("{"))
|
|
1227
|
+
return fullMatch;
|
|
1228
|
+
const [prefix, name] = iconName.split(":");
|
|
1229
|
+
if (!prefix || !name)
|
|
1230
|
+
return fullMatch;
|
|
1231
|
+
const icons = iconSets.get(prefix);
|
|
1232
|
+
if (!icons)
|
|
1233
|
+
return fullMatch;
|
|
1234
|
+
const iconData = icons.icons?.[name];
|
|
1235
|
+
if (!iconData)
|
|
1236
|
+
return fullMatch;
|
|
1237
|
+
hasChanges = true;
|
|
1238
|
+
return buildIconSvg(iconData, icons.width || 24, icons.height || 24, attrs);
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (options.initTw4)
|
|
1243
|
+
await options.initTw4();
|
|
1244
|
+
if (options.tw4StyleMap && Object.keys(options.tw4StyleMap).length > 0) {
|
|
1245
|
+
try {
|
|
1246
|
+
const styleMap = options.tw4StyleMap;
|
|
1247
|
+
const fullCode = code.slice(0, templateStart) + template + code.slice(templateEnd);
|
|
1248
|
+
const hasGradientClasses = options.tw4CssPath && (template.includes("bg-gradient") || template.includes("bg-radial") || template.includes("from-") || template.includes("to-") || template.includes("via-"));
|
|
1249
|
+
const result = await transformVueTemplate(fullCode, {
|
|
1250
|
+
resolveStyles: async (classes) => {
|
|
1251
|
+
const supported = classes.filter((cls) => !isSatoriUnsupported(cls));
|
|
1252
|
+
const unsupported = classes.filter((cls) => isSatoriUnsupported(cls));
|
|
1253
|
+
if (unsupported.length > 0) {
|
|
1254
|
+
const componentName = id.split("/").pop();
|
|
1255
|
+
console.warn(`[nuxt-og-image] ${componentName}: Filtered unsupported Satori classes: ${unsupported.join(", ")}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (hasGradientClasses && options.tw4CssPath) {
|
|
1258
|
+
const hasElementGradient = supported.some(
|
|
1259
|
+
(c) => c.startsWith("bg-gradient") || c.startsWith("bg-radial") || c.startsWith("from-") || c.startsWith("to-") || c.startsWith("via-")
|
|
1260
|
+
);
|
|
1261
|
+
if (hasElementGradient) {
|
|
1262
|
+
const nuxtUiColors = await options.loadNuxtUiColors?.();
|
|
1263
|
+
return resolveClassesToStyles(supported, {
|
|
1264
|
+
cssPath: options.tw4CssPath,
|
|
1265
|
+
nuxtUiColors
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const resolved = {};
|
|
1270
|
+
for (const cls of supported) {
|
|
1271
|
+
const baseClass = cls.replace(/^(sm|md|lg|xl|2xl):/, "");
|
|
1272
|
+
if (styleMap[baseClass]) {
|
|
1273
|
+
resolved[cls] = styleMap[baseClass];
|
|
1274
|
+
} else if (styleMap[cls]) {
|
|
1275
|
+
resolved[cls] = styleMap[cls];
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return resolved;
|
|
1279
|
+
}
|
|
1280
|
+
});
|
|
1281
|
+
if (result) {
|
|
1282
|
+
const newTemplateStart = result.code.indexOf("<template>") + "<template>".length;
|
|
1283
|
+
const newTemplateEnd = result.code.indexOf("</template>");
|
|
1284
|
+
template = result.code.slice(newTemplateStart, newTemplateEnd);
|
|
1285
|
+
hasChanges = true;
|
|
1286
|
+
}
|
|
1287
|
+
} catch (err) {
|
|
1288
|
+
const componentName = id.split("/").pop();
|
|
1289
|
+
console.warn(`[nuxt-og-image] ${componentName}: TW4 template transform failed, using original template`, err.message);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (!template.includes("data-no-inline")) {
|
|
1293
|
+
const imgRegex = /(?<!:)src="((?:\/|~\/|@\/)[^"]+\.(png|jpg|jpeg|gif|webp|svg))"/g;
|
|
1294
|
+
if (imgRegex.test(template)) {
|
|
1295
|
+
imgRegex.lastIndex = 0;
|
|
1296
|
+
const componentDir = dirname(id);
|
|
1297
|
+
const replacements = [];
|
|
1298
|
+
let match;
|
|
1299
|
+
while ((match = imgRegex.exec(template)) !== null) {
|
|
1300
|
+
const [fullMatch, srcPath, ext] = match;
|
|
1301
|
+
if (!srcPath || !ext)
|
|
1302
|
+
continue;
|
|
1303
|
+
if (srcPath.startsWith("data:") || srcPath.startsWith("http"))
|
|
1304
|
+
continue;
|
|
1305
|
+
let resolvedPath;
|
|
1306
|
+
if (srcPath.startsWith("/")) {
|
|
1307
|
+
resolvedPath = join(options.publicDir, srcPath);
|
|
1308
|
+
} else if (srcPath.startsWith("~/") || srcPath.startsWith("@/")) {
|
|
1309
|
+
resolvedPath = join(options.srcDir, srcPath.slice(2));
|
|
1310
|
+
} else if (!isAbsolute(srcPath)) {
|
|
1311
|
+
resolvedPath = resolve(componentDir, srcPath);
|
|
1312
|
+
} else {
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const fileBuffer = await readFile(resolvedPath).catch(() => null);
|
|
1316
|
+
if (!fileBuffer)
|
|
1317
|
+
continue;
|
|
1318
|
+
const mimeType = getMimeType(ext);
|
|
1319
|
+
let dataUri;
|
|
1320
|
+
if (ext === "svg") {
|
|
1321
|
+
const svgContent = fileBuffer.toString("utf-8");
|
|
1322
|
+
dataUri = `data:${mimeType},${encodeURIComponent(svgContent)}`;
|
|
1323
|
+
} else {
|
|
1324
|
+
const base64 = fileBuffer.toString("base64");
|
|
1325
|
+
dataUri = `data:${mimeType};base64,${base64}`;
|
|
1326
|
+
}
|
|
1327
|
+
replacements.push({ from: fullMatch, to: `src="${dataUri}"` });
|
|
1328
|
+
hasChanges = true;
|
|
1329
|
+
}
|
|
1330
|
+
for (const { from, to } of replacements) {
|
|
1331
|
+
template = template.replace(from, to);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (!hasChanges)
|
|
1336
|
+
return;
|
|
1337
|
+
s.overwrite(templateStart, templateEnd, template);
|
|
1338
|
+
return {
|
|
1339
|
+
code: s.toString(),
|
|
1340
|
+
map: s.generateMap({ hires: true })
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
async function getNuxtModuleOptions(module, nuxt = useNuxt()) {
|
|
1347
|
+
const moduleMeta = ({ name: module } ) || {};
|
|
1348
|
+
const { nuxtModule } = await loadNuxtModuleInstance(module, nuxt);
|
|
1349
|
+
let moduleEntry;
|
|
1350
|
+
for (const m of nuxt.options.modules) {
|
|
1351
|
+
if (Array.isArray(m) && m.length >= 2) {
|
|
1352
|
+
const _module = m[0];
|
|
1353
|
+
const _moduleEntryName = typeof _module === "string" ? _module : (await _module.getMeta?.())?.name || "";
|
|
1354
|
+
if (_moduleEntryName === moduleMeta.name)
|
|
1355
|
+
moduleEntry = m;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
let inlineOptions = {};
|
|
1359
|
+
if (moduleEntry)
|
|
1360
|
+
inlineOptions = moduleEntry[1];
|
|
1361
|
+
if (nuxtModule.getOptions)
|
|
1362
|
+
return nuxtModule.getOptions(inlineOptions, nuxt);
|
|
1363
|
+
return inlineOptions;
|
|
1364
|
+
}
|
|
1365
|
+
function isNuxtGenerate(nuxt = useNuxt()) {
|
|
1366
|
+
return nuxt.options.nitro.static || nuxt.options.nitro.preset === "static";
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function parseFontString(font) {
|
|
1370
|
+
const parts = font.split(":");
|
|
1371
|
+
if (parts.length === 3) {
|
|
1372
|
+
return {
|
|
1373
|
+
name: parts[0] || font,
|
|
1374
|
+
style: parts[1] === "ital" ? "italic" : "normal",
|
|
1375
|
+
weight: Number.parseInt(parts[2] || "") || 400
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
name: parts[0] || font,
|
|
1380
|
+
weight: Number.parseInt(parts[1] || "") || 400,
|
|
1381
|
+
style: "normal"
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function groupFontsByFamily(fonts) {
|
|
1385
|
+
const families = /* @__PURE__ */ new Map();
|
|
1386
|
+
for (const font of fonts) {
|
|
1387
|
+
const parsed = parseFontString(font);
|
|
1388
|
+
const existing = families.get(parsed.name);
|
|
1389
|
+
if (existing) {
|
|
1390
|
+
existing.weights.add(parsed.weight);
|
|
1391
|
+
existing.styles.add(parsed.style);
|
|
1392
|
+
} else {
|
|
1393
|
+
families.set(parsed.name, {
|
|
1394
|
+
weights: /* @__PURE__ */ new Set([parsed.weight]),
|
|
1395
|
+
styles: /* @__PURE__ */ new Set([parsed.style])
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return Array.from(families.entries()).map(([name, { weights, styles }]) => ({
|
|
1400
|
+
name,
|
|
1401
|
+
weights: Array.from(weights).sort((a, b) => a - b),
|
|
1402
|
+
styles: Array.from(styles)
|
|
1403
|
+
}));
|
|
1404
|
+
}
|
|
1405
|
+
async function migrateFontsConfig(rootDir) {
|
|
1406
|
+
const configPaths = [
|
|
1407
|
+
"nuxt.config.ts",
|
|
1408
|
+
"nuxt.config.js",
|
|
1409
|
+
"nuxt.config.mjs"
|
|
1410
|
+
];
|
|
1411
|
+
let configPath;
|
|
1412
|
+
for (const p of configPaths) {
|
|
1413
|
+
const fullPath = join(rootDir, p);
|
|
1414
|
+
if (existsSync(fullPath)) {
|
|
1415
|
+
configPath = fullPath;
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (!configPath) {
|
|
1420
|
+
return { migrated: false, message: "No nuxt.config found" };
|
|
1421
|
+
}
|
|
1422
|
+
const mod = await loadFile(configPath);
|
|
1423
|
+
const config = mod.exports.default;
|
|
1424
|
+
if (!config?.ogImage?.fonts) {
|
|
1425
|
+
return { migrated: false, message: "No ogImage.fonts config found" };
|
|
1426
|
+
}
|
|
1427
|
+
const oldFonts = config.ogImage.fonts;
|
|
1428
|
+
if (!Array.isArray(oldFonts) || oldFonts.length === 0) {
|
|
1429
|
+
return { migrated: false, message: "ogImage.fonts is empty or invalid" };
|
|
1430
|
+
}
|
|
1431
|
+
const groupedFonts = groupFontsByFamily(oldFonts);
|
|
1432
|
+
if (!config.fonts) {
|
|
1433
|
+
config.fonts = {};
|
|
1434
|
+
}
|
|
1435
|
+
if (!config.fonts.families) {
|
|
1436
|
+
config.fonts.families = [];
|
|
1437
|
+
}
|
|
1438
|
+
const existingFamilies = config.fonts.families;
|
|
1439
|
+
for (const font of groupedFonts) {
|
|
1440
|
+
const existing = existingFamilies.find((f) => f.name === font.name);
|
|
1441
|
+
if (existing) {
|
|
1442
|
+
const existingWeights = new Set(existing.weights || []);
|
|
1443
|
+
for (const w of font.weights) {
|
|
1444
|
+
existingWeights.add(w);
|
|
1445
|
+
}
|
|
1446
|
+
existing.weights = Array.from(existingWeights).sort((a, b) => a - b);
|
|
1447
|
+
if (font.styles.includes("italic")) {
|
|
1448
|
+
const existingStyles = new Set(existing.styles || ["normal"]);
|
|
1449
|
+
existingStyles.add("italic");
|
|
1450
|
+
existing.styles = Array.from(existingStyles);
|
|
1451
|
+
}
|
|
1452
|
+
existing.global = true;
|
|
1453
|
+
} else {
|
|
1454
|
+
const family = {
|
|
1455
|
+
name: font.name,
|
|
1456
|
+
weights: font.weights,
|
|
1457
|
+
global: true
|
|
1458
|
+
};
|
|
1459
|
+
if (font.styles.includes("italic")) {
|
|
1460
|
+
family.styles = font.styles;
|
|
1461
|
+
}
|
|
1462
|
+
existingFamilies.push(family);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
addNuxtModule(mod, "@nuxt/fonts");
|
|
1466
|
+
delete config.ogImage.fonts;
|
|
1467
|
+
if (Object.keys(config.ogImage).length === 0) {
|
|
1468
|
+
delete config.ogImage;
|
|
1469
|
+
}
|
|
1470
|
+
await writeFile$1(mod, configPath);
|
|
1471
|
+
const familyNames = groupedFonts.map((f) => f.name).join(", ");
|
|
1472
|
+
return {
|
|
1473
|
+
migrated: true,
|
|
1474
|
+
message: `Migrated fonts (${familyNames}) from ogImage.fonts to @nuxt/fonts`
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
async function promptFontsMigration(rootDir) {
|
|
1478
|
+
logger.info("");
|
|
1479
|
+
logger.info("Detected deprecated ogImage.fonts configuration.");
|
|
1480
|
+
logger.info("");
|
|
1481
|
+
logger.info("This config has been replaced with @nuxt/fonts integration.");
|
|
1482
|
+
logger.info("");
|
|
1483
|
+
logger.info("Before:");
|
|
1484
|
+
logger.info(" ogImage: {");
|
|
1485
|
+
logger.info(" fonts: ['Inter:400', 'Inter:700']");
|
|
1486
|
+
logger.info(" }");
|
|
1487
|
+
logger.info("");
|
|
1488
|
+
logger.info("After:");
|
|
1489
|
+
logger.info(" modules: ['@nuxt/fonts', 'nuxt-og-image'],");
|
|
1490
|
+
logger.info(" fonts: {");
|
|
1491
|
+
logger.info(" families: [");
|
|
1492
|
+
logger.info(" { name: 'Inter', weights: [400, 700], global: true }");
|
|
1493
|
+
logger.info(" ]");
|
|
1494
|
+
logger.info(" }");
|
|
1495
|
+
logger.info("");
|
|
1496
|
+
const migrate = await logger.prompt("Automatically migrate nuxt.config?", {
|
|
1497
|
+
type: "confirm",
|
|
1498
|
+
initial: true
|
|
1499
|
+
});
|
|
1500
|
+
if (migrate) {
|
|
1501
|
+
const result = await migrateFontsConfig(rootDir).catch((err) => {
|
|
1502
|
+
logger.error(`Migration failed: ${err.message}`);
|
|
1503
|
+
return { migrated: false, message: err.message };
|
|
1504
|
+
});
|
|
1505
|
+
if (result.migrated) {
|
|
1506
|
+
logger.success(result.message);
|
|
1507
|
+
logger.info("");
|
|
1508
|
+
logger.info("Please install @nuxt/fonts:");
|
|
1509
|
+
logger.info(" npm add @nuxt/fonts");
|
|
1510
|
+
} else {
|
|
1511
|
+
logger.warn(`Could not migrate: ${result.message}`);
|
|
1512
|
+
logger.info("Please migrate manually.");
|
|
1513
|
+
}
|
|
1514
|
+
} else {
|
|
1515
|
+
logger.info("Skipping automatic migration. Please migrate manually.");
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const PROVIDER_DEPENDENCIES = [
|
|
1520
|
+
{
|
|
1521
|
+
name: "satori",
|
|
1522
|
+
description: "SVG-based renderer using Satori (default)",
|
|
1523
|
+
bindings: {
|
|
1524
|
+
"node": [
|
|
1525
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
1526
|
+
{ name: "@resvg/resvg-js", description: "SVG to PNG converter (native)" }
|
|
1527
|
+
],
|
|
1528
|
+
"wasm": [
|
|
1529
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
1530
|
+
{ name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
|
|
1531
|
+
],
|
|
1532
|
+
"wasm-fs": [
|
|
1533
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
1534
|
+
{ name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
|
|
1535
|
+
]
|
|
1536
|
+
}
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
name: "takumi",
|
|
1540
|
+
description: "Rust-based high-performance renderer",
|
|
1541
|
+
bindings: {
|
|
1542
|
+
"node": [
|
|
1543
|
+
{ name: "@takumi-rs/core", description: "Native Takumi renderer" }
|
|
1544
|
+
],
|
|
1545
|
+
"wasm": [
|
|
1546
|
+
{ name: "@takumi-rs/wasm", description: "WASM Takumi renderer" }
|
|
1547
|
+
],
|
|
1548
|
+
"wasm-fs": [
|
|
1549
|
+
{ name: "@takumi-rs/wasm", description: "WASM Takumi renderer" }
|
|
1550
|
+
]
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: "chromium",
|
|
1555
|
+
description: "Browser-based screenshot renderer",
|
|
1556
|
+
bindings: {
|
|
1557
|
+
node: [
|
|
1558
|
+
{ name: "playwright-core", description: "Headless browser automation" }
|
|
1559
|
+
]
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
];
|
|
1563
|
+
async function isPackageInstalled(packageName) {
|
|
1564
|
+
try {
|
|
1565
|
+
await import(packageName);
|
|
1566
|
+
return true;
|
|
1567
|
+
} catch {
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
async function getInstalledProviders() {
|
|
1572
|
+
const installed = [];
|
|
1573
|
+
for (const provider of PROVIDER_DEPENDENCIES) {
|
|
1574
|
+
if (provider.bindings.node) {
|
|
1575
|
+
const allNodeInstalled = await Promise.all(
|
|
1576
|
+
provider.bindings.node.map((dep) => isPackageInstalled(dep.name))
|
|
1577
|
+
);
|
|
1578
|
+
if (allNodeInstalled.every(Boolean)) {
|
|
1579
|
+
installed.push({ provider: provider.name, binding: "node" });
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (provider.bindings.wasm) {
|
|
1584
|
+
const allWasmInstalled = await Promise.all(
|
|
1585
|
+
provider.bindings.wasm.map((dep) => isPackageInstalled(dep.name))
|
|
1586
|
+
);
|
|
1587
|
+
if (allWasmInstalled.every(Boolean)) {
|
|
1588
|
+
installed.push({ provider: provider.name, binding: "wasm" });
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return installed;
|
|
1593
|
+
}
|
|
1594
|
+
async function getMissingDependencies(provider, binding = "node") {
|
|
1595
|
+
const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
|
|
1596
|
+
if (!providerDef)
|
|
1597
|
+
return [];
|
|
1598
|
+
const deps = providerDef.bindings[binding] || providerDef.bindings.node || [];
|
|
1599
|
+
const missing = [];
|
|
1600
|
+
for (const dep of deps) {
|
|
1601
|
+
if (!await isPackageInstalled(dep.name))
|
|
1602
|
+
missing.push(dep.name);
|
|
1603
|
+
}
|
|
1604
|
+
return missing;
|
|
1605
|
+
}
|
|
1606
|
+
function getProviderDependencies(provider, binding = "node") {
|
|
1607
|
+
const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
|
|
1608
|
+
if (!providerDef)
|
|
1609
|
+
return [];
|
|
1610
|
+
const deps = providerDef.bindings[binding] || providerDef.bindings.node || [];
|
|
1611
|
+
return deps.map((d) => d.name);
|
|
1612
|
+
}
|
|
1613
|
+
function getRecommendedBindingFromPreset(provider) {
|
|
1614
|
+
const preset = resolveNitroPreset();
|
|
1615
|
+
const compatibility = getPresetNitroPresetCompatibility(preset);
|
|
1616
|
+
return getRecommendedBinding(provider, compatibility);
|
|
1617
|
+
}
|
|
1618
|
+
function getRecommendedBinding(provider, compatibility) {
|
|
1619
|
+
if (provider === "satori") {
|
|
1620
|
+
const resvgBinding = compatibility.resvg;
|
|
1621
|
+
if (resvgBinding === "wasm-fs")
|
|
1622
|
+
return "wasm-fs";
|
|
1623
|
+
if (resvgBinding === "wasm")
|
|
1624
|
+
return "wasm";
|
|
1625
|
+
return "node";
|
|
1626
|
+
}
|
|
1627
|
+
if (provider === "takumi") {
|
|
1628
|
+
const takumiBinding = compatibility.takumi;
|
|
1629
|
+
if (takumiBinding === "wasm")
|
|
1630
|
+
return "wasm";
|
|
1631
|
+
return "node";
|
|
1632
|
+
}
|
|
1633
|
+
if (provider === "chromium") {
|
|
1634
|
+
return "node";
|
|
1635
|
+
}
|
|
1636
|
+
return "node";
|
|
1637
|
+
}
|
|
1638
|
+
async function ensureProviderDependencies(provider, binding, nuxt) {
|
|
1639
|
+
const missing = await getMissingDependencies(provider, binding);
|
|
1640
|
+
if (missing.length === 0)
|
|
1641
|
+
return { success: true, installed: [] };
|
|
1642
|
+
const pm = await detectPackageManager(nuxt.options.rootDir);
|
|
1643
|
+
const pmName = pm?.name || "npm";
|
|
1644
|
+
logger.info(`Installing ${provider} dependencies: ${missing.join(", ")}`);
|
|
1645
|
+
const installed = [];
|
|
1646
|
+
for (const pkg of missing) {
|
|
1647
|
+
const success = await addDependency(pkg, {
|
|
1648
|
+
cwd: nuxt.options.rootDir,
|
|
1649
|
+
dev: false
|
|
1650
|
+
}).then(() => {
|
|
1651
|
+
installed.push(pkg);
|
|
1652
|
+
return true;
|
|
1653
|
+
}).catch(() => {
|
|
1654
|
+
logger.error(`Failed to install ${pkg}. Run manually: ${pmName} add ${pkg}`);
|
|
1655
|
+
return false;
|
|
1656
|
+
});
|
|
1657
|
+
if (!success)
|
|
1658
|
+
return { success: false, installed };
|
|
1659
|
+
}
|
|
1660
|
+
return { success: true, installed };
|
|
1661
|
+
}
|
|
1662
|
+
async function validateProviderSetup(renderer, compatibility) {
|
|
1663
|
+
const issues = [];
|
|
1664
|
+
const binding = getRecommendedBinding(renderer, compatibility);
|
|
1665
|
+
const missing = await getMissingDependencies(renderer, binding);
|
|
1666
|
+
if (missing.length > 0) {
|
|
1667
|
+
issues.push(`Missing ${renderer} dependencies: ${missing.join(", ")}`);
|
|
1668
|
+
}
|
|
1669
|
+
if (renderer === "satori") {
|
|
1670
|
+
const hasResvgNode = await isPackageInstalled("@resvg/resvg-js");
|
|
1671
|
+
const hasResvgWasm = await isPackageInstalled("@resvg/resvg-wasm");
|
|
1672
|
+
if (!hasResvgNode && !hasResvgWasm) {
|
|
1673
|
+
issues.push("Satori requires either @resvg/resvg-js (node) or @resvg/resvg-wasm to render PNGs");
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (renderer === "chromium") {
|
|
1677
|
+
const hasPlaywright = await isPackageInstalled("playwright-core");
|
|
1678
|
+
if (!hasPlaywright && binding === "node") {
|
|
1679
|
+
issues.push("Chromium renderer requires playwright-core");
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return { valid: issues.length === 0, issues };
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const DEPRECATED_CONFIG = {
|
|
1686
|
+
playground: "Removed - use Nuxt DevTools",
|
|
1687
|
+
host: "Use site.url or NUXT_SITE_URL",
|
|
1688
|
+
siteUrl: "Use site.url or NUXT_SITE_URL",
|
|
1689
|
+
runtimeBrowser: "Use compatibility.runtime.chromium",
|
|
1690
|
+
runtimeSatori: "Use compatibility.runtime.satori",
|
|
1691
|
+
cacheTtl: "Use cacheMaxAgeSeconds",
|
|
1692
|
+
cache: "Use cacheMaxAgeSeconds",
|
|
1693
|
+
cacheKey: "Removed",
|
|
1694
|
+
static: "Removed - use zeroRuntime",
|
|
1695
|
+
fonts: "Use @nuxt/fonts module instead"
|
|
1696
|
+
};
|
|
1697
|
+
const DEPRECATED_COMPOSABLES = [
|
|
1698
|
+
"defineOgImageStatic",
|
|
1699
|
+
"defineOgImageDynamic",
|
|
1700
|
+
"defineOgImageCached",
|
|
1701
|
+
"defineOgImageWithoutCache",
|
|
1702
|
+
"OgImageStatic",
|
|
1703
|
+
"OgImageDynamic",
|
|
1704
|
+
"OgImageCached",
|
|
1705
|
+
"OgImageWithoutCache"
|
|
1706
|
+
];
|
|
1707
|
+
async function onInstall(nuxt) {
|
|
1708
|
+
if (process.env.NUXT_OG_IMAGE_SKIP_ONBOARDING === "1") {
|
|
1709
|
+
logger.info("Skipping onboarding (NUXT_OG_IMAGE_SKIP_ONBOARDING=1)");
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
if (nuxt.options._prepare)
|
|
1713
|
+
return;
|
|
1714
|
+
if (isCI) {
|
|
1715
|
+
const installedProviders2 = await getInstalledProviders();
|
|
1716
|
+
if (installedProviders2.length === 0) {
|
|
1717
|
+
throw new Error(
|
|
1718
|
+
"[nuxt-og-image] No OG image provider dependencies found. Install a provider before running in CI:\n npm add @resvg/resvg-js satori yoga-wasm-web # for satori\n npm add @playwright/core # for chromium\nSee: https://nuxtseo.com/og-image/getting-started"
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
logger.info("");
|
|
1724
|
+
logger.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1725
|
+
logger.info("\u2502 nuxt-og-image \u2502");
|
|
1726
|
+
logger.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1727
|
+
logger.info("");
|
|
1728
|
+
const state = {};
|
|
1729
|
+
const installedProviders = await getInstalledProviders();
|
|
1730
|
+
if (installedProviders.length > 0) {
|
|
1731
|
+
logger.info("Detected installed providers:");
|
|
1732
|
+
for (const { provider, binding } of installedProviders) {
|
|
1733
|
+
logger.info(` - ${provider} (${binding})`);
|
|
1734
|
+
}
|
|
1735
|
+
const useExisting = await logger.prompt("Use detected providers?", {
|
|
1736
|
+
type: "confirm",
|
|
1737
|
+
initial: true
|
|
1738
|
+
});
|
|
1739
|
+
if (useExisting && installedProviders[0]) {
|
|
1740
|
+
state.selectedRenderer = installedProviders[0].provider;
|
|
1741
|
+
state.selectedBinding = installedProviders[0].binding;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
if (!state.selectedRenderer) {
|
|
1745
|
+
const rendererChoice = await logger.prompt("Select a renderer:", {
|
|
1746
|
+
type: "select",
|
|
1747
|
+
options: PROVIDER_DEPENDENCIES.map((p) => ({
|
|
1748
|
+
label: p.name,
|
|
1749
|
+
value: p.name,
|
|
1750
|
+
hint: p.description
|
|
1751
|
+
}))
|
|
1752
|
+
});
|
|
1753
|
+
if (typeof rendererChoice === "symbol") {
|
|
1754
|
+
logger.warn("Onboarding cancelled");
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
state.selectedRenderer = rendererChoice;
|
|
1758
|
+
}
|
|
1759
|
+
if (!state.selectedBinding) {
|
|
1760
|
+
const recommendedBinding = getRecommendedBindingFromPreset(state.selectedRenderer);
|
|
1761
|
+
const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === state.selectedRenderer);
|
|
1762
|
+
const hasNodeBinding = !!providerDef?.bindings.node;
|
|
1763
|
+
const hasWasmBinding = !!providerDef?.bindings.wasm || !!providerDef?.bindings["wasm-fs"];
|
|
1764
|
+
if (hasNodeBinding && hasWasmBinding) {
|
|
1765
|
+
const options = [];
|
|
1766
|
+
if (recommendedBinding === "node" || recommendedBinding === "wasm-fs") {
|
|
1767
|
+
options.push({ label: "node (Recommended)", value: "node", hint: "Native bindings (faster)" });
|
|
1768
|
+
options.push({ label: "wasm", value: recommendedBinding === "wasm-fs" ? "wasm-fs" : "wasm", hint: "WASM bindings (portable)" });
|
|
1769
|
+
} else {
|
|
1770
|
+
options.push({ label: "wasm (Recommended)", value: "wasm", hint: "WASM bindings (required for your platform)" });
|
|
1771
|
+
options.push({ label: "node", value: "node", hint: "Native bindings (may not work on edge)" });
|
|
1772
|
+
}
|
|
1773
|
+
const bindingChoice = await logger.prompt("Select binding type:", {
|
|
1774
|
+
type: "select",
|
|
1775
|
+
options
|
|
1776
|
+
});
|
|
1777
|
+
if (typeof bindingChoice === "symbol") {
|
|
1778
|
+
logger.warn("Onboarding cancelled");
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
state.selectedBinding = bindingChoice;
|
|
1782
|
+
} else {
|
|
1783
|
+
state.selectedBinding = hasNodeBinding ? "node" : "wasm";
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
const missing = await getMissingDependencies(state.selectedRenderer, state.selectedBinding);
|
|
1787
|
+
if (missing.length > 0) {
|
|
1788
|
+
logger.info(`Required dependencies for ${state.selectedRenderer} (${state.selectedBinding}):`);
|
|
1789
|
+
for (const pkg of missing) {
|
|
1790
|
+
logger.info(` - ${pkg}`);
|
|
1791
|
+
}
|
|
1792
|
+
const installDeps = await logger.prompt("Install missing dependencies?", {
|
|
1793
|
+
type: "confirm",
|
|
1794
|
+
initial: true
|
|
1795
|
+
});
|
|
1796
|
+
if (installDeps) {
|
|
1797
|
+
const result = await ensureProviderDependencies(
|
|
1798
|
+
state.selectedRenderer,
|
|
1799
|
+
state.selectedBinding,
|
|
1800
|
+
nuxt
|
|
1801
|
+
);
|
|
1802
|
+
if (result.success) {
|
|
1803
|
+
logger.success(`Installed: ${result.installed.join(", ")}`);
|
|
1804
|
+
} else {
|
|
1805
|
+
logger.error("Failed to install some dependencies. Install manually:");
|
|
1806
|
+
const allDeps = getProviderDependencies(state.selectedRenderer, state.selectedBinding);
|
|
1807
|
+
logger.info(` npm add ${allDeps.join(" ")}`);
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
const allDeps = getProviderDependencies(state.selectedRenderer, state.selectedBinding);
|
|
1811
|
+
logger.warn("Dependencies required. Install manually:");
|
|
1812
|
+
logger.info(` npm add ${allDeps.join(" ")}`);
|
|
1813
|
+
}
|
|
1814
|
+
} else {
|
|
1815
|
+
logger.success(`All ${state.selectedRenderer} dependencies installed`);
|
|
1816
|
+
}
|
|
1817
|
+
const createComponent = await logger.prompt("Create starter OgImage component?", {
|
|
1818
|
+
type: "confirm",
|
|
1819
|
+
initial: true
|
|
1820
|
+
});
|
|
1821
|
+
if (createComponent) {
|
|
1822
|
+
await createStarterComponent(nuxt);
|
|
1823
|
+
}
|
|
1824
|
+
const hasSharp = await isPackageInstalled("sharp");
|
|
1825
|
+
if (!hasSharp && state.selectedRenderer === "satori") {
|
|
1826
|
+
const wantJpeg = await logger.prompt("Install sharp for JPEG output support?", {
|
|
1827
|
+
type: "confirm",
|
|
1828
|
+
initial: false
|
|
1829
|
+
});
|
|
1830
|
+
if (wantJpeg) {
|
|
1831
|
+
await ensureProviderDependencies("satori", "node", nuxt).catch(() => logger.warn("Failed to install sharp, JPEG output unavailable"));
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
logger.info("");
|
|
1835
|
+
logger.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1836
|
+
logger.info("\u2502 Setup Complete \u2502");
|
|
1837
|
+
logger.info("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1838
|
+
logger.info(`${`\u2502 Renderer: ${state.selectedRenderer} (${state.selectedBinding})`.padEnd(42)}\u2502`);
|
|
1839
|
+
logger.info("\u2502 \u2502");
|
|
1840
|
+
logger.info("\u2502 Next steps: \u2502");
|
|
1841
|
+
logger.info("\u2502 1. Add site.url or NUXT_SITE_URL \u2502");
|
|
1842
|
+
logger.info("\u2502 2. Open DevTools \u2192 OG Image \u2502");
|
|
1843
|
+
logger.info("\u2502 \u2502");
|
|
1844
|
+
logger.info("\u2502 https://nuxtseo.com/og-image \u2502");
|
|
1845
|
+
logger.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1846
|
+
}
|
|
1847
|
+
async function onUpgrade(nuxt, _options, previousVersion) {
|
|
1848
|
+
logger.info("");
|
|
1849
|
+
logger.info("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1850
|
+
logger.info("\u2502 nuxt-og-image upgraded \u2502");
|
|
1851
|
+
logger.info(`${`\u2502 ${previousVersion} \u2192 current`.padEnd(42)}\u2502`);
|
|
1852
|
+
logger.info("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1853
|
+
const issues = [];
|
|
1854
|
+
const ogImageConfig = nuxt.options.ogImage;
|
|
1855
|
+
if (ogImageConfig) {
|
|
1856
|
+
for (const [key, replacement] of Object.entries(DEPRECATED_CONFIG)) {
|
|
1857
|
+
if (key in ogImageConfig) {
|
|
1858
|
+
issues.push(`Config \`${key}\` is deprecated: ${replacement}`);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if ("fonts" in ogImageConfig && !nuxt.options._prepare && !isCI) {
|
|
1862
|
+
await promptFontsMigration(nuxt.options.rootDir);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (issues.length > 0) {
|
|
1866
|
+
logger.warn("Deprecated configuration detected:");
|
|
1867
|
+
for (const issue of issues) {
|
|
1868
|
+
logger.warn(` - ${issue}`);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
logger.info("Check your code for deprecated composables:");
|
|
1872
|
+
for (const composable of DEPRECATED_COMPOSABLES) {
|
|
1873
|
+
logger.info(` - ${composable}`);
|
|
1874
|
+
}
|
|
1875
|
+
const renderer = ogImageConfig?.defaults?.renderer || "satori";
|
|
1876
|
+
const preset = resolveNitroPreset();
|
|
1877
|
+
const compatibility = getPresetNitroPresetCompatibility(preset);
|
|
1878
|
+
const validation = await validateProviderSetup(renderer, compatibility);
|
|
1879
|
+
if (!validation.valid) {
|
|
1880
|
+
logger.warn("Provider setup issues:");
|
|
1881
|
+
for (const issue of validation.issues) {
|
|
1882
|
+
logger.warn(` - ${issue}`);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
logger.info("Migration guide: https://nuxtseo.com/og-image/migration-guide");
|
|
1886
|
+
}
|
|
1887
|
+
async function createStarterComponent(nuxt) {
|
|
1888
|
+
const componentDir = join(nuxt.options.srcDir, "components", "OgImage");
|
|
1889
|
+
const componentPath = join(componentDir, "Default.vue");
|
|
1890
|
+
if (existsSync(componentPath)) {
|
|
1891
|
+
logger.info("OgImage/Default.vue already exists, skipping");
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
const template = `<script setup lang="ts">
|
|
1895
|
+
defineProps<{
|
|
1896
|
+
title: string
|
|
1897
|
+
description?: string
|
|
1898
|
+
}>()
|
|
1899
|
+
<\/script>
|
|
1900
|
+
|
|
1901
|
+
<template>
|
|
1902
|
+
<div class="w-full h-full flex flex-col justify-center items-center bg-gradient-to-br from-green-400 to-blue-500 text-white p-16">
|
|
1903
|
+
<h1 class="text-6xl font-bold mb-4">{{ title }}</h1>
|
|
1904
|
+
<p v-if="description" class="text-2xl opacity-80">{{ description }}</p>
|
|
1905
|
+
</div>
|
|
1906
|
+
</template>
|
|
1907
|
+
`;
|
|
1908
|
+
if (!existsSync(componentDir))
|
|
1909
|
+
await mkdir(componentDir, { recursive: true }).catch(() => {
|
|
1910
|
+
});
|
|
1911
|
+
await writeFile(componentPath, template).then(() => logger.success("Created components/OgImage/Default.vue")).catch(() => logger.error("Failed to create starter component"));
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function registerTypeTemplates(ctx) {
|
|
1915
|
+
const { nuxt, config, componentCtx } = ctx;
|
|
1916
|
+
addTypeTemplate({
|
|
1917
|
+
filename: "module/nuxt-og-image-components.d.ts",
|
|
1918
|
+
getContents: () => {
|
|
1919
|
+
const componentImports = componentCtx.components.map((component) => {
|
|
1920
|
+
const relativeComponentPath = relative$1(
|
|
1921
|
+
resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"),
|
|
1922
|
+
component.path
|
|
1923
|
+
);
|
|
1924
|
+
const name = config.componentDirs.sort((a, b) => b.length - a.length).reduce((n, dir) => n.replace(new RegExp(`^${dir}`), ""), component.pascalName);
|
|
1925
|
+
return ` '${name}': typeof import('${relativeComponentPath}')['default']`;
|
|
1926
|
+
}).join("\n");
|
|
1927
|
+
return `declare module '#og-image/components' {
|
|
1928
|
+
export interface OgImageComponents {
|
|
1929
|
+
${componentImports}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
`;
|
|
1933
|
+
}
|
|
1934
|
+
}, { nuxt: true });
|
|
1935
|
+
addTemplate({
|
|
1936
|
+
filename: "types/og-image-virtual.d.ts",
|
|
1937
|
+
getContents: (data) => {
|
|
1938
|
+
const typesPath = relative$1(resolve(data.nuxt.options.rootDir, data.nuxt.options.buildDir, "types"), resolve("runtime/types"));
|
|
1939
|
+
return `declare module '#og-image-virtual/public-assets.mjs' {
|
|
1940
|
+
import type { H3Event } from 'h3'
|
|
1941
|
+
import type { FontConfig } from '${typesPath}'
|
|
1942
|
+
export function resolve(event: H3Event, font: FontConfig): Promise<BufferSource>
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
declare module '#og-image/fonts' {
|
|
1946
|
+
import type { FontConfig } from '${typesPath}'
|
|
1947
|
+
const fonts: FontConfig[]
|
|
1948
|
+
export default fonts
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
declare module '#og-image-virtual/unocss-config.mjs' {
|
|
1952
|
+
export const theme: Record<string, any>
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
declare module '#og-image-virtual/iconify-json-icons.mjs' {
|
|
1956
|
+
export const icons: Record<string, { body: string, width?: number, height?: number }>
|
|
1957
|
+
export const width: number
|
|
1958
|
+
export const height: number
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
declare module '#og-image-virtual/component-names.mjs' {
|
|
1962
|
+
import type { OgImageComponent } from '${typesPath}'
|
|
1963
|
+
export const componentNames: OgImageComponent[]
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
declare module '#og-image-virtual/build-dir.mjs' {
|
|
1967
|
+
export const buildDir: string
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
declare module '#og-image/compatibility' {
|
|
1971
|
+
import type { RuntimeCompatibilitySchema } from '${typesPath}'
|
|
1972
|
+
const compatibility: Partial<Omit<RuntimeCompatibilitySchema, 'wasm'>>
|
|
1973
|
+
export default compatibility
|
|
1974
|
+
}
|
|
1975
|
+
declare module '#og-image-virtual/tw4-theme.mjs' {
|
|
1976
|
+
export const tw4FontVars: Record<string, string>
|
|
1977
|
+
export const tw4Breakpoints: Record<string, number>
|
|
1978
|
+
export const tw4Colors: Record<string, string | Record<string, string>>
|
|
1979
|
+
}
|
|
1980
|
+
`;
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
addTemplate({
|
|
1984
|
+
filename: "types/og-image-bindings.d.ts",
|
|
1985
|
+
getContents: (data) => {
|
|
1986
|
+
const typesPath = relative$1(resolve(data.nuxt.options.rootDir, data.nuxt.options.buildDir, "types"), resolve("runtime/types"));
|
|
1987
|
+
return `declare module '#og-image/bindings/chromium' {
|
|
1988
|
+
export function createBrowser(): Promise<any>
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
declare module '#og-image/bindings/satori' {
|
|
1992
|
+
import satori from 'satori'
|
|
1993
|
+
const _default: { initWasmPromise: Promise<void>, satori: typeof satori }
|
|
1994
|
+
export default _default
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
declare module '#og-image/bindings/resvg' {
|
|
1998
|
+
import { Resvg } from '@resvg/resvg-js'
|
|
1999
|
+
const _default: { initWasmPromise: Promise<void>, Resvg: typeof Resvg }
|
|
2000
|
+
export default _default
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
declare module '#og-image/bindings/sharp' {
|
|
2004
|
+
import sharp from 'sharp'
|
|
2005
|
+
const _default: typeof sharp
|
|
2006
|
+
export default _default
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
declare module '#og-image/bindings/css-inline' {
|
|
2010
|
+
import cssInline from '@css-inline/css-inline'
|
|
2011
|
+
const _default: { initWasmPromise: Promise<void>, cssInline: typeof cssInline }
|
|
2012
|
+
export default _default
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
declare module '#og-image/bindings/takumi' {
|
|
2016
|
+
const _default: any
|
|
2017
|
+
export default _default
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
declare module '#og-image/renderers/satori' {
|
|
2021
|
+
import type { Renderer } from '${typesPath}'
|
|
2022
|
+
const _default: Renderer
|
|
2023
|
+
export default _default
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
declare module '#og-image/renderers/chromium' {
|
|
2027
|
+
import type { Renderer } from '${typesPath}'
|
|
2028
|
+
const _default: Renderer
|
|
2029
|
+
export default _default
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
declare module '#og-image/renderers/takumi' {
|
|
2033
|
+
import type { Renderer } from '${typesPath}'
|
|
2034
|
+
const _default: Renderer
|
|
2035
|
+
export default _default
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
declare module '#og-image/emoji-transform' {
|
|
2039
|
+
import type { OgImageRenderEventContext } from '${typesPath}'
|
|
2040
|
+
export function getEmojiSvg(ctx: OgImageRenderEventContext, emoji: string): Promise<string | null>
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
declare module '#og-image-cache' {
|
|
2044
|
+
export { htmlPayloadCache, prerenderOptionsCache, emojiCache, fontCache } from '${typesPath.replace("/types", "")}/server/og-image/cache/lru'
|
|
2045
|
+
}
|
|
2046
|
+
`;
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
addTypeTemplate({
|
|
2050
|
+
filename: "types/og-image-augments.d.ts",
|
|
2051
|
+
getContents: (data) => {
|
|
2052
|
+
const typesPath = relative$1(resolve(data.nuxt.options.rootDir, data.nuxt.options.buildDir, "types"), resolve("runtime/types"));
|
|
2053
|
+
return `/// <reference path="./og-image-virtual.d.ts" />
|
|
2054
|
+
/// <reference path="./og-image-bindings.d.ts" />
|
|
2055
|
+
import type { OgImageOptions, OgImageRenderEventContext, VNode } from '${typesPath}'
|
|
2056
|
+
|
|
2057
|
+
declare module 'nitropack' {
|
|
2058
|
+
interface NitroRouteRules {
|
|
2059
|
+
ogImage?: false | OgImageOptions & Record<string, any>
|
|
2060
|
+
}
|
|
2061
|
+
interface NitroRouteConfig {
|
|
2062
|
+
ogImage?: false | OgImageOptions & Record<string, any>
|
|
2063
|
+
}
|
|
2064
|
+
interface NitroRuntimeHooks {
|
|
2065
|
+
'nuxt-og-image:context': (ctx: OgImageRenderEventContext) => void | Promise<void>
|
|
2066
|
+
'nuxt-og-image:satori:vnodes': (vnodes: VNode, ctx: OgImageRenderEventContext) => void | Promise<void>
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
declare module 'nitropack/types' {
|
|
2071
|
+
interface NitroRouteRules {
|
|
2072
|
+
ogImage?: false | OgImageOptions & Record<string, any>
|
|
2073
|
+
}
|
|
2074
|
+
interface NitroRouteConfig {
|
|
2075
|
+
ogImage?: false | OgImageOptions & Record<string, any>
|
|
2076
|
+
}
|
|
2077
|
+
interface NitroRuntimeHooks {
|
|
2078
|
+
'nuxt-og-image:context': (ctx: OgImageRenderEventContext) => void | Promise<void>
|
|
2079
|
+
'nuxt-og-image:satori:vnodes': (vnodes: VNode, ctx: OgImageRenderEventContext) => void | Promise<void>
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
export {}
|
|
2084
|
+
`;
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
const IS_MODULE_DEVELOPMENT = import.meta.filename.endsWith(".ts");
|
|
2090
|
+
function isProviderEnabledForEnv(provider, nuxt, config) {
|
|
2091
|
+
return nuxt.options.dev && config.compatibility?.dev?.[provider] !== false || !nuxt.options.dev && (config.compatibility?.runtime?.[provider] !== false || config.compatibility?.prerender?.[provider] !== false);
|
|
2092
|
+
}
|
|
2093
|
+
const defaultComponentDirs = ["OgImage", "OgImageCommunity", "og-image", "OgImageTemplate"];
|
|
2094
|
+
const module$1 = defineNuxtModule({
|
|
2095
|
+
meta: {
|
|
2096
|
+
name: "nuxt-og-image",
|
|
2097
|
+
compatibility: {
|
|
2098
|
+
nuxt: ">=3.16.0"
|
|
2099
|
+
},
|
|
2100
|
+
configKey: "ogImage"
|
|
2101
|
+
},
|
|
2102
|
+
moduleDependencies: {
|
|
2103
|
+
"@nuxt/content": {
|
|
2104
|
+
version: ">=3",
|
|
2105
|
+
optional: true
|
|
2106
|
+
},
|
|
2107
|
+
"@nuxt/fonts": {
|
|
2108
|
+
version: ">=0.13.0"
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
defaults() {
|
|
2112
|
+
return {
|
|
2113
|
+
enabled: true,
|
|
2114
|
+
defaults: {
|
|
2115
|
+
emojis: "noto",
|
|
2116
|
+
component: "NuxtSeo",
|
|
2117
|
+
extension: "png",
|
|
2118
|
+
width: 1200,
|
|
2119
|
+
height: 600,
|
|
2120
|
+
// default is to cache the image for 3 day (72 hours)
|
|
2121
|
+
cacheMaxAgeSeconds: 60 * 60 * 24 * 3
|
|
2122
|
+
},
|
|
2123
|
+
componentDirs: defaultComponentDirs,
|
|
2124
|
+
runtimeCacheStorage: true,
|
|
2125
|
+
debug: isDevelopment
|
|
2126
|
+
};
|
|
2127
|
+
},
|
|
2128
|
+
async onInstall(nuxt) {
|
|
2129
|
+
await onInstall(nuxt);
|
|
2130
|
+
},
|
|
2131
|
+
async onUpgrade(nuxt, options, previousVersion) {
|
|
2132
|
+
await onUpgrade(nuxt, options, previousVersion);
|
|
2133
|
+
},
|
|
2134
|
+
async setup(config, nuxt) {
|
|
2135
|
+
const _resolver = createResolver(import.meta.url);
|
|
2136
|
+
const fixSharedPath = (p) => p.includes("/shared/runtime/") ? p.replace("/shared/runtime/", "/runtime/") : p;
|
|
2137
|
+
const resolve = (path) => fixSharedPath(_resolver.resolve(path));
|
|
2138
|
+
const resolver = {
|
|
2139
|
+
..._resolver,
|
|
2140
|
+
resolve,
|
|
2141
|
+
resolvePath: async (path, opts) => fixSharedPath(await _resolver.resolvePath(path, opts))
|
|
2142
|
+
};
|
|
2143
|
+
const { version } = await readPackageJSON(resolve("../package.json"));
|
|
2144
|
+
const userAppPkgJson = await readPackageJSON(nuxt.options.rootDir).catch(() => ({ dependencies: {}, devDependencies: {} }));
|
|
2145
|
+
logger.level = config.debug || nuxt.options.debug ? 4 : 3;
|
|
2146
|
+
if (config.enabled === false) {
|
|
2147
|
+
logger.info("The module is disabled, skipping setup.");
|
|
2148
|
+
["defineOgImage", "defineOgImageComponent", "defineOgImageScreenshot"].forEach((name) => {
|
|
2149
|
+
addImports({ name, from: resolve(`./runtime/app/composables/mock`) });
|
|
2150
|
+
});
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (config.enabled && !nuxt.options.ssr) {
|
|
2154
|
+
logger.warn("Nuxt OG Image is enabled but SSR is disabled.\n\nYou should enable SSR (`ssr: true`) or disable the module (`ogImage: { enabled: false }`).");
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
const routeRules = nuxt.options.routeRules || {};
|
|
2158
|
+
const wildcardPatterns = ["/**", "/*", "*"];
|
|
2159
|
+
for (const pattern of wildcardPatterns) {
|
|
2160
|
+
const rule = routeRules[pattern];
|
|
2161
|
+
if (rule && (rule.swr || rule.isr || rule.cache)) {
|
|
2162
|
+
logger.warn(`Wildcard route rule \`${pattern}\` with caching (swr/isr/cache) may break og-image routes.`);
|
|
2163
|
+
logger.info("See: https://nuxtseo.com/og-image/guides/route-rules#wildcard-route-rules-warning");
|
|
2164
|
+
break;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
nuxt.options.alias["#og-image"] = resolve("./runtime");
|
|
2168
|
+
nuxt.options.alias["#og-image-cache"] = resolve("./runtime/server/og-image/cache/lru");
|
|
2169
|
+
const preset = resolveNitroPreset(nuxt.options.nitro);
|
|
2170
|
+
const targetCompatibility = getPresetNitroPresetCompatibility(preset);
|
|
2171
|
+
const emojiPkg = `@iconify-json/${config.defaults.emojis}`;
|
|
2172
|
+
let hasLocalIconify = await hasResolvableDependency(emojiPkg);
|
|
2173
|
+
let finalEmojiStrategy = config.emojiStrategy || "auto";
|
|
2174
|
+
if (!hasLocalIconify && !nuxt.options._prepare && nuxt.options.dev) {
|
|
2175
|
+
const shouldInstall = await logger.prompt(`Install ${emojiPkg} for local emoji support?`, {
|
|
2176
|
+
type: "confirm",
|
|
2177
|
+
initial: true
|
|
2178
|
+
});
|
|
2179
|
+
if (shouldInstall) {
|
|
2180
|
+
await ensureDependencies([emojiPkg], nuxt).then(() => {
|
|
2181
|
+
hasLocalIconify = true;
|
|
2182
|
+
logger.success(`Installed ${emojiPkg}`);
|
|
2183
|
+
}).catch(() => logger.warn(`Failed to install ${emojiPkg}, using API fallback`));
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
if (finalEmojiStrategy === "auto") {
|
|
2187
|
+
finalEmojiStrategy = hasLocalIconify ? "local" : "fetch";
|
|
2188
|
+
}
|
|
2189
|
+
if (finalEmojiStrategy === "local" && !hasLocalIconify) {
|
|
2190
|
+
logger.warn(`emojiStrategy is set to 'local' but ${emojiPkg} is not installed. Falling back to 'fetch'.`);
|
|
2191
|
+
finalEmojiStrategy = "fetch";
|
|
2192
|
+
}
|
|
2193
|
+
const runtimeEmojiStrategy = targetCompatibility.emoji === "fetch" ? "fetch" : finalEmojiStrategy;
|
|
2194
|
+
if (runtimeEmojiStrategy === "local") {
|
|
2195
|
+
if (nuxt.options.dev)
|
|
2196
|
+
logger.debug(`Using local dependency \`${emojiPkg}\` for emoji rendering.`);
|
|
2197
|
+
else if (nuxt.options.build && !nuxt.options._prepare)
|
|
2198
|
+
logger.info(`Using local dependency \`${emojiPkg}\` for emoji rendering.`);
|
|
2199
|
+
nuxt.options.alias["#og-image/emoji-transform"] = resolve("./runtime/server/og-image/satori/transforms/emojis/local");
|
|
2200
|
+
nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {};
|
|
2201
|
+
nuxt.options.nitro.virtual["#og-image-virtual/iconify-json-icons.mjs"] = () => {
|
|
2202
|
+
return `export { icons, width, height } from '${emojiPkg}/icons.json'`;
|
|
2203
|
+
};
|
|
2204
|
+
} else {
|
|
2205
|
+
if (targetCompatibility.emoji === "fetch" && finalEmojiStrategy === "local") {
|
|
2206
|
+
logger.info(`Using iconify API for runtime emojis on ${preset} (local icons used at build time).`);
|
|
2207
|
+
} else {
|
|
2208
|
+
logger.info(`Using iconify API for emojis${hasLocalIconify ? " (emojiStrategy: fetch)" : `, install ${emojiPkg} for local support`}.`);
|
|
2209
|
+
}
|
|
2210
|
+
nuxt.options.alias["#og-image/emoji-transform"] = resolve("./runtime/server/og-image/satori/transforms/emojis/fetch");
|
|
2211
|
+
}
|
|
2212
|
+
const tw4State = {
|
|
2213
|
+
styleMap: {},
|
|
2214
|
+
cssPath: void 0,
|
|
2215
|
+
fontVars: {},
|
|
2216
|
+
breakpoints: {},
|
|
2217
|
+
colors: {},
|
|
2218
|
+
nuxtUiColors: void 0,
|
|
2219
|
+
initialized: false
|
|
2220
|
+
};
|
|
2221
|
+
let tw4InitPromise;
|
|
2222
|
+
const nuxtUiDefaults = {
|
|
2223
|
+
primary: "green",
|
|
2224
|
+
secondary: "blue",
|
|
2225
|
+
success: "green",
|
|
2226
|
+
info: "blue",
|
|
2227
|
+
warning: "yellow",
|
|
2228
|
+
error: "red",
|
|
2229
|
+
neutral: "slate"
|
|
2230
|
+
};
|
|
2231
|
+
async function loadNuxtUiColors() {
|
|
2232
|
+
if (tw4State.nuxtUiColors)
|
|
2233
|
+
return tw4State.nuxtUiColors;
|
|
2234
|
+
if (!hasNuxtModule("@nuxt/ui"))
|
|
2235
|
+
return void 0;
|
|
2236
|
+
const appConfigPath = join(nuxt.options.buildDir, "app.config.mjs");
|
|
2237
|
+
if (!existsSync(appConfigPath))
|
|
2238
|
+
return { ...nuxtUiDefaults };
|
|
2239
|
+
const rawContent = await readFile(appConfigPath, "utf-8");
|
|
2240
|
+
const strippedContent = rawContent.replace(/\/\*\* client \*\*\/[\s\S]*?\/\*\* client-end \*\*\//g, "");
|
|
2241
|
+
const jiti = createJiti(nuxt.options.buildDir, {
|
|
2242
|
+
interopDefault: true,
|
|
2243
|
+
moduleCache: false
|
|
2244
|
+
});
|
|
2245
|
+
const mergedAppConfig = await jiti.evalModule(strippedContent, { filename: appConfigPath });
|
|
2246
|
+
tw4State.nuxtUiColors = { ...nuxtUiDefaults, ...mergedAppConfig?.ui?.colors };
|
|
2247
|
+
logger.debug(`Nuxt UI colors: ${JSON.stringify(tw4State.nuxtUiColors)}`);
|
|
2248
|
+
return tw4State.nuxtUiColors;
|
|
2249
|
+
}
|
|
2250
|
+
async function detectTailwindCssPath() {
|
|
2251
|
+
for (const cssEntry of nuxt.options.css) {
|
|
2252
|
+
const cssPath = typeof cssEntry === "string" ? cssEntry : cssEntry?.src;
|
|
2253
|
+
if (!cssPath || !cssPath.endsWith(".css"))
|
|
2254
|
+
continue;
|
|
2255
|
+
const resolved = await resolver.resolvePath(cssPath).catch(() => null);
|
|
2256
|
+
if (!resolved || !existsSync(resolved))
|
|
2257
|
+
continue;
|
|
2258
|
+
const content = await readFile(resolved, "utf-8");
|
|
2259
|
+
if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'"))
|
|
2260
|
+
return resolved;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
async function initTw4() {
|
|
2264
|
+
if (tw4State.initialized)
|
|
2265
|
+
return;
|
|
2266
|
+
if (tw4InitPromise)
|
|
2267
|
+
return tw4InitPromise;
|
|
2268
|
+
tw4InitPromise = (async () => {
|
|
2269
|
+
const resolvedCssPath = config.tailwindCss ? await resolver.resolvePath(config.tailwindCss) : nuxt.options.alias["#tailwindcss"] ?? await detectTailwindCssPath();
|
|
2270
|
+
tw4State.cssPath = resolvedCssPath;
|
|
2271
|
+
if (!resolvedCssPath || !existsSync(resolvedCssPath)) {
|
|
2272
|
+
tw4State.initialized = true;
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
const tw4CssContent = await readFile(resolvedCssPath, "utf-8");
|
|
2276
|
+
if (!tw4CssContent.includes("@theme") && !tw4CssContent.includes('@import "tailwindcss"')) {
|
|
2277
|
+
tw4State.initialized = true;
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
const nuxtUiColors = await loadNuxtUiColors();
|
|
2281
|
+
const metadata = await extractTw4Metadata({
|
|
2282
|
+
cssPath: resolvedCssPath,
|
|
2283
|
+
nuxtUiColors
|
|
2284
|
+
}).catch((e) => {
|
|
2285
|
+
logger.warn(`TW4 metadata extraction failed: ${e.message}`);
|
|
2286
|
+
return { fontVars: {}, breakpoints: {}, colors: {} };
|
|
2287
|
+
});
|
|
2288
|
+
tw4State.fontVars = metadata.fontVars;
|
|
2289
|
+
tw4State.breakpoints = metadata.breakpoints;
|
|
2290
|
+
tw4State.colors = metadata.colors;
|
|
2291
|
+
try {
|
|
2292
|
+
const { scanComponentClasses, filterProcessableClasses } = await import('../chunks/tw4-classes.mjs');
|
|
2293
|
+
const { generateStyleMap } = await import('../chunks/tw4-generator.mjs');
|
|
2294
|
+
const allClasses = await scanComponentClasses(config.componentDirs, nuxt.options.srcDir, logger);
|
|
2295
|
+
const processableClasses = filterProcessableClasses(allClasses);
|
|
2296
|
+
if (processableClasses.length > 0) {
|
|
2297
|
+
logger.debug(`TW4: Found ${processableClasses.length} unique classes in OG components`);
|
|
2298
|
+
const styleMap = await generateStyleMap({
|
|
2299
|
+
cssPath: resolvedCssPath,
|
|
2300
|
+
classes: processableClasses,
|
|
2301
|
+
nuxtUiColors
|
|
2302
|
+
});
|
|
2303
|
+
for (const [cls, styles] of styleMap.classes) {
|
|
2304
|
+
tw4State.styleMap[cls] = styles;
|
|
2305
|
+
}
|
|
2306
|
+
logger.debug(`TW4: Generated style map with ${Object.keys(tw4State.styleMap).length} resolved classes`);
|
|
2307
|
+
}
|
|
2308
|
+
} catch (e) {
|
|
2309
|
+
logger.warn(`TW4 style map generation failed: ${e.message}`);
|
|
2310
|
+
}
|
|
2311
|
+
logger.debug(`TW4 enabled from ${relative$1(nuxt.options.rootDir, resolvedCssPath)}`);
|
|
2312
|
+
tw4State.initialized = true;
|
|
2313
|
+
})();
|
|
2314
|
+
return tw4InitPromise;
|
|
2315
|
+
}
|
|
2316
|
+
const resolvedOgComponentPaths = [];
|
|
2317
|
+
nuxt.hook("modules:done", () => {
|
|
2318
|
+
addVitePlugin(AssetTransformPlugin.vite({
|
|
2319
|
+
emojiSet: finalEmojiStrategy === "local" ? config.defaults.emojis || "noto" : void 0,
|
|
2320
|
+
get ogComponentPaths() {
|
|
2321
|
+
return resolvedOgComponentPaths;
|
|
2322
|
+
},
|
|
2323
|
+
// Resolved OG component directory paths
|
|
2324
|
+
rootDir: nuxt.options.rootDir,
|
|
2325
|
+
srcDir: nuxt.options.srcDir,
|
|
2326
|
+
publicDir: join(nuxt.options.srcDir, nuxt.options.dir.public || "public"),
|
|
2327
|
+
get tw4StyleMap() {
|
|
2328
|
+
return tw4State.styleMap;
|
|
2329
|
+
},
|
|
2330
|
+
// Getter to access populated map
|
|
2331
|
+
initTw4,
|
|
2332
|
+
// Lazy initializer - called on first transform
|
|
2333
|
+
get tw4CssPath() {
|
|
2334
|
+
return tw4State.cssPath;
|
|
2335
|
+
},
|
|
2336
|
+
// Getter for gradient resolution
|
|
2337
|
+
loadNuxtUiColors
|
|
2338
|
+
// Lazy loader for Nuxt UI colors from .nuxt/app.config.mjs
|
|
2339
|
+
}));
|
|
2340
|
+
});
|
|
2341
|
+
if (config.zeroRuntime) {
|
|
2342
|
+
config.compatibility = defu(config.compatibility, {
|
|
2343
|
+
runtime: {
|
|
2344
|
+
chromium: false,
|
|
2345
|
+
// should already be false
|
|
2346
|
+
satori: false
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
if (!nuxt.options.dev) {
|
|
2350
|
+
addBuildPlugin(TreeShakeComposablesPlugin, { server: true, client: true, build: true });
|
|
2351
|
+
nuxt.options.alias["#og-image-cache"] = resolve("./runtime/server/og-image/cache/mock");
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
const basePath = config.zeroRuntime ? "./runtime/server/routes/__zero-runtime" : "./runtime/server/routes";
|
|
2355
|
+
let publicDirAbs = nuxt.options.dir.public;
|
|
2356
|
+
if (!isAbsolute(publicDirAbs)) {
|
|
2357
|
+
publicDirAbs = (publicDirAbs in nuxt.options.alias ? nuxt.options.alias[publicDirAbs] : join(nuxt.options.rootDir, publicDirAbs)) || "";
|
|
2358
|
+
}
|
|
2359
|
+
if (isProviderEnabledForEnv("satori", nuxt, config)) {
|
|
2360
|
+
let attemptSharpUsage = false;
|
|
2361
|
+
if (isProviderEnabledForEnv("sharp", nuxt, config)) {
|
|
2362
|
+
const userConfiguredExtension = config.defaults.extension;
|
|
2363
|
+
const hasConfiguredJpegs = userConfiguredExtension && ["jpeg", "jpg"].includes(userConfiguredExtension);
|
|
2364
|
+
const allDeps = {
|
|
2365
|
+
...userAppPkgJson.dependencies || {},
|
|
2366
|
+
...userAppPkgJson.devDependencies || {}
|
|
2367
|
+
};
|
|
2368
|
+
const hasExplicitSharpDependency = !!config.sharpOptions || "sharp" in allDeps || hasConfiguredJpegs;
|
|
2369
|
+
if (hasExplicitSharpDependency) {
|
|
2370
|
+
if (!targetCompatibility.sharp) {
|
|
2371
|
+
logger.warn(`Rendering JPEGs requires sharp which does not work with ${preset}. Images will be rendered as PNG at runtime.`);
|
|
2372
|
+
config.compatibility = defu(config.compatibility, {
|
|
2373
|
+
runtime: { sharp: false }
|
|
2374
|
+
});
|
|
2375
|
+
} else {
|
|
2376
|
+
await import('sharp').catch(() => {
|
|
2377
|
+
}).then(() => {
|
|
2378
|
+
attemptSharpUsage = true;
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
} else if (hasConfiguredJpegs) {
|
|
2382
|
+
logger.warn("You have enabled `JPEG` images. These require the `sharp` dependency which is missing, installing it for you.");
|
|
2383
|
+
await ensureDependencies(["sharp"]);
|
|
2384
|
+
logger.warn("Support for `sharp` is limited so check the compatibility guide.");
|
|
2385
|
+
attemptSharpUsage = true;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (!attemptSharpUsage) {
|
|
2389
|
+
config.compatibility = defu(config.compatibility, {
|
|
2390
|
+
runtime: { sharp: false },
|
|
2391
|
+
dev: { sharp: false },
|
|
2392
|
+
prerender: { sharp: false }
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
if (isProviderEnabledForEnv("resvg", nuxt, config)) {
|
|
2396
|
+
await import('@resvg/resvg-js').catch(() => {
|
|
2397
|
+
logger.warn("ReSVG is missing dependencies for environment. Falling back to WASM version, this may slow down PNG rendering.");
|
|
2398
|
+
config.compatibility = defu(config.compatibility, {
|
|
2399
|
+
dev: { resvg: "wasm-fs" },
|
|
2400
|
+
prerender: { resvg: "wasm-fs" }
|
|
2401
|
+
});
|
|
2402
|
+
if (targetCompatibility.resvg === "node") {
|
|
2403
|
+
config.compatibility = defu(config.compatibility, {
|
|
2404
|
+
runtime: { resvg: "wasm" }
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
if (isProviderEnabledForEnv("chromium", nuxt, config)) {
|
|
2411
|
+
const hasChromeLocally = checkLocalChrome();
|
|
2412
|
+
const hasPlaywrightDependency = await hasResolvableDependency("playwright");
|
|
2413
|
+
const chromeCompatibilityFlags = {
|
|
2414
|
+
prerender: config.compatibility?.prerender?.chromium,
|
|
2415
|
+
dev: config.compatibility?.dev?.chromium,
|
|
2416
|
+
runtime: config.compatibility?.runtime?.chromium
|
|
2417
|
+
};
|
|
2418
|
+
const chromiumBinding = {
|
|
2419
|
+
dev: null,
|
|
2420
|
+
prerender: null,
|
|
2421
|
+
runtime: null
|
|
2422
|
+
};
|
|
2423
|
+
if (nuxt.options.dev) {
|
|
2424
|
+
if (isUndefinedOrTruthy(chromeCompatibilityFlags.dev))
|
|
2425
|
+
chromiumBinding.dev = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
|
|
2426
|
+
} else {
|
|
2427
|
+
if (isUndefinedOrTruthy(chromeCompatibilityFlags.prerender))
|
|
2428
|
+
chromiumBinding.prerender = hasChromeLocally ? "chrome-launcher" : hasPlaywrightDependency ? "playwright" : "on-demand";
|
|
2429
|
+
if (isUndefinedOrTruthy(chromeCompatibilityFlags.runtime))
|
|
2430
|
+
chromiumBinding.runtime = hasPlaywrightDependency ? "playwright" : null;
|
|
2431
|
+
}
|
|
2432
|
+
config.compatibility = defu(config.compatibility, {
|
|
2433
|
+
runtime: { chromium: chromiumBinding.runtime },
|
|
2434
|
+
dev: { chromium: chromiumBinding.dev },
|
|
2435
|
+
prerender: { chromium: chromiumBinding.prerender }
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
await installNuxtSiteConfig();
|
|
2439
|
+
nuxt.options.experimental.componentIslands ||= true;
|
|
2440
|
+
if (config.debug || nuxt.options.dev) {
|
|
2441
|
+
addServerHandler({
|
|
2442
|
+
lazy: true,
|
|
2443
|
+
route: "/_og/debug.json",
|
|
2444
|
+
handler: resolve("./runtime/server/routes/debug.json")
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
addServerHandler({
|
|
2448
|
+
lazy: true,
|
|
2449
|
+
route: "/_og/d/**",
|
|
2450
|
+
handler: resolve(`${basePath}/image`)
|
|
2451
|
+
});
|
|
2452
|
+
addServerHandler({
|
|
2453
|
+
lazy: true,
|
|
2454
|
+
route: "/_og/s/**",
|
|
2455
|
+
handler: resolve(`${basePath}/image`)
|
|
2456
|
+
});
|
|
2457
|
+
if (!nuxt.options.dev) {
|
|
2458
|
+
nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = [];
|
|
2459
|
+
}
|
|
2460
|
+
[
|
|
2461
|
+
"defineOgImage",
|
|
2462
|
+
"defineOgImageComponent",
|
|
2463
|
+
{ name: "defineOgImageScreenshot", enabled: isProviderEnabledForEnv("chromium", nuxt, config) }
|
|
2464
|
+
].forEach((name) => {
|
|
2465
|
+
if (typeof name === "object") {
|
|
2466
|
+
if (!name.enabled) {
|
|
2467
|
+
addImports({ name: name.name, from: resolve(`./runtime/app/composables/mock`) });
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
name = name.name;
|
|
2471
|
+
}
|
|
2472
|
+
addImports({
|
|
2473
|
+
name,
|
|
2474
|
+
from: resolve(`./runtime/app/composables/${name}`)
|
|
2475
|
+
});
|
|
2476
|
+
if (!nuxt.options.dev) {
|
|
2477
|
+
nuxt.options.optimization.treeShake.composables.client = nuxt.options.optimization.treeShake.composables.client || {};
|
|
2478
|
+
nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] = nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"] || [];
|
|
2479
|
+
nuxt.options.optimization.treeShake.composables.client["nuxt-og-image"].push(name);
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2482
|
+
if (nuxt.options.dev) {
|
|
2483
|
+
addComponentsDir({
|
|
2484
|
+
path: resolve("./runtime/app/components/Templates/Community"),
|
|
2485
|
+
island: true,
|
|
2486
|
+
watch: IS_MODULE_DEVELOPMENT
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
addComponent({
|
|
2490
|
+
name: "OgImageScreenshot",
|
|
2491
|
+
filePath: resolve(`./runtime/app/components/OgImage/OgImageScreenshot`),
|
|
2492
|
+
...config.componentOptions
|
|
2493
|
+
});
|
|
2494
|
+
const basePluginPath = `./runtime/app/plugins${config.zeroRuntime ? "/__zero-runtime" : ""}`;
|
|
2495
|
+
addPlugin({ mode: "server", src: resolve(`${basePluginPath}/route-rule-og-image.server`) });
|
|
2496
|
+
addPlugin({ mode: "server", src: resolve(`${basePluginPath}/og-image-canonical-urls.server`) });
|
|
2497
|
+
const componentRoots = await Promise.all(
|
|
2498
|
+
nuxt.options.components?.dirs?.map(async (dir) => {
|
|
2499
|
+
const dirPath = typeof dir === "string" ? dir : dir.path;
|
|
2500
|
+
return resolver.resolvePath(dirPath).catch(() => null);
|
|
2501
|
+
}) || []
|
|
2502
|
+
).then((paths) => paths.filter(Boolean));
|
|
2503
|
+
for (const componentDir of config.componentDirs) {
|
|
2504
|
+
let found = false;
|
|
2505
|
+
for (const root of componentRoots) {
|
|
2506
|
+
const path = join(root, componentDir);
|
|
2507
|
+
if (existsSync(path)) {
|
|
2508
|
+
found = true;
|
|
2509
|
+
resolvedOgComponentPaths.push(path);
|
|
2510
|
+
addComponentsDir({
|
|
2511
|
+
path,
|
|
2512
|
+
island: true,
|
|
2513
|
+
watch: IS_MODULE_DEVELOPMENT,
|
|
2514
|
+
prefix: componentDir === "OgImageCommunity" ? "OgImage" : void 0
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (!found && !defaultComponentDirs.includes(componentDir)) {
|
|
2519
|
+
logger.warn(`The configured component directory \`${componentDir}\` does not exist in any component root. Skipping.`);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
const builtinTemplatesDir = resolve("./runtime/app/components/Templates");
|
|
2523
|
+
if (fs.existsSync(builtinTemplatesDir)) {
|
|
2524
|
+
resolvedOgComponentPaths.push(builtinTemplatesDir);
|
|
2525
|
+
}
|
|
2526
|
+
const ogImageComponentCtx = { components: [], detectedRenderers: /* @__PURE__ */ new Set() };
|
|
2527
|
+
for (const componentDir of config.componentDirs) {
|
|
2528
|
+
for (const root of componentRoots) {
|
|
2529
|
+
const path = join(root, componentDir);
|
|
2530
|
+
if (fs.existsSync(path)) {
|
|
2531
|
+
const files = fs.readdirSync(path).filter((f) => f.endsWith(".vue"));
|
|
2532
|
+
for (const file of files) {
|
|
2533
|
+
const renderer = getRendererFromFilename(file);
|
|
2534
|
+
if (renderer)
|
|
2535
|
+
ogImageComponentCtx.detectedRenderers.add(renderer);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
const communityDir = resolve("./runtime/app/components/Templates/Community");
|
|
2541
|
+
if (fs.existsSync(communityDir)) {
|
|
2542
|
+
const files = fs.readdirSync(communityDir).filter((f) => f.endsWith(".vue"));
|
|
2543
|
+
for (const file of files) {
|
|
2544
|
+
const renderer = getRendererFromFilename(file);
|
|
2545
|
+
if (renderer)
|
|
2546
|
+
ogImageComponentCtx.detectedRenderers.add(renderer);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
nuxt.hook("components:extend", (components) => {
|
|
2550
|
+
ogImageComponentCtx.components = [];
|
|
2551
|
+
const invalidComponents = [];
|
|
2552
|
+
const baseNameToRenderer = /* @__PURE__ */ new Map();
|
|
2553
|
+
components.forEach((component) => {
|
|
2554
|
+
let valid = false;
|
|
2555
|
+
config.componentDirs.forEach((dir) => {
|
|
2556
|
+
if (component.pascalName.startsWith(dir) || component.kebabName.startsWith(dir) || component.shortPath.includes(`components/${dir}/`)) {
|
|
2557
|
+
valid = true;
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
if (component.filePath.includes(resolve("./runtime/app/components/Templates")))
|
|
2561
|
+
valid = true;
|
|
2562
|
+
if (valid && fs.existsSync(component.filePath)) {
|
|
2563
|
+
const renderer = getRendererFromFilename(component.filePath);
|
|
2564
|
+
if (!renderer) {
|
|
2565
|
+
invalidComponents.push(component.filePath);
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
const baseName = stripRendererSuffix(component.pascalName);
|
|
2569
|
+
const existing = baseNameToRenderer.get(baseName);
|
|
2570
|
+
if (existing && existing.renderer !== renderer) {
|
|
2571
|
+
logger.warn(`Duplicate OG Image component "${baseName}" with different renderers:
|
|
2572
|
+
${existing.path} (${existing.renderer})
|
|
2573
|
+
${component.filePath} (${renderer})`);
|
|
2574
|
+
}
|
|
2575
|
+
baseNameToRenderer.set(baseName, { renderer, path: component.filePath });
|
|
2576
|
+
ogImageComponentCtx.detectedRenderers.add(renderer);
|
|
2577
|
+
component.island = true;
|
|
2578
|
+
component.mode = "server";
|
|
2579
|
+
let category = "app";
|
|
2580
|
+
if (component.filePath.includes(resolve("./runtime/app/components/Templates/Community")))
|
|
2581
|
+
category = "community";
|
|
2582
|
+
const componentFile = fs.readFileSync(component.filePath, "utf-8");
|
|
2583
|
+
const credits = componentFile.split("\n").find((line) => line.startsWith(" * @credits"))?.replace("* @credits", "").trim();
|
|
2584
|
+
ogImageComponentCtx.components.push({
|
|
2585
|
+
hash: hash(componentFile).replaceAll("_", "-"),
|
|
2586
|
+
pascalName: component.pascalName,
|
|
2587
|
+
kebabName: component.kebabName,
|
|
2588
|
+
path: component.filePath,
|
|
2589
|
+
category,
|
|
2590
|
+
credits,
|
|
2591
|
+
renderer
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
});
|
|
2595
|
+
if (!nuxt.options.dev) {
|
|
2596
|
+
const communityDir2 = resolve("./runtime/app/components/Templates/Community");
|
|
2597
|
+
if (fs.existsSync(communityDir2)) {
|
|
2598
|
+
fs.readdirSync(communityDir2).filter((f) => f.endsWith(".vue")).forEach((file) => {
|
|
2599
|
+
const renderer = getRendererFromFilename(file);
|
|
2600
|
+
if (!renderer)
|
|
2601
|
+
return;
|
|
2602
|
+
const name = file.replace(".vue", "");
|
|
2603
|
+
const filePath = resolve(communityDir2);
|
|
2604
|
+
if (ogImageComponentCtx.components.some((c) => c.pascalName === name))
|
|
2605
|
+
return;
|
|
2606
|
+
ogImageComponentCtx.components.push({
|
|
2607
|
+
hash: "",
|
|
2608
|
+
pascalName: name,
|
|
2609
|
+
kebabName: name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
|
|
2610
|
+
path: filePath,
|
|
2611
|
+
category: "community",
|
|
2612
|
+
renderer
|
|
2613
|
+
});
|
|
2614
|
+
ogImageComponentCtx.detectedRenderers.add(renderer);
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
if (invalidComponents.length > 0) {
|
|
2619
|
+
const message = `OG Image components missing renderer suffix (.satori.vue, .chromium.vue, .takumi.vue):
|
|
2620
|
+
${invalidComponents.map((c) => ` ${c}`).join("\n")}
|
|
2621
|
+
|
|
2622
|
+
Run: npx nuxt-og-image migrate v6`;
|
|
2623
|
+
if (nuxt.options.dev) {
|
|
2624
|
+
if (config.warnMissingSuffix !== false)
|
|
2625
|
+
logger.warn(message);
|
|
2626
|
+
} else {
|
|
2627
|
+
throw new Error(message);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
nuxt.hooks.hook("nuxt-og-image:components", ogImageComponentCtx);
|
|
2631
|
+
});
|
|
2632
|
+
addTemplate({
|
|
2633
|
+
filename: "nuxt-og-image/components.mjs",
|
|
2634
|
+
getContents() {
|
|
2635
|
+
return `export const componentNames = ${JSON.stringify(ogImageComponentCtx.components)}`;
|
|
2636
|
+
},
|
|
2637
|
+
options: { mode: "server" }
|
|
2638
|
+
});
|
|
2639
|
+
nuxt.options.nitro.virtual = nuxt.options.nitro.virtual || {};
|
|
2640
|
+
nuxt.options.nitro.virtual["#og-image-virtual/component-names.mjs"] = () => {
|
|
2641
|
+
return `export const componentNames = ${JSON.stringify(ogImageComponentCtx.components)}`;
|
|
2642
|
+
};
|
|
2643
|
+
nuxt.options.nitro.virtual["#og-image-virtual/public-assets.mjs"] = async () => {
|
|
2644
|
+
const isCloudflare = ["cloudflare", "cloudflare-pages", "cloudflare-module"].includes(preset);
|
|
2645
|
+
if (isCloudflare) {
|
|
2646
|
+
const devBinding = resolver.resolve("./runtime/server/og-image/bindings/font-assets/dev-prerender");
|
|
2647
|
+
const cfBinding = resolver.resolve("./runtime/server/og-image/bindings/font-assets/cloudflare");
|
|
2648
|
+
return `
|
|
2649
|
+
import { resolve as devResolve } from '${devBinding}'
|
|
2650
|
+
import { resolve as cfResolve } from '${cfBinding}'
|
|
2651
|
+
export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve : cfResolve
|
|
2652
|
+
`;
|
|
2653
|
+
}
|
|
2654
|
+
return `export { resolve } from '${resolver.resolve("./runtime/server/og-image/bindings/font-assets/dev-prerender")}'`;
|
|
2655
|
+
};
|
|
2656
|
+
nuxt.options.nitro.virtual["#og-image/fonts"] = async () => {
|
|
2657
|
+
const templates = nuxt.options.build.templates;
|
|
2658
|
+
const nuxtFontsTemplate = templates.find((t) => t.filename?.endsWith("nuxt-fonts-global.css"));
|
|
2659
|
+
if (!nuxtFontsTemplate?.getContents) {
|
|
2660
|
+
return `export default []`;
|
|
2661
|
+
}
|
|
2662
|
+
const contents = await nuxtFontsTemplate.getContents({});
|
|
2663
|
+
const fontFaceRegex = /@font-face\s*\{([^}]+)\}/g;
|
|
2664
|
+
const fonts = [];
|
|
2665
|
+
for (const match of contents.matchAll(fontFaceRegex)) {
|
|
2666
|
+
const block = match[1];
|
|
2667
|
+
if (!block)
|
|
2668
|
+
continue;
|
|
2669
|
+
const family = block.match(/font-family:\s*['"]?([^'";]+)['"]?/)?.[1]?.trim();
|
|
2670
|
+
const src = block.match(/url\(["']?([^)"']+)["']?\)/)?.[1];
|
|
2671
|
+
const weight = Number.parseInt(block.match(/font-weight:\s*(\d+)/)?.[1] || "400");
|
|
2672
|
+
const style = block.match(/font-style:\s*(\w+)/)?.[1] || "normal";
|
|
2673
|
+
if (family && src) {
|
|
2674
|
+
fonts.push({ family, src, weight, style });
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
const familiesWithNonWoff2 = new Set(fonts.filter((f) => !f.src.endsWith(".woff2")).map((f) => f.family));
|
|
2678
|
+
const warnedFamilies = /* @__PURE__ */ new Set();
|
|
2679
|
+
for (const f of fonts) {
|
|
2680
|
+
if (f.src.endsWith(".woff2") && !familiesWithNonWoff2.has(f.family) && !warnedFamilies.has(f.family)) {
|
|
2681
|
+
warnedFamilies.add(f.family);
|
|
2682
|
+
logger.warn(`WOFF2-only font detected (${f.family}). Satori renderer does not support WOFF2 - use Takumi renderer or provide WOFF/TTF alternatives.`);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
logger.debug(`Extracted fonts from @nuxt/fonts: ${JSON.stringify(fonts)}`);
|
|
2686
|
+
return `export default ${JSON.stringify(fonts)}`;
|
|
2687
|
+
};
|
|
2688
|
+
nuxt.options.nitro.virtual["#og-image-virtual/tw4-theme.mjs"] = () => {
|
|
2689
|
+
return `export const tw4FontVars = ${JSON.stringify(tw4State.fontVars)}
|
|
2690
|
+
export const tw4Breakpoints = ${JSON.stringify(tw4State.breakpoints)}
|
|
2691
|
+
export const tw4Colors = ${JSON.stringify(tw4State.colors)}`;
|
|
2692
|
+
};
|
|
2693
|
+
nuxt.options.nitro.virtual["#og-image-virtual/build-dir.mjs"] = () => {
|
|
2694
|
+
return `export const buildDir = ${JSON.stringify(nuxt.options.buildDir)}`;
|
|
2695
|
+
};
|
|
2696
|
+
let fontContext = null;
|
|
2697
|
+
nuxt.hook("fonts:public-asset-context", (ctx) => {
|
|
2698
|
+
fontContext = ctx;
|
|
2699
|
+
});
|
|
2700
|
+
nuxt.hook("vite:compiled", () => {
|
|
2701
|
+
if (fontContext?.renderedFontURLs.size) {
|
|
2702
|
+
const cacheDir = join(nuxt.options.buildDir, "cache", "og-image");
|
|
2703
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
2704
|
+
const mapping = Object.fromEntries(fontContext.renderedFontURLs);
|
|
2705
|
+
fs.writeFileSync(join(cacheDir, "font-urls.json"), JSON.stringify(mapping));
|
|
2706
|
+
logger.debug(`Persisted ${fontContext.renderedFontURLs.size} font URLs for prerender`);
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2709
|
+
registerTypeTemplates({
|
|
2710
|
+
nuxt,
|
|
2711
|
+
config,
|
|
2712
|
+
componentCtx: ogImageComponentCtx
|
|
2713
|
+
});
|
|
2714
|
+
const cacheEnabled = typeof config.runtimeCacheStorage !== "undefined" && config.runtimeCacheStorage !== false;
|
|
2715
|
+
const cacheVersion = config.cacheVersion === false ? "" : config.cacheVersion ?? version;
|
|
2716
|
+
const versionSuffix = cacheVersion ? `/${cacheVersion}` : "";
|
|
2717
|
+
let baseCacheKey;
|
|
2718
|
+
if (config.runtimeCacheStorage === true) {
|
|
2719
|
+
baseCacheKey = `/cache/nuxt-og-image${versionSuffix}`;
|
|
2720
|
+
} else if (typeof config.runtimeCacheStorage === "string") {
|
|
2721
|
+
baseCacheKey = `/${config.runtimeCacheStorage}/nuxt-og-image${versionSuffix}`;
|
|
2722
|
+
} else if (typeof config.runtimeCacheStorage === "object") {
|
|
2723
|
+
baseCacheKey = `/nuxt-og-image${versionSuffix}`;
|
|
2724
|
+
if (!nuxt.options.dev) {
|
|
2725
|
+
nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
|
|
2726
|
+
nuxt.options.nitro.storage["nuxt-og-image"] = config.runtimeCacheStorage;
|
|
2727
|
+
}
|
|
2728
|
+
} else {
|
|
2729
|
+
baseCacheKey = false;
|
|
2730
|
+
}
|
|
2731
|
+
if (!cacheEnabled)
|
|
2732
|
+
baseCacheKey = false;
|
|
2733
|
+
const buildCachePath = typeof config.buildCache === "object" && config.buildCache.base ? config.buildCache.base : "node_modules/.cache/nuxt-seo/og-image";
|
|
2734
|
+
const buildCacheDir = config.buildCache ? join(nuxt.options.rootDir, buildCachePath) : void 0;
|
|
2735
|
+
nuxt.hooks.hook("modules:done", async () => {
|
|
2736
|
+
if (!isNuxtGenerate() && nuxt.options.build) {
|
|
2737
|
+
nuxt.options.nitro = nuxt.options.nitro || {};
|
|
2738
|
+
nuxt.options.nitro.prerender = nuxt.options.nitro.prerender || {};
|
|
2739
|
+
nuxt.options.nitro.prerender.routes = nuxt.options.nitro.prerender.routes || [];
|
|
2740
|
+
}
|
|
2741
|
+
const hasColorModeModule = hasNuxtModule("@nuxtjs/color-mode");
|
|
2742
|
+
const colorModeOptions = hasColorModeModule ? await getNuxtModuleOptions("@nuxtjs/color-mode") : {};
|
|
2743
|
+
let colorPreference = colorModeOptions.preference;
|
|
2744
|
+
if (!colorPreference || colorPreference === "system")
|
|
2745
|
+
colorPreference = colorModeOptions.fallback;
|
|
2746
|
+
if (!colorPreference || colorPreference === "system")
|
|
2747
|
+
colorPreference = "light";
|
|
2748
|
+
const runtimeConfig = {
|
|
2749
|
+
version,
|
|
2750
|
+
// binding options
|
|
2751
|
+
satoriOptions: config.satoriOptions || {},
|
|
2752
|
+
resvgOptions: config.resvgOptions || {},
|
|
2753
|
+
sharpOptions: config.sharpOptions === true ? {} : config.sharpOptions || {},
|
|
2754
|
+
publicStoragePath: `root${publicDirAbs.replace(nuxt.options.rootDir, "").replaceAll("/", ":")}`,
|
|
2755
|
+
defaults: config.defaults,
|
|
2756
|
+
debug: config.debug,
|
|
2757
|
+
// avoid adding credentials
|
|
2758
|
+
baseCacheKey,
|
|
2759
|
+
buildCacheDir,
|
|
2760
|
+
hasNuxtIcon: hasNuxtModule("nuxt-icon") || hasNuxtModule("@nuxt/icon"),
|
|
2761
|
+
colorPreference,
|
|
2762
|
+
// @ts-expect-error runtime type
|
|
2763
|
+
isNuxtContentDocumentDriven: !!nuxt.options.content?.documentDriven,
|
|
2764
|
+
cacheQueryParams: config.cacheQueryParams ?? false
|
|
2765
|
+
};
|
|
2766
|
+
if (nuxt.options.dev) {
|
|
2767
|
+
runtimeConfig.componentDirs = config.componentDirs;
|
|
2768
|
+
runtimeConfig.srcDir = nuxt.options.srcDir;
|
|
2769
|
+
runtimeConfig.communityTemplatesDir = resolve("./runtime/app/components/Templates/Community");
|
|
2770
|
+
}
|
|
2771
|
+
nuxt.hooks.callHook("nuxt-og-image:runtime-config", runtimeConfig);
|
|
2772
|
+
nuxt.options.runtimeConfig["nuxt-og-image"] = runtimeConfig;
|
|
2773
|
+
});
|
|
2774
|
+
const getDetectedRenderers = () => ogImageComponentCtx.detectedRenderers;
|
|
2775
|
+
if (nuxt.options.dev) {
|
|
2776
|
+
setupDevHandler(config, resolver, getDetectedRenderers);
|
|
2777
|
+
setupDevToolsUI(config, resolve);
|
|
2778
|
+
const useNitro = new Promise((resolveNitro) => {
|
|
2779
|
+
nuxt.hooks.hook("nitro:init", resolveNitro);
|
|
2780
|
+
});
|
|
2781
|
+
nuxt.hook("builder:watch", async (_event, relativePath) => {
|
|
2782
|
+
const absolutePath = join(nuxt.options.rootDir, relativePath);
|
|
2783
|
+
const isTw4CssChange = tw4State.cssPath && absolutePath === tw4State.cssPath;
|
|
2784
|
+
const isOgImageComponent = config.componentDirs.some((dir) => {
|
|
2785
|
+
const componentDir = join(nuxt.options.srcDir, "components", dir);
|
|
2786
|
+
return absolutePath.startsWith(componentDir) && absolutePath.endsWith(".vue");
|
|
2787
|
+
});
|
|
2788
|
+
if (isTw4CssChange || isOgImageComponent) {
|
|
2789
|
+
clearTw4Cache();
|
|
2790
|
+
if (tw4State.cssPath && existsSync(tw4State.cssPath)) {
|
|
2791
|
+
const { scanComponentClasses, filterProcessableClasses } = await import('../chunks/tw4-classes.mjs');
|
|
2792
|
+
const { generateStyleMap } = await import('../chunks/tw4-generator.mjs');
|
|
2793
|
+
const allClasses = await scanComponentClasses(config.componentDirs, nuxt.options.srcDir, logger);
|
|
2794
|
+
const processableClasses = filterProcessableClasses(allClasses);
|
|
2795
|
+
if (processableClasses.length > 0) {
|
|
2796
|
+
const nuxtUiColors = await loadNuxtUiColors();
|
|
2797
|
+
const styleMap = await generateStyleMap({
|
|
2798
|
+
cssPath: tw4State.cssPath,
|
|
2799
|
+
classes: processableClasses,
|
|
2800
|
+
nuxtUiColors
|
|
2801
|
+
});
|
|
2802
|
+
tw4State.styleMap = {};
|
|
2803
|
+
for (const [cls, styles] of styleMap.classes) {
|
|
2804
|
+
tw4State.styleMap[cls] = styles;
|
|
2805
|
+
}
|
|
2806
|
+
logger.info(`HMR: Regenerated TW4 style map (${Object.keys(tw4State.styleMap).length} classes)`);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
await updateTemplates({ filter: (t) => t.filename.includes("nuxt-og-image") });
|
|
2810
|
+
const nitro = await useNitro;
|
|
2811
|
+
await nitro.hooks.callHook("rollup:reload");
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
} else if (isNuxtGenerate()) {
|
|
2815
|
+
setupGenerateHandler(config, resolver);
|
|
2816
|
+
} else if (nuxt.options.build) {
|
|
2817
|
+
await setupBuildHandler(config, resolver, getDetectedRenderers);
|
|
2818
|
+
}
|
|
2819
|
+
if (nuxt.options.build)
|
|
2820
|
+
addServerPlugin(resolve("./runtime/server/plugins/prerender"));
|
|
2821
|
+
setupPrerenderHandler(config, resolver, getDetectedRenderers);
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
export { convertOklchToHex as a, buildNuxtUiVars as b, createStylesheetLoader as c, decodeCssClassName as d, module$1 as m, walkTemplateAst as w };
|