nuxt-og-image 3.0.0-beta.25 → 3.0.0-beta.27
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 +2 -2
- package/dist/client/200.html +5 -5
- package/dist/client/404.html +5 -5
- package/dist/client/_nuxt/{IconCSS.7d585b82.js → IconCSS.d69522d9.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/04a1a499-35bd-4c8e-b749-645f4e958e84.json +1 -0
- package/dist/client/_nuxt/{entry.553872fd.js → entry.a01acc2c.js} +69 -69
- package/dist/client/_nuxt/entry.f47d0628.css +1 -0
- package/dist/client/_nuxt/{error-404.5a969881.js → error-404.ba8226d4.js} +1 -1
- package/dist/client/_nuxt/{error-500.bd8d9f3b.js → error-500.bd88cc81.js} +1 -1
- package/dist/client/index.html +5 -5
- package/dist/module.d.mts +4 -3
- package/dist/module.d.ts +4 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +43 -26
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.mjs +5 -4
- package/dist/runtime/components/OgImage/OgImage.d.ts +1 -1
- package/dist/runtime/components/Templates/{Official → Community}/BrandedLogo.vue +3 -0
- package/dist/runtime/components/Templates/Community/Nuxt.vue +6 -3
- package/dist/runtime/components/Templates/Community/NuxtSeo.vue +142 -0
- package/dist/runtime/components/Templates/{Official → Community}/SimpleBlog.vue +6 -2
- package/dist/runtime/components/Templates/{Official → Community}/Wave.vue +4 -0
- package/dist/runtime/components/Templates/{Official → Community}/WithEmoji.vue +4 -0
- package/dist/runtime/composables/defineOgImage.mjs +3 -3
- package/dist/runtime/composables/defineOgImageComponent.mjs +2 -2
- package/dist/runtime/core/cache/prerender.d.ts +1 -1
- package/dist/runtime/core/font/fetch.mjs +10 -4
- package/dist/runtime/core/html/applyEmojis.mjs +1 -1
- package/dist/runtime/core/html/devIframeTemplate.mjs +13 -5
- package/dist/runtime/core/options/normalise.mjs +3 -2
- package/dist/runtime/core/renderers/chromium/screenshot.d.ts +1 -1
- package/dist/runtime/core/renderers/chromium/screenshot.mjs +2 -2
- package/dist/runtime/core/renderers/satori/index.mjs +4 -4
- package/dist/runtime/core/utils/resolveRendererContext.mjs +13 -3
- package/dist/runtime/nitro/plugins/nuxt-content.mjs +2 -3
- package/dist/runtime/nitro/plugins/prerender.d.ts +1 -1
- package/dist/runtime/nitro/plugins/prerender.mjs +12 -4
- package/dist/runtime/nuxt/plugins/og-image-canonical-urls.server.mjs +31 -0
- package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +5 -6
- package/dist/runtime/nuxt/utils.mjs +8 -10
- package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +1 -1
- package/dist/runtime/server/routes/__og-image__/debug.json.mjs +3 -2
- package/dist/runtime/server/routes/__og-image__/image.mjs +3 -2
- package/dist/runtime/types.d.ts +18 -2
- package/dist/runtime/utils.d.ts +3 -2
- package/dist/runtime/utils.mjs +4 -4
- package/dist/runtime/utils.pure.d.ts +5 -0
- package/dist/runtime/utils.pure.mjs +43 -0
- package/package.json +3 -3
- package/dist/client/_nuxt/builds/meta/4219a0b1-e5b0-44b6-bf23-fa336afbfc0a.json +0 -1
- package/dist/client/_nuxt/entry.3a009184.css +0 -1
- package/dist/runtime/components/Templates/Official/Fallback.vue +0 -147
- package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +0 -29
- /package/dist/runtime/nuxt/plugins/{nuxt-content-canonical-urls.d.ts → og-image-canonical-urls.server.d.ts} +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* @credits Nuxt SEO <https://nuxtseo.com/>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed, defineComponent, h, resolveComponent } from 'vue'
|
|
7
|
+
import { useOgImageRuntimeConfig } from '../../../utils'
|
|
8
|
+
import { useSiteConfig } from '#imports'
|
|
9
|
+
|
|
10
|
+
// inherited attrs can mess up the satori parser
|
|
11
|
+
defineOptions({
|
|
12
|
+
inheritAttrs: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// convert to typescript props
|
|
16
|
+
const props = withDefaults(defineProps<{
|
|
17
|
+
colorMode?: 'dark' | 'light'
|
|
18
|
+
title?: string
|
|
19
|
+
description?: string
|
|
20
|
+
icon?: string | boolean
|
|
21
|
+
siteName?: string
|
|
22
|
+
siteLogo?: string
|
|
23
|
+
theme?: string
|
|
24
|
+
}>(), {
|
|
25
|
+
theme: '#00dc82',
|
|
26
|
+
title: 'title',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const HexRegex = /^#([0-9a-f]{3}){1,2}$/i
|
|
30
|
+
|
|
31
|
+
const runtimeConfig = useOgImageRuntimeConfig()
|
|
32
|
+
|
|
33
|
+
const colorMode = computed(() => {
|
|
34
|
+
return props.colorMode || runtimeConfig.colorPreference || 'light'
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const themeHex = computed(() => {
|
|
38
|
+
// regex test if valid hex
|
|
39
|
+
if (HexRegex.test(props.theme))
|
|
40
|
+
return props.theme
|
|
41
|
+
|
|
42
|
+
// if it's hex without the hash, just add the hash
|
|
43
|
+
if (HexRegex.test(`#${props.theme}`))
|
|
44
|
+
return `#${props.theme}`
|
|
45
|
+
|
|
46
|
+
// if it's rgb or rgba, we convert it to hex
|
|
47
|
+
if (props.theme.startsWith('rgb')) {
|
|
48
|
+
const rgb = props.theme
|
|
49
|
+
.replace('rgb(', '')
|
|
50
|
+
.replace('rgba(', '')
|
|
51
|
+
.replace(')', '')
|
|
52
|
+
.split(',')
|
|
53
|
+
.map(v => Number.parseInt(v.trim(), 10))
|
|
54
|
+
const hex = rgb
|
|
55
|
+
.map((v) => {
|
|
56
|
+
const hex = v.toString(16)
|
|
57
|
+
return hex.length === 1 ? `0${hex}` : hex
|
|
58
|
+
})
|
|
59
|
+
.join('')
|
|
60
|
+
return `#${hex}`
|
|
61
|
+
}
|
|
62
|
+
return '#FFFFFF'
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const themeRgb = computed(() => {
|
|
66
|
+
// we want to convert it so it's just `<red>, <green>, <blue>` (255, 255, 255)
|
|
67
|
+
return themeHex.value
|
|
68
|
+
.replace('#', '')
|
|
69
|
+
.match(/.{1,2}/g)
|
|
70
|
+
?.map(v => Number.parseInt(v, 16))
|
|
71
|
+
.join(', ')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const siteConfig = useSiteConfig()
|
|
75
|
+
const siteName = computed(() => {
|
|
76
|
+
return props.siteName || siteConfig.name
|
|
77
|
+
})
|
|
78
|
+
const siteLogo = computed(() => {
|
|
79
|
+
return props.siteLogo || siteConfig.logo
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const IconComponent = runtimeConfig.hasNuxtIcon
|
|
83
|
+
? resolveComponent('Icon')
|
|
84
|
+
: defineComponent({
|
|
85
|
+
render() {
|
|
86
|
+
return h('div', 'missing nuxt-icon')
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
if (typeof props.icon === 'string' && !runtimeConfig.hasNuxtIcon && process.dev) {
|
|
90
|
+
console.warn('Please install `nuxt-icon` to use icons with the fallback OG Image component.')
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.log('\nnpm add -D nuxt-icon\n')
|
|
93
|
+
// create simple div renderer component
|
|
94
|
+
}
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<div
|
|
99
|
+
class="w-full h-full flex justify-between relative p-[60px]"
|
|
100
|
+
:class="[
|
|
101
|
+
colorMode === 'light' ? ['bg-white', 'text-gray-900'] : ['bg-gray-900', 'text-gray-50'],
|
|
102
|
+
]"
|
|
103
|
+
>
|
|
104
|
+
<div
|
|
105
|
+
class="flex absolute top-0 right-[-100%]" :style="{
|
|
106
|
+
width: '200%',
|
|
107
|
+
height: '200%',
|
|
108
|
+
backgroundImage: `radial-gradient(circle, rgba(${themeRgb}, 0.5) 0%, ${colorMode === 'dark' ? 'rgba(5, 5, 5,0.3)' : 'rgba(255, 255, 255, 0.7)'} 50%, ${props.colorMode === 'dark' ? 'rgba(5, 5, 5,0)' : 'rgba(255, 255, 255, 0)'} 70%)`,
|
|
109
|
+
}"
|
|
110
|
+
/>
|
|
111
|
+
<div class="h-full w-full justify-between relative">
|
|
112
|
+
<div class="flex flex-row justify-between items-center">
|
|
113
|
+
<div class="flex flex-col w-full" :style="icon ? { width: '65%' } : {}">
|
|
114
|
+
<h1 class="font-bold mb-[30px] text-[75px] max-w-[70%]">
|
|
115
|
+
{{ title }}
|
|
116
|
+
</h1>
|
|
117
|
+
<p
|
|
118
|
+
v-if="description" class="text-[35px]" :class="[
|
|
119
|
+
colorMode === 'light' ? ['text-gray-700'] : ['text-gray-300'],
|
|
120
|
+
]"
|
|
121
|
+
>
|
|
122
|
+
{{ description }}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
<div v-if="icon" style="width: 30%;" class="flex justify-end">
|
|
126
|
+
<IconComponent :name="icon" size="250px" style="margin: 0 auto 0 100px;opacity: 0.9;" />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="flex flex-row justify-center items-center text-left w-full">
|
|
130
|
+
<img v-if="siteLogo" :src="siteLogo" height="30">
|
|
131
|
+
<template v-else>
|
|
132
|
+
<svg height="50" width="50" class="mr-3" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
|
133
|
+
<path :fill="theme.includes('#') ? theme : `#${theme}`" d="M62.3,-53.9C74.4,-34.5,73.5,-9,67.1,13.8C60.6,36.5,48.7,56.5,30.7,66.1C12.7,75.7,-11.4,74.8,-31.6,65.2C-51.8,55.7,-67.9,37.4,-73.8,15.7C-79.6,-6,-75.1,-31.2,-61.1,-51C-47.1,-70.9,-23.6,-85.4,0.8,-86C25.1,-86.7,50.2,-73.4,62.3,-53.9Z" transform="translate(100 100)" />
|
|
134
|
+
</svg>
|
|
135
|
+
<p v-if="siteName" style="font-size: 25px;" class="font-bold">
|
|
136
|
+
{{ siteName }}
|
|
137
|
+
</p>
|
|
138
|
+
</template>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* @credits Full Stack Heroes <https://fullstackheroes.com/>
|
|
4
|
+
*/
|
|
5
|
+
|
|
2
6
|
import { parseURL } from 'ufo'
|
|
3
7
|
import { computed, useSiteConfig } from '#imports'
|
|
4
8
|
|
|
@@ -25,9 +29,9 @@ const website = computed(() => {
|
|
|
25
29
|
<h1 class="text-[80px] p-20 font-black text-left">
|
|
26
30
|
{{ title }}
|
|
27
31
|
</h1>
|
|
28
|
-
<
|
|
32
|
+
<p class="text-2xl pb-10 px-20 font-bold mb-0">
|
|
29
33
|
{{ website }}
|
|
30
|
-
</
|
|
34
|
+
</p>
|
|
31
35
|
</div>
|
|
32
36
|
</div>
|
|
33
37
|
</div>
|
|
@@ -2,7 +2,7 @@ import { defu } from "defu";
|
|
|
2
2
|
import { appendHeader } from "h3";
|
|
3
3
|
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
4
4
|
import { withoutBase } from "ufo";
|
|
5
|
-
import { getOgImagePath } from "../utils.mjs";
|
|
5
|
+
import { getOgImagePath, separateProps, useOgImageRuntimeConfig } from "../utils.mjs";
|
|
6
6
|
import { normaliseOptions } from "../core/options/normalise.mjs";
|
|
7
7
|
import { createOgImageMeta } from "../nuxt/utils.mjs";
|
|
8
8
|
import { useNuxtApp, useRequestEvent, useRouter, useRuntimeConfig } from "#imports";
|
|
@@ -28,8 +28,8 @@ export function defineOgImage(_options = {}) {
|
|
|
28
28
|
const options = normaliseOptions({
|
|
29
29
|
..._options
|
|
30
30
|
});
|
|
31
|
-
const { defaults } =
|
|
32
|
-
const resolvedOptions = normaliseOptions(defu(options, routeRules, defaults));
|
|
31
|
+
const { defaults } = useOgImageRuntimeConfig();
|
|
32
|
+
const resolvedOptions = normaliseOptions(defu(separateProps(options), separateProps(routeRules), defaults));
|
|
33
33
|
if (_options.url) {
|
|
34
34
|
createOgImageMeta(null, options, resolvedOptions, nuxtApp.ssrContext);
|
|
35
35
|
} else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Browser } from 'playwright-core';
|
|
2
2
|
import type { OgImageOptions } from '../../types';
|
|
3
|
-
export declare const prerenderCache: import("unstorage/dist/shared/unstorage.745f9650").a<OgImageOptions<"
|
|
3
|
+
export declare const prerenderCache: import("unstorage/dist/shared/unstorage.745f9650").a<OgImageOptions<"NuxtSeo">> | undefined;
|
|
4
4
|
export declare const prerenderChromiumContext: {
|
|
5
5
|
browser?: Browser;
|
|
6
6
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createError } from "h3";
|
|
2
3
|
import { base64ToArrayBuffer } from "../env/assets.mjs";
|
|
3
4
|
import { fontCache } from "./cache.mjs";
|
|
4
5
|
import { useNitroOrigin, useStorage } from "#imports";
|
|
@@ -13,10 +14,15 @@ export async function loadFont({ e }, font) {
|
|
|
13
14
|
data = base64ToArrayBuffer(await useStorage().getItem(storageKey));
|
|
14
15
|
if (!data) {
|
|
15
16
|
if (font.path) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
17
|
+
if (import.meta.prerender) {
|
|
18
|
+
const key = `root:public${font.path.replace("./", ":").replace("/", ":")}`;
|
|
19
|
+
data = await useStorage().getItemRaw(key);
|
|
20
|
+
} else {
|
|
21
|
+
data = await e.$fetch(font.path, {
|
|
22
|
+
baseURL: useNitroOrigin(e),
|
|
23
|
+
responseType: "arrayBuffer"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
20
26
|
} else {
|
|
21
27
|
data = await e.$fetch(`/__og-image__/font/${name}/${weight}.ttf`, {
|
|
22
28
|
responseType: "arrayBuffer"
|
|
@@ -25,7 +25,7 @@ export async function applyEmojis(ctx, island) {
|
|
|
25
25
|
}
|
|
26
26
|
if (svg)
|
|
27
27
|
return `
|
|
28
|
-
${svg.replace("<svg ", '<svg data-emoji style="margin: 0 .05em 0 .
|
|
28
|
+
${svg.replace("<svg ", '<svg data-emoji style="margin: 0 .05em 0 .15em; vertical-align: -0.1em;" ')}
|
|
29
29
|
`;
|
|
30
30
|
return match;
|
|
31
31
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createHeadCore } from "@unhead/vue";
|
|
2
2
|
import { renderSSRHead } from "@unhead/ssr";
|
|
3
|
+
import { useOgImageRuntimeConfig } from "../../utils.mjs";
|
|
3
4
|
import { applyEmojis } from "./applyEmojis.mjs";
|
|
4
5
|
import { fetchIsland } from "./fetchIsland.mjs";
|
|
5
|
-
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export async function devIframeTemplate(ctx) {
|
|
7
7
|
const { options } = ctx;
|
|
8
|
-
const { fonts, satoriOptions } =
|
|
8
|
+
const { fonts, satoriOptions } = useOgImageRuntimeConfig();
|
|
9
9
|
const island = await fetchIsland(ctx);
|
|
10
10
|
const head = createHeadCore();
|
|
11
11
|
head.push(island.head);
|
|
@@ -29,15 +29,23 @@ export async function devIframeTemplate(ctx) {
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
innerHTML: `body {
|
|
32
|
-
transform: scale(${options.scale || 1});
|
|
32
|
+
transform: scale(${options.props.scale || 1});
|
|
33
33
|
transform-origin: top left;
|
|
34
34
|
max-height: 100vh;
|
|
35
35
|
position: relative;
|
|
36
36
|
width: ${options.width}px;
|
|
37
37
|
height: ${options.height}px;
|
|
38
38
|
overflow: hidden;
|
|
39
|
-
background-color: ${options.
|
|
40
|
-
}
|
|
39
|
+
background-color: ${options.props.colorMode === "dark" ? "#1b1b1b" : "#fff"};
|
|
40
|
+
}
|
|
41
|
+
div {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
}
|
|
45
|
+
svg[data-emoji] {
|
|
46
|
+
display: inline-block;
|
|
47
|
+
}
|
|
48
|
+
`
|
|
41
49
|
},
|
|
42
50
|
...fonts.filter((font) => font.path).map((font) => {
|
|
43
51
|
return `
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useOgImageRuntimeConfig } from "../../utils.mjs";
|
|
2
|
+
import { unref } from "#imports";
|
|
2
3
|
import { componentNames } from "#build/og-image-component-names.mjs";
|
|
3
4
|
export function normaliseOptions(_options) {
|
|
4
|
-
const { runtimeSatori } =
|
|
5
|
+
const { runtimeSatori } = useOgImageRuntimeConfig();
|
|
5
6
|
const options = { ...unref(_options) };
|
|
6
7
|
if (options.static)
|
|
7
8
|
options.cache = options.cache || options.static;
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
import type { Buffer } from 'node:buffer';
|
|
3
3
|
import type { Browser } from 'playwright-core';
|
|
4
4
|
import type { H3EventOgImageRender } from '../../../types';
|
|
5
|
-
export declare function createScreenshot({ e, options, extension }: H3EventOgImageRender, browser: Browser): Promise<Buffer>;
|
|
5
|
+
export declare function createScreenshot({ basePath, e, options, extension }: H3EventOgImageRender, browser: Browser): Promise<Buffer>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { joinURL, withQuery } from "ufo";
|
|
2
2
|
import { useNitroOrigin } from "#imports";
|
|
3
|
-
export async function createScreenshot({ e, options, extension }, browser) {
|
|
4
|
-
const path = options.component === "PageScreenshot" ? options.path : joinURL("/__og-image__/image",
|
|
3
|
+
export async function createScreenshot({ basePath, e, options, extension }, browser) {
|
|
4
|
+
const path = options.component === "PageScreenshot" ? options.path : joinURL("/__og-image__/image", basePath, `og.html`);
|
|
5
5
|
const page = await browser.newPage({
|
|
6
6
|
colorScheme: options.colorScheme,
|
|
7
7
|
baseURL: useNitroOrigin(e)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
|
+
import { useOgImageRuntimeConfig } from "../../../utils.mjs";
|
|
2
3
|
import { createVNodes } from "./vnodes.mjs";
|
|
3
4
|
import { loadFonts, satoriFonts } from "./fonts.mjs";
|
|
4
5
|
import { useResvg, useSatori, useSharp } from "./instances.mjs";
|
|
5
|
-
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export async function createSvg(event) {
|
|
7
7
|
const { options } = event;
|
|
8
|
-
const { fonts, satoriOptions } =
|
|
8
|
+
const { fonts, satoriOptions } = useOgImageRuntimeConfig();
|
|
9
9
|
const vnodes = await createVNodes(event);
|
|
10
10
|
if (!satoriFonts.length)
|
|
11
11
|
satoriFonts.push(...await loadFonts(event, fonts));
|
|
@@ -18,7 +18,7 @@ export async function createSvg(event) {
|
|
|
18
18
|
}));
|
|
19
19
|
}
|
|
20
20
|
async function createPng(event) {
|
|
21
|
-
const { resvgOptions } =
|
|
21
|
+
const { resvgOptions } = useOgImageRuntimeConfig();
|
|
22
22
|
const svg = await createSvg(event);
|
|
23
23
|
const Resvg = await useResvg();
|
|
24
24
|
const resvg = new Resvg(svg, defu(
|
|
@@ -29,7 +29,7 @@ async function createPng(event) {
|
|
|
29
29
|
return pngData.asPng();
|
|
30
30
|
}
|
|
31
31
|
async function createJpeg(event) {
|
|
32
|
-
const { sharpOptions } =
|
|
32
|
+
const { sharpOptions } = useOgImageRuntimeConfig();
|
|
33
33
|
const png = await createPng(event);
|
|
34
34
|
const sharp = await useSharp();
|
|
35
35
|
return sharp(png, defu(event.options.sharp, sharpOptions)).jpeg().toBuffer();
|
|
@@ -6,9 +6,10 @@ import { hash } from "ohash";
|
|
|
6
6
|
import { fetchPathHtmlAndExtractOptions } from "../options/fetch.mjs";
|
|
7
7
|
import { prerenderCache } from "../cache/prerender.mjs";
|
|
8
8
|
import { useChromiumRenderer, useSatoriRenderer } from "../renderers/satori/instances.mjs";
|
|
9
|
+
import { separateProps, useOgImageRuntimeConfig } from "../../utils.mjs";
|
|
9
10
|
import { useRuntimeConfig, useSiteConfig } from "#imports";
|
|
10
11
|
export async function resolveRendererContext(e) {
|
|
11
|
-
const runtimeConfig =
|
|
12
|
+
const runtimeConfig = useOgImageRuntimeConfig();
|
|
12
13
|
const path = parseURL(e.path).pathname;
|
|
13
14
|
const extension = path.split(".").pop();
|
|
14
15
|
if (!extension) {
|
|
@@ -26,7 +27,9 @@ export async function resolveRendererContext(e) {
|
|
|
26
27
|
const basePath = withoutTrailingSlash(
|
|
27
28
|
path.replace(`/__og-image__/image`, "").replace(`/og.${extension}`, "")
|
|
28
29
|
);
|
|
29
|
-
|
|
30
|
+
let queryParams = { ...getQuery(e) };
|
|
31
|
+
queryParams.props = JSON.parse(queryParams.props || "{}");
|
|
32
|
+
queryParams = separateProps(queryParams);
|
|
30
33
|
const isDebugJsonPayload = extension === "json" && runtimeConfig.debug;
|
|
31
34
|
const siteConfig = useSiteConfig(e);
|
|
32
35
|
const key = [
|
|
@@ -55,7 +58,14 @@ export async function resolveRendererContext(e) {
|
|
|
55
58
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
|
|
56
59
|
withoutBase(basePath.split("?")[0], useRuntimeConfig().app.baseURL)
|
|
57
60
|
).reverse());
|
|
58
|
-
|
|
61
|
+
if (typeof routeRules.ogImage === "undefined" && !options) {
|
|
62
|
+
return createError({
|
|
63
|
+
statusCode: 400,
|
|
64
|
+
statusMessage: "The route is missing the Nuxt OG Image payload or route rules."
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const ogImageRouteRules = separateProps(routeRules.ogImage);
|
|
68
|
+
options = defu(queryParams, ogImageRouteRules, options, runtimeConfig.defaults);
|
|
59
69
|
if (!options) {
|
|
60
70
|
return createError({
|
|
61
71
|
statusCode: 404,
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
2
2
|
import { defu } from "defu";
|
|
3
|
-
import { getOgImagePath } from "../../utils.mjs";
|
|
4
|
-
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { getOgImagePath, useOgImageRuntimeConfig } from "../../utils.mjs";
|
|
5
4
|
export default defineNitroPlugin((nitroApp) => {
|
|
6
5
|
nitroApp.hooks.hook("content:file:afterParse", async (content) => {
|
|
7
6
|
if (content._draft || content._extension !== "md" || content._partial || content.indexable === false || content.index === false)
|
|
8
7
|
return;
|
|
9
8
|
if (content.path && content.ogImage) {
|
|
10
9
|
const ogImageConfig = typeof content.ogImage === "object" ? content.ogImage : {};
|
|
11
|
-
const { defaults } =
|
|
10
|
+
const { defaults } = useOgImageRuntimeConfig();
|
|
12
11
|
const optionsWithDefault = defu(ogImageConfig, defaults);
|
|
13
12
|
const src = getOgImagePath(content.path, optionsWithDefault);
|
|
14
13
|
const payload = {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("nitropack
|
|
1
|
+
declare const _default: import("nitropack").NitroAppPlugin;
|
|
2
2
|
export default _default;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { parseURL, withoutLeadingSlash } from "ufo";
|
|
2
|
-
import { getRouteRules } from "nitropack/dist/runtime/route-rules";
|
|
1
|
+
import { parseURL, withoutBase, withoutLeadingSlash } from "ufo";
|
|
3
2
|
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
3
|
+
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
4
|
+
import { defu } from "defu";
|
|
4
5
|
import { extractAndNormaliseOgImageOptions } from "../../core/options/extract.mjs";
|
|
5
6
|
import { prerenderCache, prerenderChromiumContext } from "../../core/cache/prerender.mjs";
|
|
6
|
-
import { isInternalRoute } from "../../utils.mjs";
|
|
7
|
+
import { isInternalRoute } from "../../utils.pure.mjs";
|
|
8
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
9
|
export default defineNitroPlugin(async (nitro) => {
|
|
8
10
|
if (!import.meta.prerender)
|
|
9
11
|
return;
|
|
@@ -11,7 +13,13 @@ export default defineNitroPlugin(async (nitro) => {
|
|
|
11
13
|
const path = parseURL(e.event.path).pathname;
|
|
12
14
|
if (isInternalRoute(path))
|
|
13
15
|
return;
|
|
14
|
-
const
|
|
16
|
+
const runtimeConfig = useRuntimeConfig();
|
|
17
|
+
const _routeRulesMatcher = toRouteMatcher(
|
|
18
|
+
createRadixRouter({ routes: runtimeConfig.nitro?.routeRules })
|
|
19
|
+
);
|
|
20
|
+
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
|
|
21
|
+
withoutBase(path.split("?")[0], runtimeConfig.app.baseURL)
|
|
22
|
+
).reverse()).ogImage;
|
|
15
23
|
if (routeRules === false)
|
|
16
24
|
return;
|
|
17
25
|
const options = extractAndNormaliseOgImageOptions([
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseURL } from "ufo";
|
|
2
|
+
import { toValue } from "vue";
|
|
3
|
+
import { isInternalRoute } from "../../utils.pure.mjs";
|
|
4
|
+
import { defineNuxtPlugin, useRequestEvent, withSiteUrl } from "#imports";
|
|
5
|
+
export default defineNuxtPlugin({
|
|
6
|
+
setup(nuxtApp) {
|
|
7
|
+
nuxtApp.hooks.hook("app:rendered", async (ctx) => {
|
|
8
|
+
const { ssrContext } = ctx;
|
|
9
|
+
const e = useRequestEvent();
|
|
10
|
+
const path = parseURL(e.path).pathname;
|
|
11
|
+
if (isInternalRoute(path))
|
|
12
|
+
return;
|
|
13
|
+
ssrContext?.head.use({
|
|
14
|
+
key: "nuxt-og-image:canonical-urls",
|
|
15
|
+
hooks: {
|
|
16
|
+
"tags:resolve": async ({ tags }) => {
|
|
17
|
+
for (const tag of tags) {
|
|
18
|
+
if (tag.tag === "meta" && (tag.props.property === "og:image" || tag.props.name === "twitter:image:src")) {
|
|
19
|
+
if (!tag.props.content.startsWith("https")) {
|
|
20
|
+
await nuxtApp.runWithContext(() => {
|
|
21
|
+
tag.props.content = toValue(withSiteUrl(tag.props.content));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -2,9 +2,9 @@ import { defu } from "defu";
|
|
|
2
2
|
import { parseURL, withoutBase } from "ufo";
|
|
3
3
|
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
4
4
|
import { normaliseOptions } from "../../core/options/normalise.mjs";
|
|
5
|
-
import { getOgImagePath, isInternalRoute } from "../../utils.mjs";
|
|
5
|
+
import { getOgImagePath, isInternalRoute, useOgImageRuntimeConfig } from "../../utils.mjs";
|
|
6
6
|
import { createOgImageMeta } from "../utils.mjs";
|
|
7
|
-
import { defineNuxtPlugin, useRequestEvent
|
|
7
|
+
import { defineNuxtPlugin, useRequestEvent } from "#imports";
|
|
8
8
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
9
9
|
nuxtApp.hooks.hook("app:rendered", async (ctx) => {
|
|
10
10
|
const { ssrContext } = ctx;
|
|
@@ -25,15 +25,14 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
25
25
|
ogImageInstances?.forEach((e2) => {
|
|
26
26
|
e2.dispose();
|
|
27
27
|
});
|
|
28
|
+
nuxtApp.ssrContext._ogImagePayload = void 0;
|
|
28
29
|
nuxtApp.ssrContext._ogImageInstances = void 0;
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
|
-
if (ogImageInstances.length >= 0)
|
|
32
|
-
return;
|
|
33
32
|
routeRules = defu(nuxtApp.ssrContext?.event.context._nitro?.routeRules?.ogImage, routeRules);
|
|
34
|
-
const { defaults } =
|
|
33
|
+
const { defaults } = useOgImageRuntimeConfig();
|
|
35
34
|
const resolvedOptions = normaliseOptions(defu(routeRules, defaults));
|
|
36
35
|
const src = getOgImagePath(ssrContext.url, resolvedOptions);
|
|
37
|
-
createOgImageMeta(src,
|
|
36
|
+
createOgImageMeta(src, routeRules, resolvedOptions, nuxtApp.ssrContext);
|
|
38
37
|
});
|
|
39
38
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import { separateProps } from "../utils.mjs";
|
|
1
3
|
import { useServerHead } from "#imports";
|
|
2
4
|
export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
|
|
5
|
+
const _input = separateProps(defu(input, ssrContext._ogImagePayload));
|
|
3
6
|
const url = src || input.url || resolvedOptions.url;
|
|
4
7
|
let urlExtension = (url.split("/").pop() || url).split(".").pop() || resolvedOptions.extension;
|
|
5
8
|
if (urlExtension === "jpg")
|
|
@@ -30,16 +33,10 @@ export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
|
|
|
30
33
|
type: "application/json",
|
|
31
34
|
processTemplateParams: true,
|
|
32
35
|
innerHTML: () => {
|
|
33
|
-
|
|
34
|
-
title
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Object.entries({ ...input, url: void 0 }).forEach(([key, val]) => {
|
|
38
|
-
if (typeof val !== "undefined") {
|
|
39
|
-
payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
return payload;
|
|
36
|
+
if (!_input.props.title)
|
|
37
|
+
_input.props.title = "%s";
|
|
38
|
+
delete _input.url;
|
|
39
|
+
return _input;
|
|
43
40
|
},
|
|
44
41
|
// we want this to be last in our head
|
|
45
42
|
tagPosition: "bodyClose"
|
|
@@ -51,5 +48,6 @@ export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
|
|
|
51
48
|
}, {
|
|
52
49
|
tagPriority: 35
|
|
53
50
|
});
|
|
51
|
+
ssrContext._ogImagePayload = _input;
|
|
54
52
|
ssrContext._ogImageInstances.push(instance);
|
|
55
53
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineEventHandler, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { useOgImageRuntimeConfig } from "../../../utils.mjs";
|
|
3
|
+
import { useSiteConfig } from "#imports";
|
|
3
4
|
import { componentNames } from "#nuxt-og-image/component-names.mjs";
|
|
4
5
|
export default defineEventHandler(async (e) => {
|
|
5
6
|
setHeader(e, "Content-Type", "application/json");
|
|
6
|
-
const runtimeConfig =
|
|
7
|
+
const runtimeConfig = useOgImageRuntimeConfig();
|
|
7
8
|
const siteConfig = await useSiteConfig(e, { debug: true });
|
|
8
9
|
return {
|
|
9
10
|
siteConfigUrl: {
|
|
@@ -4,13 +4,14 @@ import { fetchIsland } from "../../../core/html/fetchIsland.mjs";
|
|
|
4
4
|
import { devIframeTemplate } from "../../../core/html/devIframeTemplate.mjs";
|
|
5
5
|
import { applyInlineCss } from "../../../core/html/applyInlineCss.mjs";
|
|
6
6
|
import { useOgImageBufferCache } from "../../../cache.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { useOgImageRuntimeConfig } from "../../../utils.mjs";
|
|
8
|
+
import { useSiteConfig } from "#imports";
|
|
8
9
|
export default defineEventHandler(async (e) => {
|
|
9
10
|
const ctx = await resolveRendererContext(e);
|
|
10
11
|
if (ctx instanceof H3Error)
|
|
11
12
|
return ctx;
|
|
12
13
|
const { isDebugJsonPayload, extension, options, renderer } = ctx;
|
|
13
|
-
const { debug, baseCacheKey } =
|
|
14
|
+
const { debug, baseCacheKey } = useOgImageRuntimeConfig();
|
|
14
15
|
const compatibility = [];
|
|
15
16
|
if (isDebugJsonPayload) {
|
|
16
17
|
const queryExtension = getQuery(e).extension || ctx.options.extension;
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { H3Error, H3Event } from 'h3';
|
|
|
3
3
|
import type { ResvgRenderOptions } from '@resvg/resvg-js';
|
|
4
4
|
import type { SatoriOptions } from 'satori';
|
|
5
5
|
import type { AllowedComponentProps, Component, ComponentCustomProps, VNodeProps } from '@vue/runtime-core';
|
|
6
|
+
import type { SharpOptions } from 'sharp';
|
|
6
7
|
import type { OgImageComponents } from '#nuxt-og-image/components';
|
|
7
8
|
export interface H3EventOgImageRender {
|
|
8
9
|
e: H3Event;
|
|
@@ -14,12 +15,26 @@ export interface H3EventOgImageRender {
|
|
|
14
15
|
isDebugJsonPayload: boolean;
|
|
15
16
|
}
|
|
16
17
|
export type IconifyEmojiIconSets = 'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'streamline-emojis' | 'openmoji';
|
|
18
|
+
export interface OgImageRuntimeConfig {
|
|
19
|
+
version: string;
|
|
20
|
+
satoriOptions: SatoriOptions;
|
|
21
|
+
resvgOptions: ResvgRenderOptions;
|
|
22
|
+
sharpOptions: SharpOptions;
|
|
23
|
+
runtimeSatori: boolean;
|
|
24
|
+
runtimeChromium: boolean;
|
|
25
|
+
defaults: OgImageOptions;
|
|
26
|
+
debug: boolean;
|
|
27
|
+
baseCacheKey: string;
|
|
28
|
+
fonts: FontConfig[];
|
|
29
|
+
hasNuxtIcon: boolean;
|
|
30
|
+
colorPreference: 'light' | 'dark';
|
|
31
|
+
}
|
|
17
32
|
export interface OgImageComponent {
|
|
18
33
|
path?: string;
|
|
19
34
|
pascalName: string;
|
|
20
35
|
kebabName: string;
|
|
21
36
|
hash: string;
|
|
22
|
-
category: 'app' | '
|
|
37
|
+
category: 'app' | 'community' | 'pro';
|
|
23
38
|
credits?: string;
|
|
24
39
|
}
|
|
25
40
|
export interface ScreenshotOptions {
|
|
@@ -47,7 +62,7 @@ export type OgImagePrebuilt = {
|
|
|
47
62
|
url: string;
|
|
48
63
|
} & Pick<OgImageOptions, 'width' | 'height' | 'alt'>;
|
|
49
64
|
export type DefineOgImageInput = OgImageOptions | OgImagePrebuilt | false;
|
|
50
|
-
export interface OgImageOptions<T extends keyof OgImageComponents = '
|
|
65
|
+
export interface OgImageOptions<T extends keyof OgImageComponents = 'NuxtSeo'> {
|
|
51
66
|
/**
|
|
52
67
|
* The width of the screenshot.
|
|
53
68
|
*
|
|
@@ -88,6 +103,7 @@ export interface OgImageOptions<T extends keyof OgImageComponents = 'Fallback'>
|
|
|
88
103
|
resvg?: ResvgRenderOptions;
|
|
89
104
|
satori?: SatoriOptions;
|
|
90
105
|
screenshot?: Partial<ScreenshotOptions>;
|
|
106
|
+
sharp?: SharpOptions;
|
|
91
107
|
cacheMaxAgeSeconds?: number;
|
|
92
108
|
}
|
|
93
109
|
export interface FontConfig {
|