nuxt-og-image 1.0.0-beta.0 → 1.0.0-beta.10
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 +22 -27
- package/dist/client/200.html +2 -2
- package/dist/client/404.html +2 -2
- package/dist/client/_nuxt/{Icon.4a9650c6.js → Icon.a3c98859.js} +1 -1
- package/dist/client/_nuxt/entry.2a315a2c.js +4 -0
- package/dist/client/_nuxt/{entry.0827acf4.css → entry.dc5450bf.css} +1 -1
- package/dist/client/_nuxt/{error-404.556d8899.js → error-404.0c49154a.js} +1 -1
- package/dist/client/_nuxt/{error-500.70731718.js → error-500.f3eec0bb.js} +1 -1
- package/dist/client/_nuxt/{error-component.418ebd67.js → error-component.7c753c74.js} +2 -2
- package/dist/client/index.html +2 -2
- package/dist/module.d.ts +8 -8
- package/dist/module.json +1 -1
- package/dist/module.mjs +45 -29
- package/dist/runtime/components/{OgImageTemplate.island.vue → OgImageBasic.island.vue} +2 -2
- package/dist/runtime/components/OgImageDynamic.d.ts +2 -2
- package/dist/runtime/components/OgImageScreenshot.d.ts +2 -2
- package/dist/runtime/components/OgImageStatic.d.ts +2 -2
- package/dist/runtime/composables/defineOgImage.d.ts +5 -5
- package/dist/runtime/composables/defineOgImage.mjs +12 -6
- package/dist/runtime/nitro/providers/browser.mjs +7 -8
- package/dist/runtime/nitro/providers/{satori.d.ts → satori/index.d.ts} +1 -1
- package/dist/runtime/nitro/providers/satori/index.mjs +48 -0
- package/dist/runtime/nitro/providers/satori/plugins/emojis.d.ts +2 -0
- package/dist/runtime/nitro/providers/satori/plugins/emojis.mjs +13 -0
- package/dist/runtime/nitro/providers/satori/plugins/flex.d.ts +2 -0
- package/dist/runtime/nitro/providers/satori/plugins/flex.mjs +11 -0
- package/dist/runtime/nitro/providers/satori/plugins/imageSrc.d.ts +2 -0
- package/dist/runtime/nitro/providers/satori/plugins/imageSrc.mjs +24 -0
- package/dist/runtime/nitro/providers/satori/plugins/twClasses.d.ts +2 -0
- package/dist/runtime/nitro/providers/satori/plugins/twClasses.mjs +9 -0
- package/dist/runtime/nitro/providers/satori/utils.d.ts +10 -0
- package/dist/runtime/nitro/providers/satori/utils.mjs +33 -0
- package/dist/runtime/nitro/routes/__og_image__/html.mjs +42 -12
- package/dist/runtime/nitro/routes/__og_image__/index.mjs +4 -4
- package/dist/runtime/nitro/routes/__og_image__/og.png.mjs +8 -12
- package/dist/runtime/nitro/routes/__og_image__/options.d.ts +5 -0
- package/dist/runtime/nitro/routes/__og_image__/{payload.mjs → options.mjs} +21 -28
- package/dist/runtime/nitro/routes/__og_image__/svg.mjs +5 -5
- package/dist/runtime/nitro/routes/__og_image__/vnode.d.ts +2 -0
- package/dist/runtime/nitro/routes/__og_image__/vnode.mjs +16 -0
- package/dist/runtime/nitro/utils.d.ts +3 -3
- package/dist/runtime/nitro/utils.mjs +5 -6
- package/package.json +3 -2
- package/dist/client/_nuxt/entry.ce848650.js +0 -4
- package/dist/client/nuxt.svg +0 -3
- package/dist/runtime/nitro/providers/satori.mjs +0 -48
- package/dist/runtime/nitro/routes/__og_image__/payload.d.ts +0 -5
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { useNuxt, addTemplate, defineNuxtModule, createResolver, addServerHandler, addImports, addComponent } from '@nuxt/kit';
|
|
3
|
+
import { useNuxt, addTemplate, defineNuxtModule, createResolver, getNuxtVersion, useLogger, addServerHandler, addImports, addComponent } from '@nuxt/kit';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import defu from 'defu';
|
|
@@ -57,13 +57,8 @@ async function screenshot(browser, url, options) {
|
|
|
57
57
|
return await page.screenshot();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const PayloadScriptId = "nuxt-og-image-payload";
|
|
61
|
-
const Constants = {
|
|
62
|
-
PayloadScriptId
|
|
63
|
-
};
|
|
64
|
-
|
|
65
60
|
function exposeConfig(alias, filename, config) {
|
|
66
|
-
const exports = Object.entries(config).map(([k, v]) => `export const ${k} =
|
|
61
|
+
const exports = Object.entries(config).map(([k, v]) => `export const ${k} = ${JSON.stringify(v)}`).join("\n");
|
|
67
62
|
useNuxt().options.alias[alias] = addTemplate({
|
|
68
63
|
filename,
|
|
69
64
|
getContents: () => exports
|
|
@@ -72,6 +67,13 @@ function exposeConfig(alias, filename, config) {
|
|
|
72
67
|
nitroConfig.virtual[alias] = exports;
|
|
73
68
|
});
|
|
74
69
|
}
|
|
70
|
+
function extractOgImageOptions(html) {
|
|
71
|
+
const options = html.match(/<script id="nuxt-og-image-options" type="application\/json">(.+?)<\/script>/)?.[1];
|
|
72
|
+
return options ? JSON.parse(options) : false;
|
|
73
|
+
}
|
|
74
|
+
function stripOgImageOptions(html) {
|
|
75
|
+
return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
|
|
76
|
+
}
|
|
75
77
|
|
|
76
78
|
function setupPlaygroundRPC(nuxt, config) {
|
|
77
79
|
const serverFunctions = {
|
|
@@ -155,13 +157,6 @@ function getBodyJson(req) {
|
|
|
155
157
|
});
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
function extractOgPayload(html) {
|
|
159
|
-
const payload = html.match(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.+?)<\/script>`))?.[1];
|
|
160
|
-
if (payload) {
|
|
161
|
-
return JSON.parse(payload);
|
|
162
|
-
}
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
160
|
const PATH = "/__nuxt_og_image__";
|
|
166
161
|
const PATH_ENTRY = `${PATH}/entry`;
|
|
167
162
|
const PATH_PLAYGROUND = `${PATH}/client`;
|
|
@@ -179,8 +174,26 @@ const module = defineNuxtModule({
|
|
|
179
174
|
experimentalNitroBrowser: false,
|
|
180
175
|
forcePrerender: !nuxt.options.dev && nuxt.options._generate,
|
|
181
176
|
host: nuxt.options.runtimeConfig.public?.siteUrl,
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
defaults: {
|
|
178
|
+
component: "OgImageBasic",
|
|
179
|
+
width: 1200,
|
|
180
|
+
height: 630
|
|
181
|
+
},
|
|
182
|
+
satoriFonts: [
|
|
183
|
+
{
|
|
184
|
+
name: "Inter",
|
|
185
|
+
weight: 400,
|
|
186
|
+
style: "normal",
|
|
187
|
+
publicPath: "/inter-latin-ext-400-normal.woff"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "Inter",
|
|
191
|
+
weight: 700,
|
|
192
|
+
style: "normal",
|
|
193
|
+
publicPath: "/inter-latin-ext-700-normal.woff"
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
satoriOptions: {}
|
|
184
197
|
};
|
|
185
198
|
},
|
|
186
199
|
async setup(config, nuxt) {
|
|
@@ -193,6 +206,10 @@ const module = defineNuxtModule({
|
|
|
193
206
|
};
|
|
194
207
|
nuxt.options.experimental.componentIslands = true;
|
|
195
208
|
const isEdge = (process.env.NITRO_PRESET || "").includes("edge");
|
|
209
|
+
const hasIslandSupport = getNuxtVersion(nuxt) !== "3.0.0";
|
|
210
|
+
const logger = useLogger("nuxt-og-image");
|
|
211
|
+
if (!hasIslandSupport)
|
|
212
|
+
logger.warn("You are using Nuxt 3.0.0 with `nuxt-og-image`, which only supports screenshots.\nPlease upgrade to Nuxt 3.0.1 or the edge channel: https://nuxt.com/docs/guide/going-further/edge-channel.");
|
|
196
213
|
addTemplate({
|
|
197
214
|
filename: "nuxt-og-image.d.ts",
|
|
198
215
|
getContents: () => {
|
|
@@ -211,7 +228,7 @@ export {}
|
|
|
211
228
|
nuxt.hooks.hook("prepare:types", ({ references }) => {
|
|
212
229
|
references.push({ path: resolve(nuxt.options.buildDir, "nuxt-og-image.d.ts") });
|
|
213
230
|
});
|
|
214
|
-
["html", "
|
|
231
|
+
["html", "options", "svg", "vnode", "og.png"].forEach((type) => {
|
|
215
232
|
addServerHandler({
|
|
216
233
|
handler: resolve(`./runtime/nitro/routes/__og_image__/${type}`)
|
|
217
234
|
});
|
|
@@ -238,8 +255,8 @@ export {}
|
|
|
238
255
|
});
|
|
239
256
|
});
|
|
240
257
|
await addComponent({
|
|
241
|
-
name: "
|
|
242
|
-
filePath: resolve("./runtime/components/
|
|
258
|
+
name: "OgImageBasic",
|
|
259
|
+
filePath: resolve("./runtime/components/OgImageBasic.island.vue"),
|
|
243
260
|
global: true,
|
|
244
261
|
island: true
|
|
245
262
|
});
|
|
@@ -252,7 +269,6 @@ export {}
|
|
|
252
269
|
});
|
|
253
270
|
const runtimeDir = resolve("./runtime");
|
|
254
271
|
nuxt.options.build.transpile.push(runtimeDir);
|
|
255
|
-
exposeConfig("#nuxt-og-image/constants", "nuxt-og-image-constants.mjs", Constants);
|
|
256
272
|
exposeConfig("#nuxt-og-image/config", "nuxt-og-image-config.mjs", config);
|
|
257
273
|
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
258
274
|
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
@@ -292,19 +308,19 @@ export {}
|
|
|
292
308
|
const html = ctx.contents;
|
|
293
309
|
if (!html)
|
|
294
310
|
return;
|
|
295
|
-
const
|
|
296
|
-
ctx.contents = html
|
|
311
|
+
const extractedOptions = extractOgImageOptions(html);
|
|
312
|
+
ctx.contents = stripOgImageOptions(html);
|
|
297
313
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
|
|
298
|
-
if (!
|
|
314
|
+
if (!extractedOptions || routeRules.ogImage === false)
|
|
299
315
|
return;
|
|
300
|
-
const
|
|
316
|
+
const options = {
|
|
301
317
|
path: ctx.route,
|
|
302
|
-
...
|
|
318
|
+
...extractedOptions,
|
|
303
319
|
...routeRules.ogImage || {},
|
|
304
320
|
ctx
|
|
305
321
|
};
|
|
306
|
-
if ((nuxt.options._generate ||
|
|
307
|
-
screenshotQueue.push(
|
|
322
|
+
if ((nuxt.options._generate || options.prerender) && options.provider === "browser")
|
|
323
|
+
screenshotQueue.push(options);
|
|
308
324
|
});
|
|
309
325
|
if (nuxt.options.dev)
|
|
310
326
|
return;
|
|
@@ -333,8 +349,8 @@ export {}
|
|
|
333
349
|
const filename = joinURL(dirname, "/og.png");
|
|
334
350
|
try {
|
|
335
351
|
const imgBuffer = await screenshot(browser, `${host}${entry.path}`, {
|
|
336
|
-
...config,
|
|
337
|
-
...entry
|
|
352
|
+
...config.defaults || {},
|
|
353
|
+
...entry || {}
|
|
338
354
|
});
|
|
339
355
|
try {
|
|
340
356
|
await mkdir(dirname, { recursive: true });
|
|
@@ -10,10 +10,10 @@ const props = defineProps({
|
|
|
10
10
|
<div :style="{ padding: '0 60px', width: '100%', height: '100%', backgroundColor: '#0c0c0c', backgroundImage: 'linear-gradient(to bottom, #dbf4ff, #fff1f1)', display: 'flex', alignItems: 'center' }">
|
|
11
11
|
<div :style="{ padding: '0 30px', display: 'flex', flexDirection: 'column' }">
|
|
12
12
|
<p :style="{ fontSize: '60px', fontWeight: 'bold', marginBottom: '20px' }">
|
|
13
|
-
{{ title }}
|
|
13
|
+
{{ title || 'Og Image Template' }}
|
|
14
14
|
</p>
|
|
15
15
|
<p :style="{ fontSize: '26px' }">
|
|
16
|
-
{{ description }}
|
|
16
|
+
{{ description || 'Set a description to change me.' }}
|
|
17
17
|
</p>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const _default: import("vue").DefineComponent<
|
|
1
|
+
import type { OgImageOptions } from '../../types';
|
|
2
|
+
declare const _default: import("vue").DefineComponent<OgImageOptions, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<OgImageOptions>>, {
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
}>;
|
|
5
5
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const _default: import("vue").DefineComponent<
|
|
1
|
+
import type { OgImageScreenshotOptions } from '../../types';
|
|
2
|
+
declare const _default: import("vue").DefineComponent<OgImageScreenshotOptions, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<OgImageScreenshotOptions>>, {
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
[x: number]: any;
|
|
5
5
|
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
declare const _default: import("vue").DefineComponent<
|
|
1
|
+
import type { OgImageOptions } from '../../types';
|
|
2
|
+
declare const _default: import("vue").DefineComponent<OgImageOptions, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<OgImageOptions>>, {
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
}>;
|
|
5
5
|
export default _default;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function defineOgImageScreenshot(options?:
|
|
3
|
-
export declare function defineOgImageDynamic(options?:
|
|
4
|
-
export declare function defineOgImageStatic(options?:
|
|
5
|
-
export declare function defineOgImage(options?:
|
|
1
|
+
import type { OgImageOptions, OgImageScreenshotOptions } from '../../types';
|
|
2
|
+
export declare function defineOgImageScreenshot(options?: OgImageScreenshotOptions): void;
|
|
3
|
+
export declare function defineOgImageDynamic(options?: OgImageOptions): void;
|
|
4
|
+
export declare function defineOgImageStatic(options?: OgImageOptions): void;
|
|
5
|
+
export declare function defineOgImage(options?: OgImageOptions): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useServerHead } from "@vueuse/head";
|
|
2
2
|
import { withBase } from "ufo";
|
|
3
|
+
import { useRequestEvent } from "#app";
|
|
3
4
|
import { useRouter } from "#imports";
|
|
4
|
-
import {
|
|
5
|
-
import { forcePrerender, height, host, width } from "#nuxt-og-image/config";
|
|
5
|
+
import { defaults, forcePrerender, host } from "#nuxt-og-image/config";
|
|
6
6
|
export function defineOgImageScreenshot(options = {}) {
|
|
7
7
|
const router = useRouter();
|
|
8
8
|
const route = router?.currentRoute?.value?.path || "";
|
|
@@ -44,11 +44,11 @@ export function defineOgImage(options = {}) {
|
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
property: "og:image:width",
|
|
47
|
-
content: width
|
|
47
|
+
content: options.width || defaults.width
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
property: "og:image:height",
|
|
51
|
-
content: height
|
|
51
|
+
content: options.height || defaults.height
|
|
52
52
|
}
|
|
53
53
|
];
|
|
54
54
|
if (options.alt) {
|
|
@@ -61,9 +61,15 @@ export function defineOgImage(options = {}) {
|
|
|
61
61
|
meta,
|
|
62
62
|
script: [
|
|
63
63
|
{
|
|
64
|
-
id:
|
|
64
|
+
id: "nuxt-og-image-options",
|
|
65
65
|
type: "application/json",
|
|
66
|
-
children: () =>
|
|
66
|
+
children: () => {
|
|
67
|
+
const payload = {};
|
|
68
|
+
Object.entries(options).forEach(([key, val]) => {
|
|
69
|
+
payload[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = val;
|
|
70
|
+
});
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
74
|
]
|
|
69
75
|
});
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { screenshot } from "../../browserUtil.mjs";
|
|
2
|
-
import { height, width } from "#nuxt-og-image/config";
|
|
3
2
|
import { createBrowser } from "#nuxt-og-image/browser";
|
|
4
3
|
export default {
|
|
5
4
|
name: "browser",
|
|
6
|
-
createSvg: function createSvg() {
|
|
7
|
-
throw new Error("Browser provider
|
|
5
|
+
createSvg: async function createSvg() {
|
|
6
|
+
throw new Error("Browser provider can't create SVGs.");
|
|
8
7
|
},
|
|
9
|
-
|
|
8
|
+
createVNode: async function createVNode() {
|
|
9
|
+
throw new Error("Browser provider can't create VNodes.");
|
|
10
|
+
},
|
|
11
|
+
createPng: async function createPng(basePath, options) {
|
|
10
12
|
const browser = await createBrowser();
|
|
11
|
-
return screenshot(browser, basePath,
|
|
12
|
-
width: Number(width),
|
|
13
|
-
height: Number(height)
|
|
14
|
-
});
|
|
13
|
+
return screenshot(browser, basePath, options);
|
|
15
14
|
}
|
|
16
15
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { html as convertHtmlToSatori } from "satori-html";
|
|
2
|
+
import satori from "satori";
|
|
3
|
+
import { parseURL } from "ufo";
|
|
4
|
+
import { Resvg } from "@resvg/resvg-js";
|
|
5
|
+
import twemoji from "twemoji";
|
|
6
|
+
import { parseFont, walkSatoriTree } from "./utils.mjs";
|
|
7
|
+
import imageSrc from "./plugins/imageSrc.mjs";
|
|
8
|
+
import twClasses from "./plugins/twClasses.mjs";
|
|
9
|
+
import flex from "./plugins/flex.mjs";
|
|
10
|
+
import emojis from "./plugins/emojis.mjs";
|
|
11
|
+
import { satoriFonts, satoriOptions } from "#nuxt-og-image/config";
|
|
12
|
+
export default {
|
|
13
|
+
name: "satori",
|
|
14
|
+
createPng: async function createPng(baseUrl, options) {
|
|
15
|
+
const svg = await this.createSvg(baseUrl, options);
|
|
16
|
+
const resvg = new Resvg(svg, {});
|
|
17
|
+
const pngData = resvg.render();
|
|
18
|
+
return pngData.asPng();
|
|
19
|
+
},
|
|
20
|
+
createVNode: async function createVNode(baseUrl, options) {
|
|
21
|
+
const url = parseURL(baseUrl);
|
|
22
|
+
const html = await $fetch(url.pathname);
|
|
23
|
+
const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1];
|
|
24
|
+
const emojiedFont = twemoji.parse(body, {
|
|
25
|
+
folder: "svg",
|
|
26
|
+
ext: ".svg"
|
|
27
|
+
});
|
|
28
|
+
satoriOptions.fonts = satoriOptions.fonts || [];
|
|
29
|
+
for (const font of satoriFonts)
|
|
30
|
+
satoriOptions.fonts.push(await parseFont(url, font));
|
|
31
|
+
const satoriTree = convertHtmlToSatori(emojiedFont);
|
|
32
|
+
await walkSatoriTree(url, satoriTree, [
|
|
33
|
+
emojis(url),
|
|
34
|
+
twClasses(url),
|
|
35
|
+
imageSrc(url),
|
|
36
|
+
flex(url)
|
|
37
|
+
]);
|
|
38
|
+
return satoriTree;
|
|
39
|
+
},
|
|
40
|
+
createSvg: async function createSvg(baseUrl, options) {
|
|
41
|
+
const vnodes = await this.createVNode(baseUrl, options);
|
|
42
|
+
return await satori(vnodes, {
|
|
43
|
+
...satoriOptions,
|
|
44
|
+
width: options.width,
|
|
45
|
+
height: options.height
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
|
+
export default defineSatoriTransformer(() => {
|
|
3
|
+
return {
|
|
4
|
+
filter: (node) => node.type === "img" && node.props?.class.includes("emoji"),
|
|
5
|
+
transform: async (node) => {
|
|
6
|
+
node.props.style = node.props.style || {};
|
|
7
|
+
node.props.style.height = "1em";
|
|
8
|
+
node.props.style.width = "1em";
|
|
9
|
+
node.props.style.margin = "0 .05em 0 .1em";
|
|
10
|
+
node.props.style.verticalAlign = "0.1em";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
|
+
export default defineSatoriTransformer(() => {
|
|
3
|
+
return {
|
|
4
|
+
filter: (node) => node.type === "div" && (Array.isArray(node.props?.children) && node.props?.children.length >= 1) && (!node.props.style?.display && !node.props?.class?.includes("flex")),
|
|
5
|
+
transform: async (node) => {
|
|
6
|
+
node.props.style = node.props.style || {};
|
|
7
|
+
node.props.style.display = "flex";
|
|
8
|
+
node.props.style.flexDirection = "column";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import { withBase } from "ufo";
|
|
4
|
+
import { dirname, resolve } from "pathe";
|
|
5
|
+
import { defineSatoriTransformer } from "../utils.mjs";
|
|
6
|
+
import { getAsset } from "#internal/nitro/virtual/public-assets";
|
|
7
|
+
export default defineSatoriTransformer((url) => {
|
|
8
|
+
return {
|
|
9
|
+
filter: (node) => node.type === "img",
|
|
10
|
+
transform: async (node) => {
|
|
11
|
+
const src = node.props?.src;
|
|
12
|
+
if (src && src.startsWith("/")) {
|
|
13
|
+
const file = getAsset(src);
|
|
14
|
+
if (file) {
|
|
15
|
+
const serverDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const path = resolve(serverDir, file.path);
|
|
17
|
+
node.props.src = `data:${file.type};base64,${await fsp.readFile(path, { encoding: "base64" })}`;
|
|
18
|
+
} else {
|
|
19
|
+
node.props.src = withBase(src, `${url.protocol}//${url.host}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SatoriOptions } from 'satori';
|
|
2
|
+
import type { ParsedURL } from 'ufo';
|
|
3
|
+
import type { SatoriTransformer, VNode } from '../../../../types';
|
|
4
|
+
export declare function parseFont(url: ParsedURL, font: SatoriOptions['fonts'][number] & {
|
|
5
|
+
publicPath?: string;
|
|
6
|
+
}): Promise<import("satori").Font & {
|
|
7
|
+
publicPath?: string | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function walkSatoriTree(url: ParsedURL, node: VNode, plugins: SatoriTransformer[]): Promise<void>;
|
|
10
|
+
export declare function defineSatoriTransformer(transformer: (url: ParsedURL) => SatoriTransformer): (url: ParsedURL) => SatoriTransformer;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "pathe";
|
|
4
|
+
import { withBase } from "ufo";
|
|
5
|
+
import { getAsset } from "#internal/nitro/virtual/public-assets";
|
|
6
|
+
export async function parseFont(url, font) {
|
|
7
|
+
if (typeof font.publicPath === "string") {
|
|
8
|
+
const file = getAsset(font.publicPath);
|
|
9
|
+
if (file) {
|
|
10
|
+
const serverDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
font.data = await fsp.readFile(resolve(serverDir, file.path));
|
|
12
|
+
}
|
|
13
|
+
if (!font.data)
|
|
14
|
+
font.data = await (await $fetch(withBase(font.publicPath, `${url.protocol}//${url.host}`))).arrayBuffer();
|
|
15
|
+
}
|
|
16
|
+
return font;
|
|
17
|
+
}
|
|
18
|
+
export async function walkSatoriTree(url, node, plugins) {
|
|
19
|
+
if (!node.props?.children)
|
|
20
|
+
return;
|
|
21
|
+
for (const child of node.props.children || []) {
|
|
22
|
+
if (child) {
|
|
23
|
+
for (const plugin of plugins) {
|
|
24
|
+
if (plugin.filter(child))
|
|
25
|
+
await plugin.transform(child, url);
|
|
26
|
+
}
|
|
27
|
+
await walkSatoriTree(url, child, plugins);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function defineSatoriTransformer(transformer) {
|
|
32
|
+
return transformer;
|
|
33
|
+
}
|
|
@@ -1,26 +1,56 @@
|
|
|
1
|
-
import { parseURL, withoutTrailingSlash } from "ufo";
|
|
1
|
+
import { parseURL, withBase, withoutTrailingSlash } from "ufo";
|
|
2
2
|
import { renderSSRHead } from "@unhead/ssr";
|
|
3
3
|
import { createHeadCore } from "@unhead/vue";
|
|
4
|
-
import { defineEventHandler, sendRedirect } from "h3";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { defineEventHandler, getQuery, sendRedirect } from "h3";
|
|
5
|
+
import { fetchOptions, renderIsland, useHostname } from "../../utils.mjs";
|
|
6
|
+
import { defaults } from "#nuxt-og-image/config";
|
|
7
7
|
export default defineEventHandler(async (e) => {
|
|
8
8
|
const path = parseURL(e.path).pathname;
|
|
9
9
|
if (!path.endsWith("__og_image__/html"))
|
|
10
10
|
return;
|
|
11
11
|
const basePath = withoutTrailingSlash(path.replace("__og_image__/html", ""));
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const island = await renderIsland(component, payload);
|
|
12
|
+
const scale = getQuery(e).scale;
|
|
13
|
+
const options = await fetchOptions(basePath);
|
|
14
|
+
if (options.provider === "browser")
|
|
15
|
+
return sendRedirect(e, withBase(basePath, useHostname(e)));
|
|
16
|
+
const island = await renderIsland(options);
|
|
18
17
|
const head = createHeadCore();
|
|
19
18
|
head.push(island.head);
|
|
20
19
|
head.push({
|
|
21
20
|
style: [
|
|
22
21
|
{
|
|
23
|
-
innerHTML: "body { font-family: 'Inter', sans-serif;
|
|
22
|
+
innerHTML: "body { font-family: 'Inter', sans-serif; }"
|
|
23
|
+
},
|
|
24
|
+
scale ? {
|
|
25
|
+
innerHTML: `body {
|
|
26
|
+
transform: scale(${scale});
|
|
27
|
+
transform-origin: top left;
|
|
28
|
+
max-height: 100vh;
|
|
29
|
+
}
|
|
30
|
+
img.emoji {
|
|
31
|
+
height: 1em;
|
|
32
|
+
width: 1em;
|
|
33
|
+
margin: 0 .05em 0 .1em;
|
|
34
|
+
vertical-align: -0.1em;
|
|
35
|
+
}
|
|
36
|
+
`
|
|
37
|
+
} : {}
|
|
38
|
+
],
|
|
39
|
+
meta: [
|
|
40
|
+
{
|
|
41
|
+
charset: "utf-8"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
script: [
|
|
45
|
+
{
|
|
46
|
+
src: "https://cdn.tailwindcss.com"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
innerHTML: `tailwind.config = {
|
|
50
|
+
corePlugins: {
|
|
51
|
+
preflight: false,
|
|
52
|
+
}
|
|
53
|
+
}`
|
|
24
54
|
}
|
|
25
55
|
],
|
|
26
56
|
link: [
|
|
@@ -38,6 +68,6 @@ export default defineEventHandler(async (e) => {
|
|
|
38
68
|
return `<!DOCTYPE html>
|
|
39
69
|
<html ${headChunk.htmlAttrs}>
|
|
40
70
|
<head>${headChunk.headTags}</head>
|
|
41
|
-
<body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div style="width: ${width}px; height: ${height}px; display: flex; margin: 0 auto;">${island.html}</div>${headChunk.bodyTags}</body>
|
|
71
|
+
<body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div style="width: ${defaults.width}px; height: ${defaults.height}px; display: flex; margin: 0 auto;">${island.html}</div>${headChunk.bodyTags}</body>
|
|
42
72
|
</html>`;
|
|
43
73
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
2
|
import { parseURL, withoutTrailingSlash } from "ufo";
|
|
3
|
-
import {
|
|
3
|
+
import { fetchOptions, useHostname } from "../../utils.mjs";
|
|
4
4
|
export default defineEventHandler(async (e) => {
|
|
5
5
|
const path = parseURL(e.path).pathname;
|
|
6
6
|
if (!path.endsWith("/__og_image__"))
|
|
7
7
|
return;
|
|
8
8
|
const basePath = withoutTrailingSlash(path.replace("__og_image__", ""));
|
|
9
|
-
const
|
|
10
|
-
if (!
|
|
9
|
+
const options = await fetchOptions(basePath);
|
|
10
|
+
if (!options)
|
|
11
11
|
return `The route ${basePath} has not been set up for og:image generation.`;
|
|
12
12
|
return `
|
|
13
13
|
<style>
|
|
@@ -21,6 +21,6 @@ export default defineEventHandler(async (e) => {
|
|
|
21
21
|
height: 100%;
|
|
22
22
|
}
|
|
23
23
|
</style>
|
|
24
|
-
<title>
|
|
24
|
+
<title>OG Image Playground</title>
|
|
25
25
|
<iframe src="${useHostname(e)}/__nuxt_og_image__/client/?&path=${withoutTrailingSlash(path.replace("__og_image__", ""))}"></iframe>`;
|
|
26
26
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineEventHandler, setHeader } from "h3";
|
|
2
|
-
import { parseURL, withBase, withoutTrailingSlash } from "ufo";
|
|
3
|
-
import {
|
|
2
|
+
import { joinURL, parseURL, withBase, withoutTrailingSlash } from "ufo";
|
|
3
|
+
import { fetchOptions, useHostname } from "../../utils.mjs";
|
|
4
4
|
import { useProvider } from "#nuxt-og-image/provider";
|
|
5
5
|
export default defineEventHandler(async (e) => {
|
|
6
6
|
const path = parseURL(e.path).pathname;
|
|
@@ -9,15 +9,11 @@ export default defineEventHandler(async (e) => {
|
|
|
9
9
|
const basePath = withoutTrailingSlash(
|
|
10
10
|
path.replace("__og_image__/og.png", "")
|
|
11
11
|
);
|
|
12
|
-
const
|
|
12
|
+
const options = await fetchOptions(basePath);
|
|
13
13
|
setHeader(e, "Content-Type", "image/png");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
setHeader(e, "Expires", "0");
|
|
20
|
-
}
|
|
21
|
-
const provider = await useProvider(providerName);
|
|
22
|
-
return provider.createPng(withBase(`${basePath}/__og_image__/html`, useHostname(e)));
|
|
14
|
+
setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
|
|
15
|
+
setHeader(e, "Pragma", "no-cache");
|
|
16
|
+
setHeader(e, "Expires", "0");
|
|
17
|
+
const provider = await useProvider(options.provider);
|
|
18
|
+
return provider.createPng(withBase(joinURL(basePath, "/__og_image__/html"), useHostname(e)), options);
|
|
23
19
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OgImageOptions } from '../../../../types';
|
|
2
|
+
export declare function extractOgImageOptions(html: string): any;
|
|
3
|
+
export declare const inferOgImageOptions: (html: string) => OgImageOptions;
|
|
4
|
+
declare const _default: import("h3").EventHandler<false | OgImageOptions | undefined>;
|
|
5
|
+
export default _default;
|