nuxt-og-image 3.0.0-beta.12 → 3.0.0-beta.15
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/dist/client/200.html +5 -5
- package/dist/client/404.html +5 -5
- package/dist/client/_nuxt/{IconCSS.3f011790.js → IconCSS.3a98efc6.js} +1 -1
- package/dist/client/_nuxt/IconCSS.7e8f1f7b.css +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/9ab97583-6aef-4d38-9cfe-1a0bb2c6966a.json +1 -0
- package/dist/client/_nuxt/entry.3a009184.css +1 -0
- package/dist/client/_nuxt/entry.95bee607.js +137 -0
- package/dist/client/_nuxt/{error-404.0d8fb510.js → error-404.76a67ecd.js} +1 -1
- package/dist/client/_nuxt/{error-500.cefac4a7.js → error-500.b3ac537c.js} +1 -1
- package/dist/client/index.html +5 -5
- package/dist/module.d.mts +36 -19
- package/dist/module.d.ts +36 -19
- package/dist/module.json +2 -2
- package/dist/module.mjs +54 -41
- package/dist/runtime/cache.d.ts +7 -10
- package/dist/runtime/cache.mjs +38 -26
- package/dist/runtime/components/OgImage/OgImage.d.ts +5 -0
- package/dist/runtime/components/OgImage/{index.mjs → OgImage.mjs} +1 -1
- package/dist/runtime/components/OgImage/OgImageScreenshot.d.ts +5 -0
- package/dist/runtime/components/OgImage/{Screenshot.mjs → OgImageScreenshot.mjs} +1 -1
- package/dist/runtime/components/Templates/Official/SimpleBlog.vue +1 -0
- package/dist/runtime/composables/defineOgImage.d.ts +2 -23
- package/dist/runtime/composables/defineOgImage.mjs +32 -115
- package/dist/runtime/composables/defineOgImageComponent.d.ts +3 -0
- package/dist/runtime/composables/defineOgImageComponent.mjs +8 -0
- package/dist/runtime/composables/defineOgImagePageScreenshot.d.ts +2 -0
- package/dist/runtime/composables/defineOgImagePageScreenshot.mjs +14 -0
- package/dist/runtime/core/bindings/css-inline/node.d.ts +2 -5
- package/dist/runtime/core/bindings/css-inline/node.mjs +2 -10
- package/dist/runtime/core/cache/emojis.d.ts +1 -0
- package/dist/runtime/core/cache/emojis.mjs +5 -0
- package/dist/runtime/core/cache/htmlPayload.d.ts +5 -0
- package/dist/runtime/core/cache/htmlPayload.mjs +6 -0
- package/dist/runtime/core/cache/prerender.d.ts +1 -1
- package/dist/runtime/core/font/fetch.d.ts +2 -3
- package/dist/runtime/core/font/fetch.mjs +10 -4
- package/dist/runtime/core/html/applyEmojis.d.ts +3 -0
- package/dist/runtime/core/html/applyEmojis.mjs +37 -0
- package/dist/runtime/core/html/applyInlineCss.d.ts +3 -0
- package/dist/runtime/core/html/applyInlineCss.mjs +32 -0
- package/dist/runtime/core/html/devIframeTemplate.d.ts +2 -0
- package/dist/runtime/core/html/{fetch.mjs → devIframeTemplate.mjs} +8 -31
- package/dist/runtime/core/html/fetchIsland.d.ts +3 -0
- package/dist/runtime/core/html/fetchIsland.mjs +17 -0
- package/dist/runtime/core/options/fetch.d.ts +1 -1
- package/dist/runtime/core/options/fetch.mjs +10 -5
- package/dist/runtime/core/options/normalise.d.ts +2 -2
- package/dist/runtime/core/renderers/chromium/index.mjs +6 -7
- package/dist/runtime/core/renderers/chromium/screenshot.d.ts +2 -3
- package/dist/runtime/core/renderers/chromium/screenshot.mjs +5 -4
- package/dist/runtime/core/renderers/satori/fonts.d.ts +2 -2
- package/dist/runtime/core/renderers/satori/fonts.mjs +2 -2
- package/dist/runtime/core/renderers/satori/index.d.ts +2 -3
- package/dist/runtime/core/renderers/satori/index.mjs +21 -18
- package/dist/runtime/core/renderers/satori/instances.d.ts +3 -0
- package/dist/runtime/core/renderers/satori/instances.mjs +15 -0
- package/dist/runtime/core/renderers/satori/plugins/emojis.mjs +15 -13
- package/dist/runtime/core/renderers/satori/plugins/imageSrc.mjs +8 -4
- package/dist/runtime/core/renderers/satori/utils.d.ts +2 -3
- package/dist/runtime/core/renderers/satori/vnodes.d.ts +2 -3
- package/dist/runtime/core/renderers/satori/vnodes.mjs +14 -6
- package/dist/runtime/core/utils/resolveRendererContext.d.ts +2 -6
- package/dist/runtime/core/utils/resolveRendererContext.mjs +34 -21
- package/dist/runtime/nitro/plugins/nuxt-content.mjs +2 -2
- package/dist/runtime/nitro/plugins/prerender.mjs +2 -2
- package/dist/runtime/nuxt/plugins/nuxt-content-canonical-urls.mjs +1 -1
- package/dist/runtime/nuxt/plugins/route-rule-og-image.server.mjs +14 -47
- package/dist/runtime/nuxt/utils.d.ts +2 -0
- package/dist/runtime/nuxt/utils.mjs +55 -0
- package/dist/runtime/server/routes/__og-image__/debug.json.d.ts +0 -2
- package/dist/runtime/server/routes/__og-image__/debug.json.mjs +2 -7
- package/dist/runtime/server/routes/__og-image__/image.mjs +86 -0
- package/dist/runtime/types.d.ts +57 -24
- package/dist/runtime/utils.d.ts +3 -0
- package/dist/runtime/utils.mjs +11 -0
- package/package.json +17 -32
- package/virtual.d.ts +49 -0
- package/dist/client/_nuxt/IconCSS.8f429b14.css +0 -1
- package/dist/client/_nuxt/builds/meta/f929037e-e674-4060-8070-9dfb30335a28.json +0 -1
- package/dist/client/_nuxt/entry.434c2c45.css +0 -1
- package/dist/client/_nuxt/entry.6c0001e5.js +0 -137
- package/dist/client/grid.png +0 -0
- package/dist/runtime/components/OgImage/Cached.d.ts +0 -5
- package/dist/runtime/components/OgImage/Cached.mjs +0 -10
- package/dist/runtime/components/OgImage/Dynamic.d.ts +0 -8
- package/dist/runtime/components/OgImage/Dynamic.mjs +0 -10
- package/dist/runtime/components/OgImage/Screenshot.d.ts +0 -6
- package/dist/runtime/components/OgImage/Static.d.ts +0 -8
- package/dist/runtime/components/OgImage/Static.mjs +0 -10
- package/dist/runtime/components/OgImage/WithoutCache.d.ts +0 -5
- package/dist/runtime/components/OgImage/WithoutCache.mjs +0 -10
- package/dist/runtime/components/OgImage/index.d.ts +0 -5
- package/dist/runtime/core/bindings/css-inline/mock.d.ts +0 -5
- package/dist/runtime/core/bindings/css-inline/mock.mjs +0 -3
- package/dist/runtime/core/html/fetch.d.ts +0 -3
- package/dist/runtime/server/routes/__og-image__/image-[path]-og.[extension].mjs +0 -45
- package/dist/runtime/utilts.d.ts +0 -2
- package/dist/runtime/utilts.mjs +0 -8
- /package/dist/runtime/server/routes/__og-image__/{image-[path]-og.[extension].d.ts → image.d.ts} +0 -0
|
@@ -3,11 +3,12 @@ import { createVNodes } from "./vnodes.mjs";
|
|
|
3
3
|
import { loadFonts, satoriFonts } from "./fonts.mjs";
|
|
4
4
|
import { useResvg, useSatori, useSharp } from "./instances.mjs";
|
|
5
5
|
import { useRuntimeConfig } from "#imports";
|
|
6
|
-
export async function createSvg(
|
|
6
|
+
export async function createSvg(event) {
|
|
7
|
+
const { options } = event;
|
|
7
8
|
const { fonts, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
|
|
8
|
-
const vnodes = await createVNodes(
|
|
9
|
+
const vnodes = await createVNodes(event);
|
|
9
10
|
if (!satoriFonts.length)
|
|
10
|
-
satoriFonts.push(...await loadFonts(
|
|
11
|
+
satoriFonts.push(...await loadFonts(event, fonts));
|
|
11
12
|
const satori = await useSatori();
|
|
12
13
|
return satori(vnodes, defu(options.satori, satoriOptions, {
|
|
13
14
|
fonts: satoriFonts,
|
|
@@ -16,38 +17,40 @@ export async function createSvg(e, options) {
|
|
|
16
17
|
height: options.height
|
|
17
18
|
}));
|
|
18
19
|
}
|
|
19
|
-
async function createPng(
|
|
20
|
+
async function createPng(event) {
|
|
20
21
|
const { resvgOptions } = useRuntimeConfig()["nuxt-og-image"];
|
|
21
|
-
const svg = await createSvg(
|
|
22
|
+
const svg = await createSvg(event);
|
|
22
23
|
const Resvg = await useResvg();
|
|
23
24
|
const resvg = new Resvg(svg, defu(
|
|
24
|
-
options.resvg,
|
|
25
|
+
event.options.resvg,
|
|
25
26
|
resvgOptions
|
|
26
27
|
));
|
|
27
28
|
const pngData = resvg.render();
|
|
28
29
|
return pngData.asPng();
|
|
29
30
|
}
|
|
30
|
-
async function createJpeg(
|
|
31
|
+
async function createJpeg(event) {
|
|
31
32
|
const { sharpOptions } = useRuntimeConfig()["nuxt-og-image"];
|
|
32
|
-
const png = await createPng(
|
|
33
|
+
const png = await createPng(event);
|
|
33
34
|
const sharp = await useSharp();
|
|
34
|
-
return sharp(png, defu(options.sharp, sharpOptions)).jpeg(
|
|
35
|
+
return sharp(png, defu(event.options.sharp, sharpOptions)).jpeg().toBuffer();
|
|
35
36
|
}
|
|
36
37
|
const SatoriRenderer = {
|
|
37
38
|
name: "satori",
|
|
38
|
-
supportedFormats: ["
|
|
39
|
-
async createImage(e
|
|
40
|
-
switch (
|
|
41
|
-
case "json":
|
|
42
|
-
return createVNodes(e, options);
|
|
43
|
-
case "svg":
|
|
44
|
-
return createSvg(e, options);
|
|
39
|
+
supportedFormats: ["png", "jpeg", "jpg", "json"],
|
|
40
|
+
async createImage(e) {
|
|
41
|
+
switch (e.extension) {
|
|
45
42
|
case "png":
|
|
46
|
-
return createPng(e
|
|
43
|
+
return createPng(e);
|
|
47
44
|
case "jpeg":
|
|
48
45
|
case "jpg":
|
|
49
|
-
return createJpeg(e
|
|
46
|
+
return createJpeg(e);
|
|
50
47
|
}
|
|
48
|
+
},
|
|
49
|
+
async debug(e) {
|
|
50
|
+
return {
|
|
51
|
+
vnodes: await createVNodes(e),
|
|
52
|
+
svg: await createSvg(e)
|
|
53
|
+
};
|
|
51
54
|
}
|
|
52
55
|
};
|
|
53
56
|
export default SatoriRenderer;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type _satori from 'satori';
|
|
2
|
+
export declare function useSatoriRenderer(): Promise<import("../../../types").Renderer>;
|
|
3
|
+
export declare function useChromiumRenderer(): Promise<import("../../../types").Renderer>;
|
|
2
4
|
export declare function useResvg(): Promise<new (svg: string | Uint8Array, options?: import("@resvg/resvg-wasm").ResvgRenderOptions | undefined) => {
|
|
3
5
|
free(): void;
|
|
4
6
|
render(): {
|
|
@@ -37,3 +39,4 @@ export declare function useResvg(): Promise<new (svg: string | Uint8Array, optio
|
|
|
37
39
|
}>;
|
|
38
40
|
export declare function useSatori(): Promise<typeof _satori>;
|
|
39
41
|
export declare function useSharp(): Promise<any>;
|
|
42
|
+
export declare function useCssInline(): Promise<any>;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
const cssInlineInstance = { instance: void 0 };
|
|
1
2
|
const sharpInstance = { instance: void 0 };
|
|
2
3
|
const resvgInstance = { instance: void 0 };
|
|
3
4
|
const satoriInstance = { instance: void 0 };
|
|
5
|
+
const satoriRendererInstance = { instance: void 0 };
|
|
6
|
+
const chromiumRendererInstance = { instance: void 0 };
|
|
7
|
+
export async function useSatoriRenderer() {
|
|
8
|
+
satoriRendererInstance.instance = satoriRendererInstance.instance || await import("#nuxt-og-image/renderers/satori").then((m) => m.default);
|
|
9
|
+
return satoriRendererInstance.instance;
|
|
10
|
+
}
|
|
11
|
+
export async function useChromiumRenderer() {
|
|
12
|
+
chromiumRendererInstance.instance = chromiumRendererInstance.instance || await import("#nuxt-og-image/renderers/chromium").then((m) => m.default);
|
|
13
|
+
return chromiumRendererInstance.instance;
|
|
14
|
+
}
|
|
4
15
|
export async function useResvg() {
|
|
5
16
|
resvgInstance.instance = resvgInstance.instance || await import("#nuxt-og-image/bindings/resvg").then((m) => m.default);
|
|
6
17
|
await resvgInstance.instance.initWasmPromise;
|
|
@@ -15,3 +26,7 @@ export async function useSharp() {
|
|
|
15
26
|
sharpInstance.instance = sharpInstance.instance || await import("#nuxt-og-image/bindings/sharp").then((m) => m.default);
|
|
16
27
|
return sharpInstance.instance;
|
|
17
28
|
}
|
|
29
|
+
export async function useCssInline() {
|
|
30
|
+
cssInlineInstance.instance = cssInlineInstance.instance || await import("#nuxt-og-image/bindings/css-inline").then((m) => m.default);
|
|
31
|
+
return cssInlineInstance.instance;
|
|
32
|
+
}
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
2
|
function isEmojiFilter(node) {
|
|
3
|
-
return node.type === "
|
|
3
|
+
return node.type === "svg" && typeof node.props?.["data-emoji"] !== "undefined";
|
|
4
4
|
}
|
|
5
5
|
export default defineSatoriTransformer([
|
|
6
6
|
// need to make sure parent div has flex for the emoji to render inline
|
|
7
7
|
{
|
|
8
|
-
filter: (node) => node.type
|
|
8
|
+
filter: (node) => ["div", "p"].includes(node.type) && Array.isArray(node.props?.children) && node.props.children.some(isEmojiFilter),
|
|
9
9
|
transform: async (node) => {
|
|
10
10
|
node.props.style = node.props.style || {};
|
|
11
11
|
node.props.style.display = "flex";
|
|
12
12
|
node.props.style.alignItems = "center";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
node.props.children = node.props.children.map((child) => {
|
|
14
|
+
if (typeof child === "string") {
|
|
15
|
+
return {
|
|
16
|
+
type: "div",
|
|
17
|
+
props: {
|
|
18
|
+
children: child
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (child.props.class?.includes("emoji"))
|
|
23
|
+
delete child.props.class;
|
|
24
|
+
return child;
|
|
25
|
+
});
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
]);
|
|
@@ -6,8 +6,9 @@ import { toBase64Image } from "../../../env/assets.mjs";
|
|
|
6
6
|
import { useNitroOrigin } from "#imports";
|
|
7
7
|
export default defineSatoriTransformer({
|
|
8
8
|
filter: (node) => node.type === "img",
|
|
9
|
-
transform: async (node, e) => {
|
|
9
|
+
transform: async (node, { e }) => {
|
|
10
10
|
const src = node.props?.src;
|
|
11
|
+
const isRelative = src?.startsWith("/");
|
|
11
12
|
if (src) {
|
|
12
13
|
let updated = false;
|
|
13
14
|
let dimensions;
|
|
@@ -21,8 +22,11 @@ export default defineSatoriTransformer({
|
|
|
21
22
|
});
|
|
22
23
|
if (valid) {
|
|
23
24
|
node.props.src = toBase64Image(src, response);
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
try {
|
|
26
|
+
const imageSize = sizeOf(Buffer.from(response));
|
|
27
|
+
dimensions = { width: imageSize.width, height: imageSize.height };
|
|
28
|
+
} catch (e2) {
|
|
29
|
+
}
|
|
26
30
|
updated = true;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -37,7 +41,7 @@ export default defineSatoriTransformer({
|
|
|
37
41
|
node.props.height = dimensions.height;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
|
-
if (!updated) {
|
|
44
|
+
if (!updated && isRelative) {
|
|
41
45
|
node.props.src = `${withBase(src, `${useNitroOrigin(e)}`)}?${Date.now()}`;
|
|
42
46
|
}
|
|
43
47
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare function walkSatoriTree(e: H3Event, node: VNode, plugins: (SatoriTransformer | SatoriTransformer[])[]): Promise<void>;
|
|
1
|
+
import type { H3EventOgImageRender, SatoriTransformer, VNode } from '../../../types';
|
|
2
|
+
export declare function walkSatoriTree(e: H3EventOgImageRender, node: VNode, plugins: (SatoriTransformer | SatoriTransformer[])[]): Promise<void>;
|
|
4
3
|
export declare function defineSatoriTransformer(transformer: SatoriTransformer | SatoriTransformer[]): SatoriTransformer | SatoriTransformer[];
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export declare function createVNodes(e: H3Event, options: RendererOptions): Promise<VNode>;
|
|
1
|
+
import type { H3EventOgImageRender, VNode } from '../../../types';
|
|
2
|
+
export declare function createVNodes(ctx: H3EventOgImageRender): Promise<VNode>;
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { html as convertHtmlToSatori } from "satori-html";
|
|
2
|
-
import {
|
|
2
|
+
import { fetchIsland } from "../../html/fetchIsland.mjs";
|
|
3
|
+
import { applyInlineCss } from "../../html/applyInlineCss.mjs";
|
|
4
|
+
import { applyEmojis } from "../../html/applyEmojis.mjs";
|
|
3
5
|
import { walkSatoriTree } from "./utils.mjs";
|
|
4
6
|
import emojis from "./plugins/emojis.mjs";
|
|
5
7
|
import twClasses from "./plugins/twClasses.mjs";
|
|
6
8
|
import imageSrc from "./plugins/imageSrc.mjs";
|
|
7
9
|
import flex from "./plugins/flex.mjs";
|
|
8
10
|
import encoding from "./plugins/encoding.mjs";
|
|
9
|
-
export async function createVNodes(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export async function createVNodes(ctx) {
|
|
12
|
+
let html = ctx.options.html;
|
|
13
|
+
if (!html) {
|
|
14
|
+
const island = await fetchIsland(ctx);
|
|
15
|
+
await applyInlineCss(ctx, island);
|
|
16
|
+
await applyEmojis(ctx, island);
|
|
17
|
+
html = island.html;
|
|
18
|
+
}
|
|
19
|
+
const template = `<div data-v-inspector-ignore="true" style="position: relative; display: flex; margin: 0 auto; width: ${ctx.options.width}px; height: ${ctx.options.height}px; overflow: hidden;">${html}</div>`;
|
|
20
|
+
const satoriTree = convertHtmlToSatori(template);
|
|
21
|
+
await walkSatoriTree(ctx, satoriTree, [
|
|
14
22
|
emojis,
|
|
15
23
|
twClasses,
|
|
16
24
|
imageSrc,
|
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
import type { H3Error, H3Event } from 'h3';
|
|
2
|
-
import type {
|
|
3
|
-
export declare function resolveRendererContext(e: H3Event): Promise<H3Error |
|
|
4
|
-
extension: RuntimeOgImageOptions['extension'];
|
|
5
|
-
renderer: Renderer;
|
|
6
|
-
options: RuntimeOgImageOptions;
|
|
7
|
-
}>;
|
|
2
|
+
import type { H3EventOgImageRender } from '../../types';
|
|
3
|
+
export declare function resolveRendererContext(e: H3Event): Promise<H3Error | H3EventOgImageRender>;
|
|
@@ -2,11 +2,11 @@ import { parseURL, withoutBase, withoutLeadingSlash, withoutTrailingSlash } from
|
|
|
2
2
|
import { createError, getQuery } from "h3";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
5
|
+
import { hash } from "ohash";
|
|
5
6
|
import { fetchPathHtmlAndExtractOptions } from "../options/fetch.mjs";
|
|
6
7
|
import { prerenderCache } from "../cache/prerender.mjs";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const chromiumRendererInstance = { instance: void 0 };
|
|
8
|
+
import { useChromiumRenderer, useSatoriRenderer } from "../renderers/satori/instances.mjs";
|
|
9
|
+
import { useRuntimeConfig, useSiteConfig } from "#imports";
|
|
10
10
|
export async function resolveRendererContext(e) {
|
|
11
11
|
const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
|
|
12
12
|
const path = parseURL(e.path).pathname;
|
|
@@ -17,22 +17,35 @@ export async function resolveRendererContext(e) {
|
|
|
17
17
|
statusMessage: `Missing OG Image type.`
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
if (!["png", "jpeg", "jpg", "svg", "html", "json"].includes(extension)) {
|
|
21
|
+
return createError({
|
|
22
|
+
statusCode: 400,
|
|
23
|
+
statusMessage: `Unknown OG Image type ${extension}.`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
20
26
|
const basePath = withoutTrailingSlash(
|
|
21
|
-
path.replace(
|
|
27
|
+
path.replace(`/__og-image__/image`, "").replace(`/og.${extension}`, "")
|
|
22
28
|
);
|
|
23
29
|
const queryParams = { ...getQuery(e) };
|
|
30
|
+
const isDebugJsonPayload = extension === "json" && runtimeConfig.debug;
|
|
31
|
+
const siteConfig = useSiteConfig(e);
|
|
32
|
+
const key = [
|
|
33
|
+
withoutLeadingSlash(basePath === "/" || !basePath ? "index" : basePath).replaceAll("/", "-"),
|
|
34
|
+
hash([
|
|
35
|
+
basePath,
|
|
36
|
+
siteConfig.url,
|
|
37
|
+
hash(queryParams)
|
|
38
|
+
])
|
|
39
|
+
].join(":");
|
|
24
40
|
let options = queryParams.options;
|
|
25
41
|
if (!options) {
|
|
26
42
|
if (import.meta.prerender) {
|
|
27
|
-
const key = [
|
|
28
|
-
withoutLeadingSlash(basePath === "/" || !basePath ? "index" : basePath).replaceAll("/", "-")
|
|
29
|
-
].join(":");
|
|
30
43
|
options = await prerenderCache?.getItem(key);
|
|
31
44
|
} else {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
35
|
-
options =
|
|
45
|
+
const payload = await fetchPathHtmlAndExtractOptions(e, basePath, key);
|
|
46
|
+
if (payload instanceof Error)
|
|
47
|
+
return payload;
|
|
48
|
+
options = payload;
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
delete queryParams.options;
|
|
@@ -41,8 +54,8 @@ export async function resolveRendererContext(e) {
|
|
|
41
54
|
);
|
|
42
55
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(
|
|
43
56
|
withoutBase(basePath.split("?")[0], useRuntimeConfig().app.baseURL)
|
|
44
|
-
).reverse())
|
|
45
|
-
options = defu(queryParams, routeRules, options, runtimeConfig.defaults);
|
|
57
|
+
).reverse());
|
|
58
|
+
options = defu(queryParams, routeRules.ogImage, options, runtimeConfig.defaults);
|
|
46
59
|
if (!options) {
|
|
47
60
|
return createError({
|
|
48
61
|
statusCode: 404,
|
|
@@ -52,25 +65,25 @@ export async function resolveRendererContext(e) {
|
|
|
52
65
|
let renderer;
|
|
53
66
|
switch (options.renderer) {
|
|
54
67
|
case "satori":
|
|
55
|
-
renderer =
|
|
68
|
+
renderer = await useSatoriRenderer();
|
|
56
69
|
break;
|
|
57
70
|
case "chromium":
|
|
58
|
-
renderer =
|
|
71
|
+
renderer = await useChromiumRenderer();
|
|
59
72
|
break;
|
|
60
73
|
}
|
|
61
|
-
if (!renderer) {
|
|
74
|
+
if (!renderer || renderer.__unenv__) {
|
|
62
75
|
throw createError({
|
|
63
76
|
statusCode: 400,
|
|
64
77
|
statusMessage: `Renderer ${options.renderer} is missing.`
|
|
65
78
|
});
|
|
66
79
|
}
|
|
67
80
|
return {
|
|
81
|
+
e,
|
|
82
|
+
key,
|
|
68
83
|
renderer,
|
|
84
|
+
isDebugJsonPayload,
|
|
69
85
|
extension,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
extension: extension === "json" ? options.extension : extension,
|
|
73
|
-
path: basePath
|
|
74
|
-
}
|
|
86
|
+
basePath,
|
|
87
|
+
options
|
|
75
88
|
};
|
|
76
89
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
2
2
|
import { defu } from "defu";
|
|
3
|
-
import { getOgImagePath } from "../../
|
|
3
|
+
import { getOgImagePath } from "../../utils.mjs";
|
|
4
4
|
import { useRuntimeConfig } from "#imports";
|
|
5
5
|
export default defineNitroPlugin((nitroApp) => {
|
|
6
6
|
nitroApp.hooks.hook("content:file:afterParse", async (content) => {
|
|
@@ -10,7 +10,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
10
10
|
const ogImageConfig = typeof content.ogImage === "object" ? content.ogImage : {};
|
|
11
11
|
const { defaults } = useRuntimeConfig()["nuxt-og-image"];
|
|
12
12
|
const optionsWithDefault = defu(ogImageConfig, defaults);
|
|
13
|
-
const src = getOgImagePath(content.path, optionsWithDefault
|
|
13
|
+
const src = getOgImagePath(content.path, optionsWithDefault);
|
|
14
14
|
const payload = {
|
|
15
15
|
title: content.title,
|
|
16
16
|
excerpt: content.description || content.excerpt,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parseURL, withoutLeadingSlash } from "ufo";
|
|
2
2
|
import { getRouteRules } from "nitropack/dist/runtime/route-rules";
|
|
3
|
+
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
3
4
|
import { extractAndNormaliseOgImageOptions } from "../../core/options/extract.mjs";
|
|
4
5
|
import { prerenderCache, prerenderChromiumContext } from "../../core/cache/prerender.mjs";
|
|
5
|
-
import { isInternalRoute } from "../../
|
|
6
|
-
import { defineNitroPlugin } from "nitropack/dist/runtime/plugin";
|
|
6
|
+
import { isInternalRoute } from "../../utils.mjs";
|
|
7
7
|
export default defineNitroPlugin(async (nitro) => {
|
|
8
8
|
if (!import.meta.prerender)
|
|
9
9
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseURL } from "ufo";
|
|
2
2
|
import { toValue } from "vue";
|
|
3
|
-
import { isInternalRoute } from "../../
|
|
3
|
+
import { isInternalRoute } from "../../utils.mjs";
|
|
4
4
|
import { useRequestEvent, withSiteUrl } from "#imports";
|
|
5
5
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
6
6
|
nuxtApp.hooks.hook("app:rendered", async (ctx) => {
|
|
@@ -2,7 +2,8 @@ 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 "../../
|
|
5
|
+
import { getOgImagePath, isInternalRoute } from "../../utils.mjs";
|
|
6
|
+
import { createOgImageMeta } from "../utils.mjs";
|
|
6
7
|
import { defineNuxtPlugin, useRequestEvent, useRuntimeConfig } from "#imports";
|
|
7
8
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
8
9
|
nuxtApp.hooks.hook("app:rendered", async (ctx) => {
|
|
@@ -14,59 +15,25 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
14
15
|
const _routeRulesMatcher = toRouteMatcher(
|
|
15
16
|
createRadixRouter({ routes: ssrContext?.runtimeConfig?.nitro?.routeRules })
|
|
16
17
|
);
|
|
17
|
-
|
|
18
|
+
let routeRules = defu({}, ..._routeRulesMatcher.matchAll(
|
|
18
19
|
withoutBase(path.split("?")[0], ssrContext?.runtimeConfig?.app.baseURL)
|
|
19
20
|
).reverse()).ogImage;
|
|
20
21
|
if (typeof routeRules === "undefined")
|
|
21
22
|
return;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const ogImageInstances = nuxtApp.ssrContext._ogImageInstances || [];
|
|
24
|
+
if (routeRules === false) {
|
|
25
|
+
ogImageInstances?.forEach((e2) => {
|
|
26
|
+
e2.dispose();
|
|
27
|
+
});
|
|
28
|
+
nuxtApp.ssrContext._ogImageInstances = void 0;
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
|
-
if (
|
|
31
|
+
if (ogImageInstances.length >= 0)
|
|
30
32
|
return;
|
|
31
|
-
|
|
33
|
+
routeRules = defu(nuxtApp.ssrContext?.event.context._nitro?.routeRules?.ogImage, routeRules);
|
|
32
34
|
const { defaults } = useRuntimeConfig()["nuxt-og-image"];
|
|
33
|
-
const
|
|
34
|
-
const src = getOgImagePath(ssrContext.url,
|
|
35
|
-
|
|
36
|
-
script: [
|
|
37
|
-
{
|
|
38
|
-
id: "nuxt-og-image-options",
|
|
39
|
-
type: "application/json",
|
|
40
|
-
processTemplateParams: true,
|
|
41
|
-
innerHTML: () => {
|
|
42
|
-
const payload = {
|
|
43
|
-
title: "%s"
|
|
44
|
-
};
|
|
45
|
-
Object.entries(options).forEach(([key, val]) => {
|
|
46
|
-
payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
|
|
47
|
-
});
|
|
48
|
-
return payload;
|
|
49
|
-
},
|
|
50
|
-
// we want this to be last in our head
|
|
51
|
-
tagPosition: "bodyClose"
|
|
52
|
-
}
|
|
53
|
-
],
|
|
54
|
-
meta: [
|
|
55
|
-
{ property: "og:image", content: src },
|
|
56
|
-
{ property: "og:image:width", content: optionsWithDefault.width },
|
|
57
|
-
{ property: "og:image:height", content: optionsWithDefault.height },
|
|
58
|
-
{ property: "og:image:type", content: `image/${optionsWithDefault.extension}` },
|
|
59
|
-
{ property: "og:image:alt", content: optionsWithDefault.alt },
|
|
60
|
-
// twitter
|
|
61
|
-
{ name: "twitter:card", content: "summary_large_image" },
|
|
62
|
-
{ name: "twitter:image:src", content: src },
|
|
63
|
-
{ name: "twitter:image:width", content: optionsWithDefault.width },
|
|
64
|
-
{ name: "twitter:image:height", content: optionsWithDefault.height },
|
|
65
|
-
{ name: "twitter:image:alt", content: optionsWithDefault.alt }
|
|
66
|
-
]
|
|
67
|
-
}, {
|
|
68
|
-
mode: "server",
|
|
69
|
-
tagPriority: 35
|
|
70
|
-
});
|
|
35
|
+
const resolvedOptions = normaliseOptions(defu(routeRules, defaults));
|
|
36
|
+
const src = getOgImagePath(ssrContext.url, resolvedOptions);
|
|
37
|
+
createOgImageMeta(src, {}, resolvedOptions, nuxtApp.ssrContext);
|
|
71
38
|
});
|
|
72
39
|
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useServerHead } from "#imports";
|
|
2
|
+
export function createOgImageMeta(src, input, resolvedOptions, ssrContext) {
|
|
3
|
+
const url = src || input.url || resolvedOptions.url;
|
|
4
|
+
let urlExtension = (url.split("/").pop() || url).split(".").pop() || resolvedOptions.extension;
|
|
5
|
+
if (urlExtension === "jpg")
|
|
6
|
+
urlExtension = "jpeg";
|
|
7
|
+
const meta = [
|
|
8
|
+
{ property: "og:image", content: url },
|
|
9
|
+
{ property: "og:image:type", content: `image/${urlExtension}` },
|
|
10
|
+
{ name: "twitter:card", content: "summary_large_image" },
|
|
11
|
+
{ name: "twitter:image:src", content: url }
|
|
12
|
+
];
|
|
13
|
+
if (resolvedOptions.width) {
|
|
14
|
+
meta.push({ property: "og:image:width", content: resolvedOptions.width });
|
|
15
|
+
meta.push({ name: "twitter:image:width", content: resolvedOptions.width });
|
|
16
|
+
}
|
|
17
|
+
if (resolvedOptions.height) {
|
|
18
|
+
meta.push({ property: "og:image:height", content: resolvedOptions.height });
|
|
19
|
+
meta.push({ name: "twitter:image:height", content: resolvedOptions.height });
|
|
20
|
+
}
|
|
21
|
+
if (resolvedOptions.alt) {
|
|
22
|
+
meta.push({ property: "og:image:alt", content: resolvedOptions.alt });
|
|
23
|
+
meta.push({ name: "twitter:image:alt", content: resolvedOptions.alt });
|
|
24
|
+
}
|
|
25
|
+
ssrContext._ogImageInstances = ssrContext._ogImageInstances || [];
|
|
26
|
+
const script = [];
|
|
27
|
+
if (src) {
|
|
28
|
+
script.push({
|
|
29
|
+
id: "nuxt-og-image-options",
|
|
30
|
+
type: "application/json",
|
|
31
|
+
processTemplateParams: true,
|
|
32
|
+
innerHTML: () => {
|
|
33
|
+
const payload = {
|
|
34
|
+
title: "%s"
|
|
35
|
+
};
|
|
36
|
+
delete input.url;
|
|
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;
|
|
43
|
+
},
|
|
44
|
+
// we want this to be last in our head
|
|
45
|
+
tagPosition: "bodyClose"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const instance = useServerHead({
|
|
49
|
+
script,
|
|
50
|
+
meta
|
|
51
|
+
}, {
|
|
52
|
+
tagPriority: 35
|
|
53
|
+
});
|
|
54
|
+
ssrContext._ogImageInstances.push(instance);
|
|
55
|
+
}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { defineEventHandler, setHeader } from "h3";
|
|
2
|
-
import {
|
|
3
|
-
import { useRuntimeConfig, useSiteConfig, useStorage } from "#imports";
|
|
2
|
+
import { useRuntimeConfig, useSiteConfig } from "#imports";
|
|
4
3
|
import { componentNames } from "#nuxt-og-image/component-names.mjs";
|
|
5
4
|
export default defineEventHandler(async (e) => {
|
|
6
5
|
setHeader(e, "Content-Type", "application/json");
|
|
7
6
|
const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
|
|
8
7
|
const siteConfig = await useSiteConfig(e, { debug: true });
|
|
9
|
-
const baseCacheKey = runtimeConfig.runtimeCacheStorage === "default" ? `/cache/nuxt-og-image@${runtimeConfig.version}` : `/nuxt-og-image@${runtimeConfig.version}`;
|
|
10
|
-
const cache = prefixStorage(useStorage(), `${baseCacheKey}/`);
|
|
11
8
|
return {
|
|
12
9
|
siteConfigUrl: {
|
|
13
10
|
value: siteConfig.url,
|
|
14
11
|
source: siteConfig._context.url || "unknown"
|
|
15
12
|
},
|
|
16
13
|
componentNames,
|
|
17
|
-
runtimeConfig
|
|
18
|
-
baseCacheKey,
|
|
19
|
-
cachedKeys: await cache.getKeys()
|
|
14
|
+
runtimeConfig
|
|
20
15
|
};
|
|
21
16
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { H3Error, createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { resolveRendererContext } from "../../../core/utils/resolveRendererContext.mjs";
|
|
3
|
+
import { fetchIsland } from "../../../core/html/fetchIsland.mjs";
|
|
4
|
+
import { devIframeTemplate } from "../../../core/html/devIframeTemplate.mjs";
|
|
5
|
+
import { applyInlineCss } from "../../../core/html/applyInlineCss.mjs";
|
|
6
|
+
import { useOgImageBufferCache } from "../../../cache.mjs";
|
|
7
|
+
import { useRuntimeConfig, useSiteConfig } from "#imports";
|
|
8
|
+
export default defineEventHandler(async (e) => {
|
|
9
|
+
const ctx = await resolveRendererContext(e);
|
|
10
|
+
if (ctx instanceof H3Error)
|
|
11
|
+
return ctx;
|
|
12
|
+
const { isDebugJsonPayload, extension, options, renderer } = ctx;
|
|
13
|
+
const { debug, baseCacheKey } = useRuntimeConfig()["nuxt-og-image"];
|
|
14
|
+
const compatibility = [];
|
|
15
|
+
if (isDebugJsonPayload) {
|
|
16
|
+
const queryExtension = getQuery(e).extension || ctx.options.extension;
|
|
17
|
+
if (["jpeg", "jpg"].includes(queryExtension) && options.renderer === "satori")
|
|
18
|
+
compatibility.push("Converting PNGs to JPEGs requires Sharp which only runs on Node based systems.");
|
|
19
|
+
if (options.renderer === "chromium")
|
|
20
|
+
compatibility.push("Using Chromium to generate images is only supported in Node based environments. It's recommended to only use this if you're prerendering");
|
|
21
|
+
if (await applyInlineCss(ctx, await fetchIsland(ctx)))
|
|
22
|
+
compatibility.push("Inlining CSS is only supported in Node based environments.");
|
|
23
|
+
setHeader(e, "Content-Type", "application/json");
|
|
24
|
+
return {
|
|
25
|
+
compatibility,
|
|
26
|
+
...ctx,
|
|
27
|
+
siteConfig: useSiteConfig(e),
|
|
28
|
+
...options.renderer === "satori" ? await renderer.debug(ctx) : void 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
switch (extension) {
|
|
32
|
+
case "html":
|
|
33
|
+
setHeader(e, "Content-Type", `text/html`);
|
|
34
|
+
return devIframeTemplate(ctx);
|
|
35
|
+
case "svg":
|
|
36
|
+
if (!debug && !import.meta.dev) {
|
|
37
|
+
return createError({
|
|
38
|
+
statusCode: 404
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (ctx.renderer.name !== "satori") {
|
|
42
|
+
return createError({
|
|
43
|
+
statusCode: 400,
|
|
44
|
+
statusMessage: `Generating ${extension}'s with ${renderer.name} is not supported.`
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
setHeader(e, "Content-Type", `image/svg+xml`);
|
|
48
|
+
return (await ctx.renderer.debug(ctx)).svg;
|
|
49
|
+
case "png":
|
|
50
|
+
case "jpeg":
|
|
51
|
+
case "jpg":
|
|
52
|
+
if (!renderer.supportedFormats.includes(extension)) {
|
|
53
|
+
return createError({
|
|
54
|
+
statusCode: 400,
|
|
55
|
+
statusMessage: `Generating ${extension}'s with ${renderer.name} is not supported.`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
setHeader(e, "Content-Type", `image/${extension === "jpg" ? "jpeg" : extension}`);
|
|
59
|
+
break;
|
|
60
|
+
default:
|
|
61
|
+
return createError({
|
|
62
|
+
statusCode: 400,
|
|
63
|
+
statusMessage: `Invalid request for og.${extension}.`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const cacheApi = await useOgImageBufferCache(ctx, {
|
|
67
|
+
cacheMaxAgeSeconds: ctx.options.cacheMaxAgeSeconds,
|
|
68
|
+
baseCacheKey
|
|
69
|
+
});
|
|
70
|
+
if (typeof cacheApi === "undefined")
|
|
71
|
+
return;
|
|
72
|
+
let image = cacheApi.cachedItem;
|
|
73
|
+
if (!image) {
|
|
74
|
+
image = await renderer.createImage(ctx);
|
|
75
|
+
if (image instanceof H3Error)
|
|
76
|
+
return image;
|
|
77
|
+
if (!image) {
|
|
78
|
+
return createError({
|
|
79
|
+
statusCode: 500,
|
|
80
|
+
statusMessage: `Failed to generate og.${extension}.`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
await cacheApi.update(image);
|
|
84
|
+
}
|
|
85
|
+
return image;
|
|
86
|
+
});
|