nuxt-og-image 2.0.0-beta.2 → 2.0.0-beta.21
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.ecebf6a5.js +1 -0
- package/dist/client/_nuxt/{ImageLoader.9bf39d71.js → ImageLoader.97400b2b.js} +1 -1
- package/dist/client/_nuxt/entry.7e98a020.css +1 -0
- package/dist/client/_nuxt/entry.c8bf4454.js +5 -0
- package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.7660b68a.js} +1 -1
- package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.776f22a1.js} +1 -1
- package/dist/client/_nuxt/{error-component.cf7543e5.js → error-component.d4668032.js} +2 -2
- package/dist/client/_nuxt/{index.3f356409.js → index.77081a6c.js} +1 -1
- package/dist/client/_nuxt/{options.56a3e5f9.js → options.b6164a5b.js} +1 -1
- package/dist/client/_nuxt/{png.37f3e77b.js → png.b914f6c4.js} +1 -1
- package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.ba10b978.js} +1 -1
- package/dist/client/_nuxt/{svg.186c6bd1.js → svg.1e41877e.js} +1 -1
- package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.a857bced.js} +1 -1
- 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 +72 -60
- package/dist/runtime/browserUtil.d.ts +1 -0
- package/dist/runtime/browserUtil.mjs +7 -4
- package/dist/runtime/composables/defineOgImage.mjs +6 -7
- package/dist/runtime/nitro/middleware/og.png.mjs +17 -2
- package/dist/runtime/nitro/middleware/playground.mjs +4 -3
- package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
- package/dist/runtime/nitro/plugins/prerender.mjs +20 -0
- package/dist/runtime/nitro/providers/browser/node.mjs +10 -8
- package/dist/runtime/nitro/providers/svg2png/universal.mjs +5 -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/renderers/satori/utils.mjs +1 -0
- 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 +15 -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 +3 -2
- package/dist/runtime/nitro/utils.mjs +9 -17
- package/package.json +19 -18
- package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
- package/dist/client/_nuxt/entry.74018bda.js +0 -5
- package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
package/dist/module.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
|
|
2
|
+
import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent, addServerPlugin } from '@nuxt/kit';
|
|
3
3
|
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,24 +194,24 @@ 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`;
|
|
203
211
|
const PATH_PLAYGROUND = `${PATH}/client`;
|
|
204
212
|
const edgeProvidersSupported = [
|
|
205
213
|
"cloudflare",
|
|
214
|
+
"cloudflare-pages",
|
|
206
215
|
"vercel-edge",
|
|
207
216
|
"netlify-edge"
|
|
208
217
|
];
|
|
@@ -217,6 +226,7 @@ const module = defineNuxtModule({
|
|
|
217
226
|
},
|
|
218
227
|
defaults(nuxt) {
|
|
219
228
|
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
|
|
229
|
+
const isEdgeProvider = edgeProvidersSupported.includes(process.env.NITRO_PRESET || "") || edgeProvidersSupported.includes(nuxt.options.nitro.preset);
|
|
220
230
|
return {
|
|
221
231
|
// when we run `nuxi generate` we need to force prerendering
|
|
222
232
|
forcePrerender: !nuxt.options.dev && nuxt.options._generate,
|
|
@@ -227,11 +237,11 @@ const module = defineNuxtModule({
|
|
|
227
237
|
height: 630
|
|
228
238
|
},
|
|
229
239
|
satoriProvider: true,
|
|
230
|
-
|
|
240
|
+
// disable browser in edge environments
|
|
241
|
+
browserProvider: !isEdgeProvider,
|
|
231
242
|
fonts: [],
|
|
232
243
|
satoriOptions: {},
|
|
233
244
|
experimentalInlineWasm: process.env.NITRO_PRESET === "netlify-edge" || nuxt.options.nitro.preset === "netlify-edge" || false,
|
|
234
|
-
experimentalRuntimeBrowser: false,
|
|
235
245
|
playground: process.env.NODE_ENV === "development" || nuxt.options.dev
|
|
236
246
|
};
|
|
237
247
|
},
|
|
@@ -323,6 +333,7 @@ export {}
|
|
|
323
333
|
});
|
|
324
334
|
const runtimeDir = resolve("./runtime");
|
|
325
335
|
nuxt.options.build.transpile.push(runtimeDir);
|
|
336
|
+
addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
|
|
326
337
|
const moduleAssetDir = resolve("./runtime/public-assets");
|
|
327
338
|
const assetDirs = [
|
|
328
339
|
resolve(nuxt.options.rootDir, nuxt.options.dir.public),
|
|
@@ -337,17 +348,13 @@ export {}
|
|
|
337
348
|
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
338
349
|
inline: [runtimeDir]
|
|
339
350
|
});
|
|
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
351
|
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
|
|
347
352
|
nitroConfig.publicAssets.push({ dir: moduleAssetDir, maxAge: 31536e3 });
|
|
348
353
|
const providerPath = `${runtimeDir}/nitro/providers`;
|
|
354
|
+
const nitroPreset = nuxt.options.nitro.preset || process.env.NITRO_PRESET;
|
|
355
|
+
const isNodeNitroServer = !nitroPreset || nitroPreset === "node";
|
|
349
356
|
if (config.browserProvider) {
|
|
350
|
-
nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev ||
|
|
357
|
+
nitroConfig.virtual["#nuxt-og-image/browser"] = nuxt.options.dev || process.env.prerender || isNodeNitroServer ? `
|
|
351
358
|
import node from '${providerPath}/browser/node'
|
|
352
359
|
|
|
353
360
|
export default async function() {
|
|
@@ -370,14 +377,15 @@ export default async function() {
|
|
|
370
377
|
}`;
|
|
371
378
|
}
|
|
372
379
|
nitroConfig.virtual["#nuxt-og-image/provider"] = `
|
|
373
|
-
import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
|
|
374
|
-
import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
|
|
380
|
+
${config.satoriProvider ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
|
|
381
|
+
${config.browserProvider ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
|
|
375
382
|
|
|
376
383
|
export async function useProvider(provider) {
|
|
377
384
|
if (provider === 'satori')
|
|
378
|
-
return satori
|
|
385
|
+
return ${config.satoriProvider ? "satori" : "null"}
|
|
379
386
|
if (provider === 'browser')
|
|
380
|
-
return browser
|
|
387
|
+
return ${config.browserProvider ? "browser" : "null"}
|
|
388
|
+
return null
|
|
381
389
|
}
|
|
382
390
|
`;
|
|
383
391
|
});
|
|
@@ -392,9 +400,14 @@ export async function useProvider(provider) {
|
|
|
392
400
|
if (useSatoriWasm)
|
|
393
401
|
await copy(resolve("./runtime/public-assets/yoga.wasm"), resolve(_nitro.options.output.serverDir, "yoga.wasm"));
|
|
394
402
|
}
|
|
395
|
-
const
|
|
403
|
+
const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
|
|
404
|
+
const entryFile = typeof configuredEntry === "string" ? configuredEntry : "index.mjs";
|
|
405
|
+
const indexFile = resolve(_nitro.options.output.serverDir, entryFile);
|
|
396
406
|
if (await pathExists(indexFile)) {
|
|
397
|
-
|
|
407
|
+
let indexContents = await readFile(indexFile, "utf-8");
|
|
408
|
+
if (_nitro.options.preset.includes("vercel")) {
|
|
409
|
+
indexContents = indexContents.replace(".cwd(),", '?.cwd || "/",');
|
|
410
|
+
}
|
|
398
411
|
if (!config.experimentalInlineWasm) {
|
|
399
412
|
await writeFile(
|
|
400
413
|
indexFile,
|
|
@@ -421,7 +434,6 @@ export async function useProvider(provider) {
|
|
|
421
434
|
if (!html)
|
|
422
435
|
return;
|
|
423
436
|
const extractedOptions = extractOgImageOptions(html);
|
|
424
|
-
ctx.contents = stripOgImageOptions(html);
|
|
425
437
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
|
|
426
438
|
if (!extractedOptions || routeRules.ogImage === false)
|
|
427
439
|
return;
|
|
@@ -440,21 +452,6 @@ export async function useProvider(provider) {
|
|
|
440
452
|
await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
|
|
441
453
|
if (screenshotQueue.length === 0)
|
|
442
454
|
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
455
|
nitro.logger.info("Ensuring chromium install for og:image generation...");
|
|
459
456
|
const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
|
|
460
457
|
stdio: "inherit"
|
|
@@ -481,6 +478,21 @@ export async function useProvider(provider) {
|
|
|
481
478
|
browser = await createBrowser();
|
|
482
479
|
if (browser) {
|
|
483
480
|
nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
|
|
481
|
+
for (const entry of screenshotQueue) {
|
|
482
|
+
if (entry.route && Object.keys(entry).length === 1) {
|
|
483
|
+
const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
|
|
484
|
+
const extractedOptions = extractOgImageOptions(html);
|
|
485
|
+
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
|
|
486
|
+
Object.assign(entry, {
|
|
487
|
+
// @ts-expect-error runtime
|
|
488
|
+
path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
|
|
489
|
+
...extractedOptions,
|
|
490
|
+
...routeRules.ogImage || {}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (entry.component)
|
|
494
|
+
entry.html = await globalThis.$fetch(entry.path);
|
|
495
|
+
}
|
|
484
496
|
for (const k in screenshotQueue) {
|
|
485
497
|
const entry = screenshotQueue[k];
|
|
486
498
|
const start = Date.now();
|
|
@@ -518,7 +530,7 @@ export async function useProvider(provider) {
|
|
|
518
530
|
}
|
|
519
531
|
screenshotQueue = [];
|
|
520
532
|
};
|
|
521
|
-
if (
|
|
533
|
+
if (nuxt.options._generate) {
|
|
522
534
|
nitro.hooks.hook("rollup:before", async () => {
|
|
523
535
|
await captureScreenshots();
|
|
524
536
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
import type { Buffer } from 'node:buffer';
|
|
2
3
|
import type { Browser } from 'playwright-core';
|
|
3
4
|
import type { ScreenshotOptions } from '../types';
|
|
4
5
|
export declare function screenshot(browser: Browser, options: Partial<ScreenshotOptions> & Record<string, any>): Promise<Buffer>;
|
|
@@ -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,7 @@
|
|
|
1
|
-
import { useServerHead } from "@vueuse/head";
|
|
2
1
|
import { withBase } from "ufo";
|
|
3
2
|
import { useRequestEvent } from "#app";
|
|
4
|
-
import {
|
|
3
|
+
import { useHostname } from "../nitro/util-hostname.mjs";
|
|
4
|
+
import { useRouter, useRuntimeConfig, useServerHead } from "#imports";
|
|
5
5
|
export function defineOgImageScreenshot(options = {}) {
|
|
6
6
|
const router = useRouter();
|
|
7
7
|
const route = router?.currentRoute?.value?.path || "";
|
|
@@ -31,12 +31,11 @@ export function defineOgImageStatic(options = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
export function defineOgImage(options = {}) {
|
|
33
33
|
if (process.server) {
|
|
34
|
-
const {
|
|
34
|
+
const { defaults, siteUrl } = useRuntimeConfig()["nuxt-og-image"];
|
|
35
35
|
const router = useRouter();
|
|
36
36
|
const route = router?.currentRoute?.value?.path || "";
|
|
37
37
|
const e = useRequestEvent();
|
|
38
|
-
|
|
39
|
-
e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
|
|
38
|
+
const baseUrl = process.env.prerender ? siteUrl : useHostname(e);
|
|
40
39
|
const meta = [
|
|
41
40
|
{
|
|
42
41
|
name: "twitter:card",
|
|
@@ -44,11 +43,11 @@ export function defineOgImage(options = {}) {
|
|
|
44
43
|
},
|
|
45
44
|
{
|
|
46
45
|
name: "twitter:image:src",
|
|
47
|
-
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`,
|
|
46
|
+
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
|
|
48
47
|
},
|
|
49
48
|
{
|
|
50
49
|
property: "og:image",
|
|
51
|
-
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`,
|
|
50
|
+
content: () => withBase(`${route === "/" ? "" : route}/__og_image__/og.png`, baseUrl)
|
|
52
51
|
},
|
|
53
52
|
{
|
|
54
53
|
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
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { appendHeader } from "h3";
|
|
2
|
+
import { joinURL } from "ufo";
|
|
3
|
+
import { extractOgImageOptions } from "../utils-pure.mjs";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
const OgImagePrenderNitroPlugin = (nitroApp) => {
|
|
6
|
+
if (!process.env.prerender)
|
|
7
|
+
return;
|
|
8
|
+
const { forcePrerender } = useRuntimeConfig()["nuxt-og-image"];
|
|
9
|
+
nitroApp.hooks.hook("render:html", async (ctx, { event }) => {
|
|
10
|
+
const url = event.node.req.url;
|
|
11
|
+
if (url.includes(".") || url.startsWith("/__nuxt_island/"))
|
|
12
|
+
return;
|
|
13
|
+
const options = extractOgImageOptions(ctx.head.join("\n"));
|
|
14
|
+
if (!options)
|
|
15
|
+
return;
|
|
16
|
+
if ((forcePrerender || options.static) && options.provider === "satori")
|
|
17
|
+
appendHeader(event, "x-nitro-prerender", joinURL(url, "/__og_image__/og.png"));
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
export default OgImagePrenderNitroPlugin;
|
|
@@ -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,11 @@ 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
|
+
if (!e.message.trim().endsWith("function can be used only once."))
|
|
8
|
+
throw e;
|
|
7
9
|
});
|
|
10
|
+
}
|
|
8
11
|
return await svg2png(svg, options);
|
|
9
12
|
}
|
|
@@ -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,15 @@
|
|
|
1
|
+
import { getRequestHost, getRequestProtocol } from "h3";
|
|
2
|
+
import { withBase } from "ufo";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
export function useHostname(e) {
|
|
5
|
+
const base = useRuntimeConfig().app.baseURL;
|
|
6
|
+
let host = getRequestHost(e);
|
|
7
|
+
if (host === "localhost")
|
|
8
|
+
host = process.env.NITRO_HOST || process.env.HOST || host;
|
|
9
|
+
const protocol = getRequestProtocol(e);
|
|
10
|
+
const useHttp = process.dev || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
|
|
11
|
+
let port = host.includes(":") ? host.split(":").pop() : false;
|
|
12
|
+
if ((process.dev || process.env.prerender || host.includes("localhost")) && !port)
|
|
13
|
+
port = process.env.NITRO_PORT || process.env.PORT;
|
|
14
|
+
return withBase(base, `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}`);
|
|
15
|
+
}
|
|
@@ -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;
|