nuxt-og-image 2.0.0-beta.1 → 2.0.0-beta.11
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 +96 -35
- package/dist/client/200.html +2 -2
- package/dist/client/404.html +2 -2
- package/dist/client/_nuxt/{IconCSS.60a77d69.js → IconCSS.1ea3f0cd.js} +1 -1
- package/dist/client/_nuxt/ImageLoader.fe3ede25.js +1 -0
- package/dist/client/_nuxt/entry.09f25aaf.css +1 -0
- package/dist/client/_nuxt/entry.e84c6828.js +5 -0
- package/dist/client/_nuxt/{error-404.8c1e055e.js → error-404.7c81d800.js} +1 -1
- package/dist/client/_nuxt/{error-500.bb6daf96.js → error-500.6085d60e.js} +1 -1
- package/dist/client/_nuxt/{error-component.62480d86.js → error-component.a44fd872.js} +2 -2
- package/dist/client/_nuxt/index.6e84ca7d.js +1 -0
- package/dist/client/_nuxt/options.b57b4483.js +1 -0
- package/dist/client/_nuxt/png.59dec69d.js +1 -0
- package/dist/client/_nuxt/shiki.15577506.js +7 -0
- package/dist/client/_nuxt/{svg.b9393fe1.js → svg.bc1d22d0.js} +1 -1
- package/dist/client/_nuxt/vnodes.bcd7509e.js +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/options/index.html +2 -2
- package/dist/client/png/index.html +2 -2
- package/dist/client/svg/index.html +2 -2
- package/dist/client/vnodes/index.html +2 -2
- package/dist/module.d.ts +0 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +54 -52
- package/dist/runtime/browserUtil.mjs +7 -4
- package/dist/runtime/composables/defineOgImage.mjs +6 -4
- package/dist/runtime/nitro/middleware/og.png.mjs +17 -2
- package/dist/runtime/nitro/middleware/playground.mjs +4 -3
- package/dist/runtime/nitro/providers/browser/node.mjs +10 -8
- package/dist/runtime/nitro/providers/svg2png/universal.mjs +4 -2
- package/dist/runtime/nitro/renderers/browser.mjs +6 -1
- package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
- package/dist/runtime/nitro/routes/html.mjs +11 -8
- package/dist/runtime/nitro/routes/options.mjs +6 -3
- package/dist/runtime/nitro/routes/svg.mjs +3 -1
- package/dist/runtime/nitro/routes/vnode.mjs +3 -1
- package/dist/runtime/nitro/util-hostname.d.ts +2 -0
- package/dist/runtime/nitro/util-hostname.mjs +13 -0
- package/dist/runtime/nitro/utils-pure.d.ts +3 -2
- package/dist/runtime/nitro/utils-pure.mjs +16 -13
- package/dist/runtime/nitro/utils.d.ts +1 -1
- package/dist/runtime/nitro/utils.mjs +6 -15
- package/package.json +14 -13
- package/dist/client/_nuxt/ImageLoader.dba956e5.js +0 -1
- package/dist/client/_nuxt/entry.3ad25beb.js +0 -5
- package/dist/client/_nuxt/entry.99fbf2af.css +0 -1
- package/dist/client/_nuxt/index.5554552f.js +0 -1
- package/dist/client/_nuxt/options.a3f35ef0.js +0 -1
- package/dist/client/_nuxt/png.e6fefdc5.js +0 -1
- package/dist/client/_nuxt/vnodes.fe6b127e.js +0 -1
package/dist/module.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { execa } from 'execa';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import defu from 'defu';
|
|
6
6
|
import { toRouteMatcher, createRouter } from 'radix3';
|
|
7
|
-
import { joinURL } from 'ufo';
|
|
7
|
+
import { withBase, joinURL } from 'ufo';
|
|
8
8
|
import { resolve, relative } from 'pathe';
|
|
9
9
|
import { tinyws } from 'tinyws';
|
|
10
10
|
import sirv from 'sirv';
|
|
@@ -16,14 +16,16 @@ import { createBirpcGroup } from 'birpc';
|
|
|
16
16
|
import { stringify, parse } from 'flatted';
|
|
17
17
|
|
|
18
18
|
async function createBrowser() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
if (process.dev || process.env.prerender) {
|
|
20
|
+
try {
|
|
21
|
+
const { Launcher } = await import(String("chrome-launcher"));
|
|
22
|
+
const chromePath = Launcher.getFirstInstallation();
|
|
23
|
+
return await playwrightCore.chromium.launch({
|
|
24
|
+
headless: true,
|
|
25
|
+
executablePath: chromePath
|
|
26
|
+
});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
}
|
|
27
29
|
}
|
|
28
30
|
try {
|
|
29
31
|
return await playwrightCore.chromium.launch({
|
|
@@ -54,9 +56,9 @@ async function screenshot(browser, options) {
|
|
|
54
56
|
width: options.width || 1200,
|
|
55
57
|
height: options.height || 630
|
|
56
58
|
});
|
|
57
|
-
const isHtml = options.path
|
|
59
|
+
const isHtml = options.html || options.path?.startsWith("html:");
|
|
58
60
|
if (isHtml) {
|
|
59
|
-
const html = options.html || options.path
|
|
61
|
+
const html = options.html || options.path?.substring(5);
|
|
60
62
|
await page.evaluate((html2) => {
|
|
61
63
|
document.open("text/html");
|
|
62
64
|
document.write(html2);
|
|
@@ -69,6 +71,9 @@ async function screenshot(browser, options) {
|
|
|
69
71
|
waitUntil: "networkidle"
|
|
70
72
|
});
|
|
71
73
|
}
|
|
74
|
+
const screenshotOptions = {
|
|
75
|
+
timeout: 1e4
|
|
76
|
+
};
|
|
72
77
|
if (options.delay)
|
|
73
78
|
await page.waitForTimeout(options.delay);
|
|
74
79
|
if (options.mask) {
|
|
@@ -78,8 +83,8 @@ async function screenshot(browser, options) {
|
|
|
78
83
|
}, options.mask);
|
|
79
84
|
}
|
|
80
85
|
if (options.selector)
|
|
81
|
-
return await page.locator(options.selector).screenshot();
|
|
82
|
-
return await page.screenshot();
|
|
86
|
+
return await page.locator(options.selector).screenshot(screenshotOptions);
|
|
87
|
+
return await page.screenshot(screenshotOptions);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
function setupPlaygroundRPC(nuxt, config) {
|
|
@@ -113,7 +118,7 @@ function setupPlaygroundRPC(nuxt, config) {
|
|
|
113
118
|
const birpc = createBirpcGroup(serverFunctions, []);
|
|
114
119
|
nuxt.hook("builder:watch", (e, path) => {
|
|
115
120
|
if (e === "change")
|
|
116
|
-
birpc.
|
|
121
|
+
birpc.broadcast.refresh.asEvent(path);
|
|
117
122
|
});
|
|
118
123
|
const middleware = async (req, res) => {
|
|
119
124
|
if (req.ws) {
|
|
@@ -164,11 +169,15 @@ function getBodyJson(req) {
|
|
|
164
169
|
});
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
function
|
|
172
|
+
function decodeHtml(html) {
|
|
173
|
+
return html.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/¢/g, "\xA2").replace(/£/g, "\xA3").replace(/¥/g, "\xA5").replace(/€/g, "\u20AC").replace(/©/g, "\xA9").replace(/®/g, "\xAE").replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(///g, "/").replace(/&#([0-9]+);/g, (full, int) => {
|
|
174
|
+
return String.fromCharCode(parseInt(int));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function decodeObjectHtmlEntities(obj) {
|
|
168
178
|
Object.entries(obj).forEach(([key, value]) => {
|
|
169
|
-
if (typeof value === "string")
|
|
170
|
-
obj[key] = value
|
|
171
|
-
}
|
|
179
|
+
if (typeof value === "string")
|
|
180
|
+
obj[key] = decodeHtml(value);
|
|
172
181
|
});
|
|
173
182
|
return obj;
|
|
174
183
|
}
|
|
@@ -185,18 +194,17 @@ function extractOgImageOptions(html) {
|
|
|
185
194
|
console.warn("Failed to parse #nuxt-og-image-options", e, options);
|
|
186
195
|
}
|
|
187
196
|
if (options) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
if (!options.description) {
|
|
198
|
+
const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
|
|
199
|
+
if (description)
|
|
200
|
+
options.description = description;
|
|
201
|
+
else
|
|
202
|
+
options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
|
|
203
|
+
}
|
|
204
|
+
return decodeObjectHtmlEntities(options);
|
|
194
205
|
}
|
|
195
206
|
return false;
|
|
196
207
|
}
|
|
197
|
-
function stripOgImageOptions(html) {
|
|
198
|
-
return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
|
|
199
|
-
}
|
|
200
208
|
|
|
201
209
|
const PATH = "/__nuxt_og_image__";
|
|
202
210
|
const PATH_ENTRY = `${PATH}/entry`;
|
|
@@ -231,7 +239,6 @@ const module = defineNuxtModule({
|
|
|
231
239
|
fonts: [],
|
|
232
240
|
satoriOptions: {},
|
|
233
241
|
experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
|
|
234
|
-
experimentalRuntimeBrowser: false,
|
|
235
242
|
playground: process.env.NODE_ENV === "development" || nuxt.options.dev
|
|
236
243
|
};
|
|
237
244
|
},
|
|
@@ -337,17 +344,13 @@ export {}
|
|
|
337
344
|
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
338
345
|
inline: [runtimeDir]
|
|
339
346
|
});
|
|
340
|
-
if (config.experimentalRuntimeBrowser) {
|
|
341
|
-
nitroConfig.alias = nitroConfig.alias || {};
|
|
342
|
-
nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
|
|
343
|
-
nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
|
|
344
|
-
nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
|
|
345
|
-
}
|
|
346
347
|
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
|
|
347
348
|
nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
|
|
348
349
|
const providerPath = `${runtimeDir}/nitro/providers`;
|
|
350
|
+
const nitroPreset = nuxt.options.nitro.preset || process.env.NITRO_PRESET;
|
|
351
|
+
const isNodeNitroServer = !nitroPreset || nitroPreset === "node";
|
|
349
352
|
if (config.browserProvider) {
|
|
350
|
-
nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev ||
|
|
353
|
+
nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || process.env.prerender || isNodeNitroServer ? `
|
|
351
354
|
import node from '${providerPath}/browser/node'
|
|
352
355
|
|
|
353
356
|
export default async function() {
|
|
@@ -421,7 +424,6 @@ export async function useProvider(provider) {
|
|
|
421
424
|
if (!html)
|
|
422
425
|
return;
|
|
423
426
|
const extractedOptions = extractOgImageOptions(html);
|
|
424
|
-
ctx.contents = stripOgImageOptions(html);
|
|
425
427
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
|
|
426
428
|
if (!extractedOptions || routeRules.ogImage === false)
|
|
427
429
|
return;
|
|
@@ -440,21 +442,6 @@ export async function useProvider(provider) {
|
|
|
440
442
|
await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
|
|
441
443
|
if (screenshotQueue.length === 0)
|
|
442
444
|
return;
|
|
443
|
-
for (const entry of screenshotQueue) {
|
|
444
|
-
if (entry.route && Object.keys(entry).length === 1) {
|
|
445
|
-
const html = await $fetch(entry.route);
|
|
446
|
-
const extractedOptions = extractOgImageOptions(html);
|
|
447
|
-
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
|
|
448
|
-
Object.assign(entry, {
|
|
449
|
-
// @ts-expect-error runtime
|
|
450
|
-
path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
|
|
451
|
-
...extractedOptions,
|
|
452
|
-
...routeRules.ogImage || {}
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
if (entry.component)
|
|
456
|
-
entry.html = await $fetch(entry.path);
|
|
457
|
-
}
|
|
458
445
|
nitro.logger.info("Ensuring chromium install for og:image generation...");
|
|
459
446
|
const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
|
|
460
447
|
stdio: "inherit"
|
|
@@ -481,6 +468,21 @@ export async function useProvider(provider) {
|
|
|
481
468
|
browser = await createBrowser();
|
|
482
469
|
if (browser) {
|
|
483
470
|
nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
|
|
471
|
+
for (const entry of screenshotQueue) {
|
|
472
|
+
if (entry.route && Object.keys(entry).length === 1) {
|
|
473
|
+
const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
|
|
474
|
+
const extractedOptions = extractOgImageOptions(html);
|
|
475
|
+
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
|
|
476
|
+
Object.assign(entry, {
|
|
477
|
+
// @ts-expect-error runtime
|
|
478
|
+
path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
|
|
479
|
+
...extractedOptions,
|
|
480
|
+
...routeRules.ogImage || {}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (entry.component)
|
|
484
|
+
entry.html = await globalThis.$fetch(entry.path);
|
|
485
|
+
}
|
|
484
486
|
for (const k in screenshotQueue) {
|
|
485
487
|
const entry = screenshotQueue[k];
|
|
486
488
|
const start = Date.now();
|
|
@@ -518,7 +520,7 @@ export async function useProvider(provider) {
|
|
|
518
520
|
}
|
|
519
521
|
screenshotQueue = [];
|
|
520
522
|
};
|
|
521
|
-
if (
|
|
523
|
+
if (nuxt.options._generate) {
|
|
522
524
|
nitro.hooks.hook("rollup:before", async () => {
|
|
523
525
|
await captureScreenshots();
|
|
524
526
|
});
|
|
@@ -6,9 +6,9 @@ export async function screenshot(browser, options) {
|
|
|
6
6
|
width: options.width || 1200,
|
|
7
7
|
height: options.height || 630
|
|
8
8
|
});
|
|
9
|
-
const isHtml = options.path
|
|
9
|
+
const isHtml = options.html || options.path?.startsWith("html:");
|
|
10
10
|
if (isHtml) {
|
|
11
|
-
const html = options.html || options.path
|
|
11
|
+
const html = options.html || options.path?.substring(5);
|
|
12
12
|
await page.evaluate((html2) => {
|
|
13
13
|
document.open("text/html");
|
|
14
14
|
document.write(html2);
|
|
@@ -21,6 +21,9 @@ export async function screenshot(browser, options) {
|
|
|
21
21
|
waitUntil: "networkidle"
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
const screenshotOptions = {
|
|
25
|
+
timeout: 1e4
|
|
26
|
+
};
|
|
24
27
|
if (options.delay)
|
|
25
28
|
await page.waitForTimeout(options.delay);
|
|
26
29
|
if (options.mask) {
|
|
@@ -30,6 +33,6 @@ export async function screenshot(browser, options) {
|
|
|
30
33
|
}, options.mask);
|
|
31
34
|
}
|
|
32
35
|
if (options.selector)
|
|
33
|
-
return await page.locator(options.selector).screenshot();
|
|
34
|
-
return await page.screenshot();
|
|
36
|
+
return await page.locator(options.selector).screenshot(screenshotOptions);
|
|
37
|
+
return await page.screenshot(screenshotOptions);
|
|
35
38
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useServerHead } from "@vueuse/head";
|
|
2
2
|
import { withBase } from "ufo";
|
|
3
3
|
import { useRequestEvent } from "#app";
|
|
4
|
-
import {
|
|
4
|
+
import { useHostname } from "../nitro/util-hostname.mjs";
|
|
5
|
+
import { useRouter, useRuntimeConfig } from "#imports";
|
|
5
6
|
export function defineOgImageScreenshot(options = {}) {
|
|
6
7
|
const router = useRouter();
|
|
7
8
|
const route = router?.currentRoute?.value?.path || "";
|
|
@@ -31,12 +32,13 @@ export function defineOgImageStatic(options = {}) {
|
|
|
31
32
|
}
|
|
32
33
|
export function defineOgImage(options = {}) {
|
|
33
34
|
if (process.server) {
|
|
34
|
-
const { forcePrerender, defaults,
|
|
35
|
+
const { forcePrerender, defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
|
|
35
36
|
const router = useRouter();
|
|
36
37
|
const route = router?.currentRoute?.value?.path || "";
|
|
37
38
|
const e = useRequestEvent();
|
|
38
39
|
if ((forcePrerender || options.static) && options.provider === "satori")
|
|
39
40
|
e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
|
|
41
|
+
const baseUrl = process.env.prerender ? siteUrl : useHostname(e);
|
|
40
42
|
const meta = [
|
|
41
43
|
{
|
|
42
44
|
name: "twitter:card",
|
|
@@ -44,11 +46,11 @@ export function defineOgImage(options = {}) {
|
|
|
44
46
|
},
|
|
45
47
|
{
|
|
46
48
|
name: "twitter:image:src",
|
|
47
|
-
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`,
|
|
49
|
+
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
|
|
48
50
|
},
|
|
49
51
|
{
|
|
50
52
|
property: "og:image",
|
|
51
|
-
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`,
|
|
53
|
+
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
|
|
52
54
|
},
|
|
53
55
|
{
|
|
54
56
|
property: "og:image:width",
|
|
@@ -9,7 +9,6 @@ export default defineEventHandler(async (e) => {
|
|
|
9
9
|
const basePath = withoutTrailingSlash(
|
|
10
10
|
path.replace("__og_image__/og.png", "")
|
|
11
11
|
);
|
|
12
|
-
setHeader(e, "Content-Type", "image/png");
|
|
13
12
|
setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
|
|
14
13
|
setHeader(e, "Pragma", "no-cache");
|
|
15
14
|
setHeader(e, "Expires", "0");
|
|
@@ -21,5 +20,21 @@ export default defineEventHandler(async (e) => {
|
|
|
21
20
|
statusMessage: `Provider ${options.provider} is missing.`
|
|
22
21
|
});
|
|
23
22
|
}
|
|
24
|
-
|
|
23
|
+
try {
|
|
24
|
+
const png = await provider.createPng(withBase(basePath, useHostname(e)), options);
|
|
25
|
+
if (png) {
|
|
26
|
+
setHeader(e, "Content-Type", "image/png");
|
|
27
|
+
return png;
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
throw createError({
|
|
31
|
+
statusCode: 500,
|
|
32
|
+
statusMessage: `Failed to create og image: ${err.message}`
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
throw createError({
|
|
36
|
+
statusCode: 500,
|
|
37
|
+
statusMessage: "Failed to create og image, unknown error."
|
|
38
|
+
});
|
|
39
|
+
return false;
|
|
25
40
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
|
-
import { parseURL, withoutTrailingSlash } from "ufo";
|
|
2
|
+
import { parseURL, withBase, withoutTrailingSlash } from "ufo";
|
|
3
3
|
import { fetchOptions } from "../utils.mjs";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
5
|
export default defineEventHandler(async (e) => {
|
|
5
6
|
const path = withoutTrailingSlash(parseURL(e.path).pathname);
|
|
6
7
|
if (!path.endsWith("/__og_image__"))
|
|
7
8
|
return;
|
|
8
|
-
const basePath = path.replace("/__og_image__", "");
|
|
9
|
+
const basePath = withBase(path.replace("/__og_image__", ""), useRuntimeConfig().app.baseURL);
|
|
9
10
|
const options = await fetchOptions(e, basePath === "" ? "/" : basePath);
|
|
10
11
|
if (!options)
|
|
11
12
|
return `The route ${basePath} has not been set up for og:image generation.`;
|
|
@@ -22,5 +23,5 @@ export default defineEventHandler(async (e) => {
|
|
|
22
23
|
}
|
|
23
24
|
</style>
|
|
24
25
|
<title>OG Image Playground</title>
|
|
25
|
-
<iframe src="/__nuxt_og_image__/client
|
|
26
|
+
<iframe src="/__nuxt_og_image__/client?&path=${basePath}&base=${useRuntimeConfig().app.baseURL}"></iframe>`;
|
|
26
27
|
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import playwrightCore from "playwright-core";
|
|
2
2
|
export default async function createBrowser() {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
if (process.dev || process.env.prerender) {
|
|
4
|
+
try {
|
|
5
|
+
const { Launcher } = await import(String("chrome-launcher"));
|
|
6
|
+
const chromePath = Launcher.getFirstInstallation();
|
|
7
|
+
return await playwrightCore.chromium.launch({
|
|
8
|
+
headless: true,
|
|
9
|
+
executablePath: chromePath
|
|
10
|
+
});
|
|
11
|
+
} catch (e) {
|
|
12
|
+
}
|
|
11
13
|
}
|
|
12
14
|
try {
|
|
13
15
|
return await playwrightCore.chromium.launch({
|
|
@@ -2,8 +2,10 @@ import { initialize, svg2png } from "svg2png-wasm";
|
|
|
2
2
|
import { wasmLoader } from "../../utils.mjs";
|
|
3
3
|
export default async function(svg, options) {
|
|
4
4
|
const loader = wasmLoader("/* NUXT_OG_IMAGE_SVG2PNG_WASM */", "/svg2png.wasm", options.baseUrl);
|
|
5
|
-
if (!await loader.loaded())
|
|
6
|
-
await initialize(await loader.load()).catch(() => {
|
|
5
|
+
if (!await loader.loaded()) {
|
|
6
|
+
await initialize(await loader.load()).catch((e) => {
|
|
7
|
+
console.log("svg2png wasm failed to load", e);
|
|
7
8
|
});
|
|
9
|
+
}
|
|
8
10
|
return await svg2png(svg, options);
|
|
9
11
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { withBase } from "ufo";
|
|
1
2
|
import { screenshot } from "../../browserUtil.mjs";
|
|
2
3
|
import loadBrowser from "#nuxt-og-image/browser";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
5
|
export default {
|
|
4
6
|
name: "browser",
|
|
5
7
|
createSvg: async function createSvg() {
|
|
@@ -11,12 +13,15 @@ export default {
|
|
|
11
13
|
createPng: async function createPng(basePath, options) {
|
|
12
14
|
const url = new URL(basePath);
|
|
13
15
|
const createBrowser = await loadBrowser();
|
|
16
|
+
if (!createBrowser) {
|
|
17
|
+
throw new Error("Failed to load browser. Is the `browserProvider` enabled?");
|
|
18
|
+
}
|
|
14
19
|
const browser = await createBrowser();
|
|
15
20
|
if (browser) {
|
|
16
21
|
try {
|
|
17
22
|
return await screenshot(browser, {
|
|
18
23
|
...options,
|
|
19
|
-
host: url.origin,
|
|
24
|
+
host: withBase(useRuntimeConfig().app.baseURL, url.origin),
|
|
20
25
|
path: `/api/og-image-html?path=${url.pathname}`
|
|
21
26
|
});
|
|
22
27
|
} finally {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
|
+
import { decodeHtml } from "../../../utils-pure.mjs";
|
|
2
3
|
export default defineSatoriTransformer(() => {
|
|
3
4
|
return {
|
|
4
5
|
filter: (node) => typeof node.props?.children === "string",
|
|
5
6
|
transform: async (node) => {
|
|
6
|
-
node.props.children = node.props.children
|
|
7
|
+
node.props.children = decodeHtml(node.props.children);
|
|
7
8
|
}
|
|
8
9
|
};
|
|
9
10
|
});
|
|
@@ -3,19 +3,22 @@ import { renderSSRHead } from "@unhead/ssr";
|
|
|
3
3
|
import { createHeadCore } from "@unhead/vue";
|
|
4
4
|
import { defineEventHandler, getQuery, sendRedirect } from "h3";
|
|
5
5
|
import { fetchOptions, renderIsland, useHostname } from "../utils.mjs";
|
|
6
|
-
import { useRuntimeConfig } from "#
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
7
|
export default defineEventHandler(async (e) => {
|
|
8
8
|
const { fonts, defaults } = useRuntimeConfig()["nuxt-og-image"];
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
const query = getQuery(e);
|
|
10
|
+
const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
|
|
11
|
+
const scale = query.scale;
|
|
12
|
+
const mode = query.mode || "light";
|
|
12
13
|
let options;
|
|
13
|
-
if (
|
|
14
|
-
options = JSON.parse(
|
|
14
|
+
if (query.options)
|
|
15
|
+
options = JSON.parse(query.options);
|
|
15
16
|
if (!options)
|
|
16
17
|
options = await fetchOptions(e, path);
|
|
17
|
-
if (options.provider === "browser" && !options.component)
|
|
18
|
-
|
|
18
|
+
if (options.provider === "browser" && !options.component) {
|
|
19
|
+
const pathWithoutBase = path.replace(new RegExp(`^${useRuntimeConfig().app.baseURL}`), "");
|
|
20
|
+
return sendRedirect(e, withBase(pathWithoutBase, useHostname(e)));
|
|
21
|
+
}
|
|
19
22
|
const island = await renderIsland(options);
|
|
20
23
|
const head = createHeadCore();
|
|
21
24
|
head.push(island.head);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getHeaders, getQuery } from "h3";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { withoutBase } from "ufo";
|
|
3
|
+
import { extractOgImageOptions } from "../utils.mjs";
|
|
4
|
+
import { useHostname } from "../util-hostname.mjs";
|
|
5
|
+
import { getRouteRules } from "#internal/nitro";
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
7
|
export default defineEventHandler(async (e) => {
|
|
5
8
|
const query = getQuery(e);
|
|
6
|
-
const path = query.path || "/";
|
|
9
|
+
const path = withoutBase(query.path || "/", useRuntimeConfig().app.baseURL);
|
|
7
10
|
const fetchOptions = process.dev || process.env.prerender ? {
|
|
8
11
|
headers: getHeaders(e)
|
|
9
12
|
} : {
|
|
@@ -2,8 +2,10 @@ import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
|
2
2
|
import { withBase } from "ufo";
|
|
3
3
|
import { fetchOptions, useHostname } from "../utils.mjs";
|
|
4
4
|
import { useProvider } from "#nuxt-og-image/provider";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
6
|
export default defineEventHandler(async (e) => {
|
|
6
|
-
const
|
|
7
|
+
const query = getQuery(e);
|
|
8
|
+
const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
|
|
7
9
|
const options = await fetchOptions(e, path);
|
|
8
10
|
setHeader(e, "Content-Type", "image/svg+xml");
|
|
9
11
|
const provider = await useProvider(options.provider);
|
|
@@ -2,8 +2,10 @@ import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
|
2
2
|
import { withBase } from "ufo";
|
|
3
3
|
import { fetchOptions, useHostname } from "../utils.mjs";
|
|
4
4
|
import { useProvider } from "#nuxt-og-image/provider";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
6
|
export default defineEventHandler(async (e) => {
|
|
6
|
-
const
|
|
7
|
+
const query = getQuery(e);
|
|
8
|
+
const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
|
|
7
9
|
const options = await fetchOptions(e, path);
|
|
8
10
|
setHeader(e, "Content-Type", "application/json");
|
|
9
11
|
const provider = await useProvider(options.provider);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getRequestHeader } from "h3";
|
|
2
|
+
import { withBase } from "ufo";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
export function useHostname(e) {
|
|
5
|
+
const base = useRuntimeConfig().app.baseURL;
|
|
6
|
+
const host = getRequestHeader(e, "host") || process.env.NITRO_HOST || process.env.HOST || "localhost";
|
|
7
|
+
const protocol = getRequestHeader(e, "x-forwarded-proto") || "http";
|
|
8
|
+
const useHttp = process.dev || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
|
|
9
|
+
let port = host.includes(":") ? host.split(":").pop() : false;
|
|
10
|
+
if ((process.dev || process.env.prerender || host.includes("localhost")) && !port)
|
|
11
|
+
port = process.env.NITRO_PORT || process.env.PORT;
|
|
12
|
+
return withBase(base, `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}`);
|
|
13
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function
|
|
1
|
+
import type { OgImageOptions } from '../../types';
|
|
2
|
+
export declare function decodeHtml(html: string): string;
|
|
3
|
+
export declare function extractOgImageOptions(html: string): OgImageOptions | false;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
function
|
|
1
|
+
export function decodeHtml(html) {
|
|
2
|
+
return html.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/¢/g, "\xA2").replace(/£/g, "\xA3").replace(/¥/g, "\xA5").replace(/€/g, "\u20AC").replace(/©/g, "\xA9").replace(/®/g, "\xAE").replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(///g, "/").replace(/&#([0-9]+);/g, (full, int) => {
|
|
3
|
+
return String.fromCharCode(parseInt(int));
|
|
4
|
+
});
|
|
5
|
+
}
|
|
6
|
+
function decodeObjectHtmlEntities(obj) {
|
|
2
7
|
Object.entries(obj).forEach(([key, value]) => {
|
|
3
|
-
if (typeof value === "string")
|
|
4
|
-
obj[key] = value
|
|
5
|
-
}
|
|
8
|
+
if (typeof value === "string")
|
|
9
|
+
obj[key] = decodeHtml(value);
|
|
6
10
|
});
|
|
7
11
|
return obj;
|
|
8
12
|
}
|
|
@@ -19,15 +23,14 @@ export function extractOgImageOptions(html) {
|
|
|
19
23
|
console.warn("Failed to parse #nuxt-og-image-options", e, options);
|
|
20
24
|
}
|
|
21
25
|
if (options) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (!options.description) {
|
|
27
|
+
const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
|
|
28
|
+
if (description)
|
|
29
|
+
options.description = description;
|
|
30
|
+
else
|
|
31
|
+
options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
|
|
32
|
+
}
|
|
33
|
+
return decodeObjectHtmlEntities(options);
|
|
28
34
|
}
|
|
29
35
|
return false;
|
|
30
36
|
}
|
|
31
|
-
export function stripOgImageOptions(html) {
|
|
32
|
-
return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
|
|
33
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { H3Event } from 'h3';
|
|
3
3
|
import type { OgImageOptions } from '../../types';
|
|
4
|
+
export * from './util-hostname';
|
|
4
5
|
export declare function wasmLoader(key: any, fallback: string, baseUrl: string): {
|
|
5
6
|
loaded(): boolean | Promise<any>;
|
|
6
7
|
load(): Promise<any>;
|
|
@@ -11,7 +12,6 @@ export declare function renderIsland(payload: OgImageOptions): Promise<{
|
|
|
11
12
|
html: string;
|
|
12
13
|
head: any;
|
|
13
14
|
}>;
|
|
14
|
-
export declare function useHostname(e: H3Event): any;
|
|
15
15
|
export declare function readPublicAsset(file: string, encoding?: BufferEncoding): Promise<string | Buffer | undefined>;
|
|
16
16
|
export declare function readPublicAssetBase64(file: string): Promise<string | undefined>;
|
|
17
17
|
export * from './utils-pure';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync, promises as fsp } from "node:fs";
|
|
2
|
-
import { getHeaders, getQuery
|
|
2
|
+
import { getHeaders, getQuery } from "h3";
|
|
3
3
|
import { join } from "pathe";
|
|
4
|
-
import {
|
|
4
|
+
import { useHostname } from "./util-hostname.mjs";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export * from "./util-hostname.mjs";
|
|
5
7
|
export function wasmLoader(key, fallback, baseUrl) {
|
|
6
8
|
let promise;
|
|
7
9
|
let loaded = false;
|
|
@@ -58,20 +60,9 @@ export function renderIsland(payload) {
|
|
|
58
60
|
query: { props: JSON.stringify(payload) }
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
|
-
|
|
62
|
-
const config = useRuntimeConfig()["nuxt-og-image"];
|
|
63
|
-
if (!process.dev && config.siteUrl)
|
|
64
|
-
return config.siteUrl;
|
|
65
|
-
const host = getRequestHeader(e, "host") || process.env.NITRO_HOST || process.env.HOST || "localhost";
|
|
66
|
-
const protocol = getRequestHeader(e, "x-forwarded-proto") || "http";
|
|
67
|
-
const useHttp = process.env.NODE_ENV === "development" || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
|
|
68
|
-
const port = host.includes(":") ? host.split(":").pop() : process.env.NITRO_PORT || process.env.PORT;
|
|
69
|
-
const base = useRuntimeConfig().app.baseURL;
|
|
70
|
-
return `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}${base}`;
|
|
71
|
-
}
|
|
72
|
-
const r = (base, key) => {
|
|
63
|
+
function r(base, key) {
|
|
73
64
|
return join(base, key.replace(/:/g, "/"));
|
|
74
|
-
}
|
|
65
|
+
}
|
|
75
66
|
export async function readPublicAsset(file, encoding) {
|
|
76
67
|
const { assetDirs } = useRuntimeConfig()["nuxt-og-image"];
|
|
77
68
|
for (const assetDir of assetDirs) {
|