nuxt-og-image 2.0.0-beta.5 → 2.0.0-beta.51
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 +265 -53
- package/dist/client/200.html +2 -2
- package/dist/client/404.html +2 -2
- package/dist/client/_nuxt/IconCSS.73da91e8.js +1 -0
- package/dist/client/_nuxt/IconCSS.8bbd2aa2.css +1 -0
- package/dist/client/_nuxt/ImageLoader.1dc2da65.js +1 -0
- package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
- package/dist/client/_nuxt/entry.9627f7dd.js +5 -0
- package/dist/client/_nuxt/entry.f4586a2b.css +1 -0
- package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.e8b1a738.js} +1 -1
- package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.34be4bd8.js} +1 -1
- package/dist/client/_nuxt/error-component.4733114a.js +3 -0
- package/dist/client/_nuxt/index.35b0e13d.js +1 -0
- package/dist/client/_nuxt/index.403133d8.css +1 -0
- package/dist/client/_nuxt/{options.56a3e5f9.js → options.bd244bf2.js} +1 -1
- package/dist/client/_nuxt/{png.37f3e77b.js → png.765461fb.js} +1 -1
- package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.474ce6f4.js} +1 -1
- package/dist/client/_nuxt/{svg.186c6bd1.js → svg.1511e50d.js} +1 -1
- package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.4f695971.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 +22 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +312 -114
- package/dist/runtime/browserUtil.d.ts +1 -0
- package/dist/runtime/browserUtil.mjs +10 -5
- package/dist/runtime/components/OgImageBasic.island.vue +5 -0
- package/dist/runtime/components/OgImageDynamic.d.ts +1 -1
- package/dist/runtime/components/OgImageScreenshot.d.ts +1 -1
- package/dist/runtime/components/OgImageStatic.d.ts +1 -1
- package/dist/runtime/composables/defineOgImage.mjs +15 -12
- package/dist/runtime/nitro/middleware/og.png.mjs +51 -7
- 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 +26 -0
- package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
- package/dist/runtime/nitro/providers/browser/lambda.mjs +3 -3
- package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
- package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
- package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
- package/dist/runtime/nitro/providers/png/resvg-node.d.ts +5 -0
- package/dist/runtime/nitro/providers/png/resvg-node.mjs +6 -0
- package/dist/runtime/nitro/providers/png/resvg-wasm.d.ts +4 -0
- package/dist/runtime/nitro/providers/png/resvg-wasm.mjs +11 -0
- package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
- package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
- package/dist/runtime/nitro/renderers/browser.d.ts +2 -2
- package/dist/runtime/nitro/renderers/browser.mjs +14 -7
- package/dist/runtime/nitro/renderers/satori/index.d.ts +2 -2
- package/dist/runtime/nitro/renderers/satori/index.mjs +15 -16
- package/dist/runtime/nitro/renderers/satori/plugins/emojis.d.ts +1 -1
- package/dist/runtime/nitro/renderers/satori/plugins/emojis.mjs +24 -9
- package/dist/runtime/nitro/renderers/satori/plugins/encoding.d.ts +1 -1
- package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
- package/dist/runtime/nitro/renderers/satori/plugins/flex.d.ts +1 -1
- package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.d.ts +1 -1
- package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +25 -3
- package/dist/runtime/nitro/renderers/satori/plugins/twClasses.d.ts +1 -1
- package/dist/runtime/nitro/renderers/satori/utils.d.ts +4 -4
- package/dist/runtime/nitro/renderers/satori/utils.mjs +26 -13
- package/dist/runtime/nitro/routes/debug.d.ts +4 -0
- package/dist/runtime/nitro/routes/debug.mjs +9 -0
- package/dist/runtime/nitro/routes/html.mjs +100 -25
- package/dist/runtime/nitro/routes/options.mjs +20 -22
- 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 +20 -0
- package/dist/runtime/nitro/utils-pure.d.ts +3 -2
- package/dist/runtime/nitro/utils-pure.mjs +9 -8
- package/dist/runtime/nitro/utils.d.ts +6 -8
- package/dist/runtime/nitro/utils.mjs +50 -47
- package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
- package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
- package/dist/types.d.ts +6 -0
- package/package.json +37 -24
- package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
- package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
- package/dist/client/_nuxt/entry.74018bda.js +0 -5
- package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
- package/dist/client/_nuxt/error-component.cf7543e5.js +0 -3
- package/dist/client/_nuxt/index.3f356409.js +0 -1
- package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
- /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
- /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
- /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
- /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
- /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
- /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
- /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
- /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
- /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
package/dist/module.mjs
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { defineNuxtModule, createResolver, addTemplate, addServerHandler, addImports, addComponent } from '@nuxt/kit';
|
|
2
|
+
import { defineNuxtModule, useLogger, 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';
|
|
8
|
-
import { resolve, relative } from 'pathe';
|
|
7
|
+
import { withBase, joinURL } from 'ufo';
|
|
8
|
+
import { resolve, relative, dirname } from 'pathe';
|
|
9
9
|
import { tinyws } from 'tinyws';
|
|
10
10
|
import sirv from 'sirv';
|
|
11
11
|
import { pathExists, copy, mkdirp } from 'fs-extra';
|
|
12
|
-
import {
|
|
12
|
+
import { globby } from 'globby';
|
|
13
13
|
import playwrightCore from 'playwright-core';
|
|
14
14
|
import { existsSync } from 'node:fs';
|
|
15
15
|
import { createBirpcGroup } from 'birpc';
|
|
16
16
|
import { stringify, parse } from 'flatted';
|
|
17
|
+
import { addDependency } from 'nypm';
|
|
18
|
+
import { provider } from 'std-env';
|
|
17
19
|
|
|
18
20
|
async function createBrowser() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
if (process.dev || process.env.prerender) {
|
|
22
|
+
try {
|
|
23
|
+
const { Launcher } = await import(String("chrome-launcher"));
|
|
24
|
+
const chromePath = Launcher.getFirstInstallation();
|
|
25
|
+
return await playwrightCore.chromium.launch({
|
|
26
|
+
headless: true,
|
|
27
|
+
executablePath: chromePath
|
|
28
|
+
});
|
|
29
|
+
} catch (e) {
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
try {
|
|
29
33
|
return await playwrightCore.chromium.launch({
|
|
@@ -54,9 +58,9 @@ async function screenshot(browser, options) {
|
|
|
54
58
|
width: options.width || 1200,
|
|
55
59
|
height: options.height || 630
|
|
56
60
|
});
|
|
57
|
-
const isHtml = options.path
|
|
61
|
+
const isHtml = options.html || options.path?.startsWith("html:");
|
|
58
62
|
if (isHtml) {
|
|
59
|
-
const html = options.html || options.path
|
|
63
|
+
const html = options.html || options.path?.substring(5);
|
|
60
64
|
await page.evaluate((html2) => {
|
|
61
65
|
document.open("text/html");
|
|
62
66
|
document.write(html2);
|
|
@@ -65,10 +69,13 @@ async function screenshot(browser, options) {
|
|
|
65
69
|
await page.waitForLoadState("networkidle");
|
|
66
70
|
} else {
|
|
67
71
|
await page.goto(`${options.host}${options.path}`, {
|
|
68
|
-
timeout: 1e4,
|
|
72
|
+
timeout: process.env.prerender || process.dev ? 1e4 : 3500,
|
|
69
73
|
waitUntil: "networkidle"
|
|
70
74
|
});
|
|
71
75
|
}
|
|
76
|
+
const screenshotOptions = {
|
|
77
|
+
timeout: process.env.prerender || process.dev ? 1e4 : 3500
|
|
78
|
+
};
|
|
72
79
|
if (options.delay)
|
|
73
80
|
await page.waitForTimeout(options.delay);
|
|
74
81
|
if (options.mask) {
|
|
@@ -78,8 +85,10 @@ async function screenshot(browser, options) {
|
|
|
78
85
|
}, options.mask);
|
|
79
86
|
}
|
|
80
87
|
if (options.selector)
|
|
81
|
-
return await page.locator(options.selector).screenshot();
|
|
82
|
-
|
|
88
|
+
return await page.locator(options.selector).screenshot(screenshotOptions);
|
|
89
|
+
const screenshot2 = await page.screenshot(screenshotOptions);
|
|
90
|
+
await page.close();
|
|
91
|
+
return screenshot2;
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
function setupPlaygroundRPC(nuxt, config) {
|
|
@@ -113,7 +122,7 @@ function setupPlaygroundRPC(nuxt, config) {
|
|
|
113
122
|
const birpc = createBirpcGroup(serverFunctions, []);
|
|
114
123
|
nuxt.hook("builder:watch", (e, path) => {
|
|
115
124
|
if (e === "change")
|
|
116
|
-
birpc.
|
|
125
|
+
birpc.broadcast.refresh.asEvent(path);
|
|
117
126
|
});
|
|
118
127
|
const middleware = async (req, res) => {
|
|
119
128
|
if (req.ws) {
|
|
@@ -164,11 +173,15 @@ function getBodyJson(req) {
|
|
|
164
173
|
});
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
function
|
|
176
|
+
function decodeHtml(html) {
|
|
177
|
+
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) => {
|
|
178
|
+
return String.fromCharCode(Number.parseInt(int));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function decodeObjectHtmlEntities(obj) {
|
|
168
182
|
Object.entries(obj).forEach(([key, value]) => {
|
|
169
|
-
if (typeof value === "string")
|
|
170
|
-
obj[key] = value
|
|
171
|
-
}
|
|
183
|
+
if (typeof value === "string")
|
|
184
|
+
obj[key] = decodeHtml(value);
|
|
172
185
|
});
|
|
173
186
|
return obj;
|
|
174
187
|
}
|
|
@@ -192,22 +205,120 @@ function extractOgImageOptions(html) {
|
|
|
192
205
|
else
|
|
193
206
|
options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
|
|
194
207
|
}
|
|
195
|
-
return
|
|
208
|
+
return decodeObjectHtmlEntities(options);
|
|
196
209
|
}
|
|
197
210
|
return false;
|
|
198
211
|
}
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
|
|
213
|
+
const SVG2PNGWasmPlaceholder = '"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"';
|
|
214
|
+
const YogaWasmPlaceholder = '"/* NUXT_OG_IMAGE_YOGA_WASM */"';
|
|
215
|
+
const ReSVGWasmPlaceholder = '"/* NUXT_OG_IMAGE_RESVG_WASM */"';
|
|
216
|
+
const Wasms = [
|
|
217
|
+
{
|
|
218
|
+
placeholder: SVG2PNGWasmPlaceholder,
|
|
219
|
+
path: "svg2png/svg2png.wasm",
|
|
220
|
+
file: "svg2png.wasm"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
placeholder: ReSVGWasmPlaceholder,
|
|
224
|
+
path: "resvg/resvg.wasm",
|
|
225
|
+
file: "resvg.wasm"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
placeholder: YogaWasmPlaceholder,
|
|
229
|
+
path: "yoga/yoga.wasm",
|
|
230
|
+
file: "yoga.wasm"
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
const DefaultRuntimeCompatibility = {
|
|
234
|
+
// node-server runtime
|
|
235
|
+
browser: "playwright",
|
|
236
|
+
satori: "default",
|
|
237
|
+
wasm: "fetch",
|
|
238
|
+
png: "resvg-node"
|
|
239
|
+
};
|
|
240
|
+
const RuntimeCompatibility = {
|
|
241
|
+
"nitro-dev": {
|
|
242
|
+
wasm: "fetch",
|
|
243
|
+
browser: "universal"
|
|
244
|
+
},
|
|
245
|
+
"stackblitz": {
|
|
246
|
+
browser: false,
|
|
247
|
+
satori: "yoga-wasm",
|
|
248
|
+
wasm: "inline",
|
|
249
|
+
png: "resvg-wasm"
|
|
250
|
+
},
|
|
251
|
+
"netlify": {
|
|
252
|
+
browser: "lambda",
|
|
253
|
+
wasm: "inline"
|
|
254
|
+
},
|
|
255
|
+
"netlify-edge": {
|
|
256
|
+
wasm: "inline",
|
|
257
|
+
png: "resvg-wasm"
|
|
258
|
+
},
|
|
259
|
+
"vercel": {
|
|
260
|
+
// exceeds 50mb limit
|
|
261
|
+
browser: false
|
|
262
|
+
},
|
|
263
|
+
"vercel-edge": {
|
|
264
|
+
browser: false,
|
|
265
|
+
wasm: "import",
|
|
266
|
+
wasmImportQuery: "?module",
|
|
267
|
+
png: "resvg-wasm"
|
|
268
|
+
},
|
|
269
|
+
"cloudflare-pages": {
|
|
270
|
+
browser: false,
|
|
271
|
+
wasm: "import",
|
|
272
|
+
png: "resvg-wasm"
|
|
273
|
+
},
|
|
274
|
+
"cloudflare": {
|
|
275
|
+
browser: false,
|
|
276
|
+
wasm: "import"
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const autodetectableProviders = {
|
|
281
|
+
azure_static: "azure",
|
|
282
|
+
cloudflare_pages: "cloudflare-pages",
|
|
283
|
+
netlify: "netlify",
|
|
284
|
+
stormkit: "stormkit",
|
|
285
|
+
vercel: "vercel",
|
|
286
|
+
cleavr: "cleavr"
|
|
287
|
+
};
|
|
288
|
+
const autodetectableStaticProviders = {
|
|
289
|
+
netlify: "netlify-static",
|
|
290
|
+
vercel: "vercel-static"
|
|
291
|
+
};
|
|
292
|
+
function detectTarget(options = {}) {
|
|
293
|
+
return options?.static ? autodetectableStaticProviders[provider] : autodetectableProviders[provider];
|
|
294
|
+
}
|
|
295
|
+
function getNitroPreset(nuxt) {
|
|
296
|
+
return process.env.NITRO_PRESET || nuxt.options.nitro.preset || detectTarget() || "node-server";
|
|
297
|
+
}
|
|
298
|
+
function getNitroProviderCompatibility(nuxt) {
|
|
299
|
+
if (provider === "stackblitz")
|
|
300
|
+
return defu(RuntimeCompatibility.stackblitz, DefaultRuntimeCompatibility);
|
|
301
|
+
if (nuxt.options.dev || nuxt.options._prepare || nuxt.options._generate) {
|
|
302
|
+
return defu({
|
|
303
|
+
wasm: "fetch",
|
|
304
|
+
browser: "universal"
|
|
305
|
+
}, DefaultRuntimeCompatibility);
|
|
306
|
+
}
|
|
307
|
+
const target = getNitroPreset(nuxt);
|
|
308
|
+
const compatibility = RuntimeCompatibility[target];
|
|
309
|
+
if (compatibility === false)
|
|
310
|
+
return false;
|
|
311
|
+
return defu(compatibility || {}, DefaultRuntimeCompatibility);
|
|
312
|
+
}
|
|
313
|
+
function ensureDependencies(nuxt, dep) {
|
|
314
|
+
return Promise.all(dep.map((d) => {
|
|
315
|
+
return addDependency(d, { cwd: nuxt.options.rootDir });
|
|
316
|
+
}));
|
|
201
317
|
}
|
|
202
318
|
|
|
203
319
|
const PATH = "/__nuxt_og_image__";
|
|
204
320
|
const PATH_ENTRY = `${PATH}/entry`;
|
|
205
321
|
const PATH_PLAYGROUND = `${PATH}/client`;
|
|
206
|
-
const edgeProvidersSupported = [
|
|
207
|
-
"cloudflare",
|
|
208
|
-
"vercel-edge",
|
|
209
|
-
"netlify-edge"
|
|
210
|
-
];
|
|
211
322
|
const module = defineNuxtModule({
|
|
212
323
|
meta: {
|
|
213
324
|
name: "nuxt-og-image",
|
|
@@ -220,26 +331,54 @@ const module = defineNuxtModule({
|
|
|
220
331
|
defaults(nuxt) {
|
|
221
332
|
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || process.env.NUXT_SITE_URL || nuxt.options.runtimeConfig.public?.siteUrl || nuxt.options.runtimeConfig.siteUrl;
|
|
222
333
|
return {
|
|
223
|
-
// when we run `nuxi generate` we need to force prerendering
|
|
224
|
-
forcePrerender: !nuxt.options.dev && nuxt.options._generate,
|
|
225
334
|
siteUrl,
|
|
226
335
|
defaults: {
|
|
227
336
|
component: "OgImageBasic",
|
|
228
337
|
width: 1200,
|
|
229
|
-
height: 630
|
|
338
|
+
height: 630,
|
|
339
|
+
cacheTtl: 24 * 60 * 60 * 1e3
|
|
340
|
+
// default is to cache the image for 24 hours
|
|
230
341
|
},
|
|
231
|
-
|
|
232
|
-
|
|
342
|
+
runtimeSatori: true,
|
|
343
|
+
runtimeBrowser: nuxt.options.dev,
|
|
233
344
|
fonts: [],
|
|
345
|
+
runtimeCacheStorage: false,
|
|
234
346
|
satoriOptions: {},
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
playground: process.env.NODE_ENV === "development" || nuxt.options.dev
|
|
347
|
+
playground: process.env.NODE_ENV === "development" || nuxt.options.dev,
|
|
348
|
+
debug: false
|
|
238
349
|
};
|
|
239
350
|
},
|
|
240
351
|
async setup(config, nuxt) {
|
|
352
|
+
const logger = useLogger("nuxt-og-image");
|
|
353
|
+
logger.level = config.debug ? 4 : 3;
|
|
241
354
|
const { resolve } = createResolver(import.meta.url);
|
|
355
|
+
logger.debug("Using Nitro preset", getNitroPreset(nuxt));
|
|
356
|
+
const nitroCompatibility = getNitroProviderCompatibility(nuxt);
|
|
357
|
+
logger.debug("Nitro compatibility", nitroCompatibility);
|
|
358
|
+
const nitroTarget = process.env.NITRO_PRESET || nuxt.options.nitro.preset;
|
|
359
|
+
if (!nitroCompatibility) {
|
|
360
|
+
logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\`. Please make an issue. `);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (!nitroCompatibility.browser && config.runtimeBrowser) {
|
|
364
|
+
config.runtimeBrowser = false;
|
|
365
|
+
logger.warn(`\`nuxt-og-image\` does not support the nitro target \`${nitroTarget}\` with the runtime browser. Set runtimeBrowser: false to stop seeing this.`);
|
|
366
|
+
}
|
|
367
|
+
if (config.runtimeBrowser && nitroCompatibility.browser === "lambda") {
|
|
368
|
+
logger.info(`\`nuxt-og-image\` is deploying to nitro target \`${nitroTarget}\` that installs extra dependencies.`);
|
|
369
|
+
await ensureDependencies(nuxt, ["puppeteer-core@14.1.1", "@sparticuz/chrome-aws-lambda@14.1.1"]);
|
|
370
|
+
}
|
|
242
371
|
config.siteUrl = config.siteUrl || config.host;
|
|
372
|
+
if (!nuxt.options.dev && nuxt.options._generate && !config.siteUrl)
|
|
373
|
+
logger.warn("Missing `ogImage.siteUrl` and site is being prerendered. This will result in broken og images.");
|
|
374
|
+
nuxt.options.nitro.storage = nuxt.options.nitro.storage || {};
|
|
375
|
+
if (nuxt.options._generate) {
|
|
376
|
+
nuxt.options.nitro.storage["og-image"] = {
|
|
377
|
+
driver: "memory"
|
|
378
|
+
};
|
|
379
|
+
} else if (config.runtimeCacheStorage && !nuxt.options.dev && typeof config.runtimeCacheStorage === "object") {
|
|
380
|
+
nuxt.options.nitro.storage["og-image"] = config.runtimeCacheStorage;
|
|
381
|
+
}
|
|
243
382
|
if (!config.fonts.length)
|
|
244
383
|
config.fonts = ["Inter:400", "Inter:700"];
|
|
245
384
|
const distResolve = (p) => {
|
|
@@ -271,12 +410,14 @@ export {}
|
|
|
271
410
|
lazy: true,
|
|
272
411
|
handler: resolve("./runtime/nitro/middleware/og.png")
|
|
273
412
|
});
|
|
274
|
-
["html", "options", "svg", "vnode", "font"].forEach((type) => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
413
|
+
["html", "options", "svg", "vnode", "font", "debug"].forEach((type) => {
|
|
414
|
+
if (type !== "debug" || config.debug) {
|
|
415
|
+
addServerHandler({
|
|
416
|
+
lazy: true,
|
|
417
|
+
route: `/api/og-image-${type}`,
|
|
418
|
+
handler: resolve(`./runtime/nitro/routes/${type}`)
|
|
419
|
+
});
|
|
420
|
+
}
|
|
280
421
|
});
|
|
281
422
|
nuxt.hook("devtools:customTabs", (iframeTabs) => {
|
|
282
423
|
iframeTabs.push({
|
|
@@ -325,92 +466,150 @@ export {}
|
|
|
325
466
|
});
|
|
326
467
|
const runtimeDir = resolve("./runtime");
|
|
327
468
|
nuxt.options.build.transpile.push(runtimeDir);
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
469
|
+
addServerPlugin(resolve("./runtime/nitro/plugins/prerender"));
|
|
470
|
+
const customAssetDirs = [
|
|
471
|
+
// allows us to show custom error images
|
|
472
|
+
resolve("./runtime/public-assets")
|
|
332
473
|
];
|
|
474
|
+
if (config.runtimeSatori) {
|
|
475
|
+
if (config.fonts.includes("Inter:400"))
|
|
476
|
+
customAssetDirs.push(resolve("./runtime/public-assets-optional/inter-font"));
|
|
477
|
+
if (nitroCompatibility.png === "resvg-wasm" && nitroCompatibility.wasm === "fetch")
|
|
478
|
+
customAssetDirs.push(resolve("./runtime/public-assets-optional/resvg"));
|
|
479
|
+
else if (nitroCompatibility.png === "svg2png" && nitroCompatibility.wasm === "fetch")
|
|
480
|
+
customAssetDirs.push(resolve("./runtime/public-assets-optional/svg2png"));
|
|
481
|
+
if (nitroCompatibility.satori === "yoga-wasm")
|
|
482
|
+
customAssetDirs.push(resolve("./runtime/public-assets-optional/yoga"));
|
|
483
|
+
}
|
|
333
484
|
nuxt.hooks.hook("modules:done", async () => {
|
|
334
485
|
nuxt.hooks.callHook("og-image:config", config);
|
|
335
|
-
nuxt.options.runtimeConfig["nuxt-og-image"] = {
|
|
486
|
+
nuxt.options.runtimeConfig["nuxt-og-image"] = {
|
|
487
|
+
...config,
|
|
488
|
+
// avoid adding credentials
|
|
489
|
+
runtimeCacheStorage: Boolean(config.runtimeCacheStorage),
|
|
490
|
+
// convert the fonts to uniform type to fix ts issue
|
|
491
|
+
fonts: config.fonts.map((f) => {
|
|
492
|
+
if (typeof f === "string") {
|
|
493
|
+
const [name, weight] = f.split(":");
|
|
494
|
+
return {
|
|
495
|
+
name,
|
|
496
|
+
weight
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return f;
|
|
500
|
+
}),
|
|
501
|
+
assetDirs: [
|
|
502
|
+
resolve(nuxt.options.srcDir, nuxt.options.dir.public),
|
|
503
|
+
...customAssetDirs,
|
|
504
|
+
// always add runtime dirs for prerendering to work, these are just used as scan roots
|
|
505
|
+
resolve("./runtime/public-assets-optional/inter-font"),
|
|
506
|
+
resolve("./runtime/public-assets-optional/resvg"),
|
|
507
|
+
resolve("./runtime/public-assets-optional/yoga"),
|
|
508
|
+
resolve("./runtime/public-assets-optional/svg2png")
|
|
509
|
+
]
|
|
510
|
+
};
|
|
336
511
|
});
|
|
337
|
-
const useSatoriWasm = provider === "stackblitz";
|
|
338
512
|
nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
|
|
339
513
|
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
340
514
|
inline: [runtimeDir]
|
|
341
515
|
});
|
|
342
|
-
if (config.
|
|
516
|
+
if (config.runtimeBrowser) {
|
|
343
517
|
nitroConfig.alias = nitroConfig.alias || {};
|
|
344
518
|
nitroConfig.alias.electron = "unenv/runtime/mock/proxy-cjs";
|
|
345
519
|
nitroConfig.alias.bufferutil = "unenv/runtime/mock/proxy-cjs";
|
|
346
520
|
nitroConfig.alias["utf-8-validate"] = "unenv/runtime/mock/proxy-cjs";
|
|
347
521
|
}
|
|
348
522
|
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
|
|
349
|
-
|
|
523
|
+
customAssetDirs.forEach((dir) => {
|
|
524
|
+
nitroConfig.publicAssets.push({ dir, maxAge: 31536e3 });
|
|
525
|
+
});
|
|
350
526
|
const providerPath = `${runtimeDir}/nitro/providers`;
|
|
351
|
-
if (config.
|
|
352
|
-
nitroConfig.virtual["#nuxt-og-image/browser"] =
|
|
353
|
-
|
|
354
|
-
|
|
527
|
+
if (config.runtimeBrowser) {
|
|
528
|
+
nitroConfig.virtual["#nuxt-og-image/browser"] = `
|
|
529
|
+
let browser
|
|
355
530
|
export default async function() {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
` : `export default async function() {
|
|
359
|
-
return () => {}
|
|
531
|
+
browser = browser || await import('${providerPath}/browser/${nitroCompatibility.browser}').then((m) => m.default || m)
|
|
532
|
+
return browser
|
|
360
533
|
}
|
|
361
534
|
`;
|
|
362
535
|
}
|
|
363
|
-
if (config.
|
|
364
|
-
nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${
|
|
365
|
-
export default
|
|
536
|
+
if (config.runtimeSatori) {
|
|
537
|
+
nitroConfig.virtual["#nuxt-og-image/satori"] = `import satori from '${providerPath}/satori/${nitroCompatibility.satori}'
|
|
538
|
+
export default function() {
|
|
366
539
|
return satori
|
|
367
540
|
}`;
|
|
368
|
-
nitroConfig.virtual["#nuxt-og-image/
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
541
|
+
nitroConfig.virtual["#nuxt-og-image/png"] = `import png from '${providerPath}/png/${nitroCompatibility.png}'
|
|
542
|
+
export default function() {
|
|
543
|
+
return png
|
|
544
|
+
}
|
|
545
|
+
`;
|
|
373
546
|
}
|
|
374
547
|
nitroConfig.virtual["#nuxt-og-image/provider"] = `
|
|
375
|
-
import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'
|
|
376
|
-
import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'
|
|
548
|
+
${config.runtimeSatori ? `import satori from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/satori"))}'` : ""}
|
|
549
|
+
${config.runtimeBrowser ? `import browser from '${relative(nuxt.options.rootDir, resolve("./runtime/nitro/renderers/browser"))}'` : ""}
|
|
377
550
|
|
|
378
551
|
export async function useProvider(provider) {
|
|
379
552
|
if (provider === 'satori')
|
|
380
|
-
return satori
|
|
553
|
+
return ${config.runtimeSatori ? "satori" : "null"}
|
|
381
554
|
if (provider === 'browser')
|
|
382
|
-
return browser
|
|
555
|
+
return ${config.runtimeBrowser ? "browser" : "null"}
|
|
556
|
+
return null
|
|
383
557
|
}
|
|
384
558
|
`;
|
|
385
559
|
});
|
|
386
560
|
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
387
561
|
let screenshotQueue = [];
|
|
388
562
|
nitro.hooks.hook("compiled", async (_nitro) => {
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
563
|
+
if (!config.runtimeSatori || nuxt.options.dev)
|
|
564
|
+
return;
|
|
565
|
+
if (config.fonts.includes("Inter:400"))
|
|
566
|
+
await copy(resolve("./runtime/public-assets-optional/inter-font/inter-latin-ext-400-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-400-normal.woff"));
|
|
567
|
+
if (config.fonts.includes("Inter:700"))
|
|
568
|
+
await copy(resolve("./runtime/public-assets-optional/inter-font/inter-latin-ext-700-normal.woff"), resolve(_nitro.options.output.publicDir, "inter-latin-ext-700-normal.woff"));
|
|
569
|
+
const configuredEntry = nitro.options.rollupConfig?.output.entryFileNames;
|
|
570
|
+
const wasmProviderPath = resolve(_nitro.options.output.serverDir, typeof configuredEntry === "string" ? configuredEntry : "index.mjs");
|
|
571
|
+
const paths = [wasmProviderPath];
|
|
572
|
+
const chunks = await globby([`${_nitro.options.output.serverDir}/chunks/**/*.mjs`], { absolute: true });
|
|
573
|
+
paths.push(...chunks);
|
|
574
|
+
for (const path of paths) {
|
|
575
|
+
if (!await pathExists(path))
|
|
576
|
+
continue;
|
|
577
|
+
let contents = await readFile(path, "utf-8");
|
|
578
|
+
let updated = false;
|
|
579
|
+
if (_nitro.options.preset.includes("vercel") && path === wasmProviderPath) {
|
|
580
|
+
contents = contents.replace(".cwd(),", '?.cwd || "/",');
|
|
581
|
+
updated = true;
|
|
582
|
+
}
|
|
583
|
+
if (_nitro.options.preset.includes("netlify") && path.endsWith("netlify.mjs")) {
|
|
584
|
+
const match = "// TODO: handle event.isBase64Encoded\n });";
|
|
585
|
+
contents = contents.replace(match, `${match}
|
|
586
|
+
|
|
587
|
+
const headers = normalizeOutgoingHeaders(r.headers);
|
|
588
|
+
// image buffers must be base64 encoded
|
|
589
|
+
if (Buffer.isBuffer(r.body) && headers["content-type"].startsWith("image/")) {
|
|
590
|
+
return {
|
|
591
|
+
statusCode: r.status,
|
|
592
|
+
headers,
|
|
593
|
+
body: r.body.toString("base64"),
|
|
594
|
+
isBase64Encoded: true
|
|
595
|
+
};
|
|
596
|
+
}`);
|
|
597
|
+
updated = true;
|
|
396
598
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const yogaWasm = await readFile(resolve("./runtime/public-assets/yoga.wasm"), "base64");
|
|
408
|
-
await writeFile(
|
|
409
|
-
indexFile,
|
|
410
|
-
indexContents.replace('"/* NUXT_OG_IMAGE_SVG2PNG_WASM */"', `Buffer.from("${svg2pngWasm}", "base64")`).replace('"/* NUXT_OG_IMAGE_YOGA_WASM */"', `Buffer.from("${yogaWasm}", "base64")`)
|
|
411
|
-
);
|
|
599
|
+
for (const wasm of Wasms) {
|
|
600
|
+
if (contents.includes(wasm.placeholder)) {
|
|
601
|
+
if (nitroCompatibility.wasm === "import") {
|
|
602
|
+
contents = contents.replace(wasm.placeholder, `import("./${wasm.file}${nitroCompatibility.wasmImportQuery || ""}").then(m => m.default || m)`);
|
|
603
|
+
await copy(resolve(`./runtime/public-assets-optional/${wasm.path}`), resolve(dirname(path), wasm.file));
|
|
604
|
+
} else if (nitroCompatibility.wasm === "inline") {
|
|
605
|
+
const wasmBuffer = await readFile(resolve(`./runtime/public-assets-optional/${wasm.path}`));
|
|
606
|
+
contents = contents.replace(wasm.placeholder, `Buffer.from("${wasmBuffer}", "base64")`);
|
|
607
|
+
}
|
|
608
|
+
updated = true;
|
|
412
609
|
}
|
|
413
610
|
}
|
|
611
|
+
if (updated)
|
|
612
|
+
await writeFile(path, contents, { encoding: "utf-8" });
|
|
414
613
|
}
|
|
415
614
|
});
|
|
416
615
|
const _routeRulesMatcher = toRouteMatcher(
|
|
@@ -423,7 +622,6 @@ export async function useProvider(provider) {
|
|
|
423
622
|
if (!html)
|
|
424
623
|
return;
|
|
425
624
|
const extractedOptions = extractOgImageOptions(html);
|
|
426
|
-
ctx.contents = stripOgImageOptions(html);
|
|
427
625
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
|
|
428
626
|
if (!extractedOptions || routeRules.ogImage === false)
|
|
429
627
|
return;
|
|
@@ -442,21 +640,6 @@ export async function useProvider(provider) {
|
|
|
442
640
|
await nuxt.callHook("og-image:prerenderScreenshots", screenshotQueue);
|
|
443
641
|
if (screenshotQueue.length === 0)
|
|
444
642
|
return;
|
|
445
|
-
for (const entry of screenshotQueue) {
|
|
446
|
-
if (entry.route && Object.keys(entry).length === 1) {
|
|
447
|
-
const html = await $fetch(entry.route);
|
|
448
|
-
const extractedOptions = extractOgImageOptions(html);
|
|
449
|
-
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
|
|
450
|
-
Object.assign(entry, {
|
|
451
|
-
// @ts-expect-error runtime
|
|
452
|
-
path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
|
|
453
|
-
...extractedOptions,
|
|
454
|
-
...routeRules.ogImage || {}
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
if (entry.component)
|
|
458
|
-
entry.html = await $fetch(entry.path);
|
|
459
|
-
}
|
|
460
643
|
nitro.logger.info("Ensuring chromium install for og:image generation...");
|
|
461
644
|
const installChromeProcess = execa("npx", ["playwright", "install", "chromium"], {
|
|
462
645
|
stdio: "inherit"
|
|
@@ -483,12 +666,27 @@ export async function useProvider(provider) {
|
|
|
483
666
|
browser = await createBrowser();
|
|
484
667
|
if (browser) {
|
|
485
668
|
nitro.logger.info(`Prerendering ${screenshotQueue.length} og:image screenshots...`);
|
|
669
|
+
for (const entry of screenshotQueue) {
|
|
670
|
+
if (entry.route && Object.keys(entry).length === 1) {
|
|
671
|
+
const html = await $fetch(entry.route, { baseURL: withBase(nuxt.options.app.baseURL, host) });
|
|
672
|
+
const extractedOptions = extractOgImageOptions(html);
|
|
673
|
+
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(entry.route).reverse());
|
|
674
|
+
Object.assign(entry, {
|
|
675
|
+
// @ts-expect-error runtime
|
|
676
|
+
path: extractedOptions.component ? `/api/og-image-html?path=${entry.route}` : entry.route,
|
|
677
|
+
...extractedOptions,
|
|
678
|
+
...routeRules.ogImage || {}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
if (entry.component)
|
|
682
|
+
entry.html = await globalThis.$fetch(entry.path);
|
|
683
|
+
}
|
|
486
684
|
for (const k in screenshotQueue) {
|
|
487
685
|
const entry = screenshotQueue[k];
|
|
488
686
|
const start = Date.now();
|
|
489
687
|
let hasError = false;
|
|
490
|
-
const
|
|
491
|
-
const filename = joinURL(
|
|
688
|
+
const dirname2 = joinURL(nitro.options.output.publicDir, entry.route, "/__og_image__/");
|
|
689
|
+
const filename = joinURL(dirname2, "/og.png");
|
|
492
690
|
try {
|
|
493
691
|
const imgBuffer = await screenshot(browser, {
|
|
494
692
|
...config.defaults || {},
|
|
@@ -496,7 +694,7 @@ export async function useProvider(provider) {
|
|
|
496
694
|
host
|
|
497
695
|
});
|
|
498
696
|
try {
|
|
499
|
-
await mkdirp(
|
|
697
|
+
await mkdirp(dirname2);
|
|
500
698
|
} catch (e) {
|
|
501
699
|
}
|
|
502
700
|
await writeFile(filename, imgBuffer);
|
|
@@ -520,7 +718,7 @@ export async function useProvider(provider) {
|
|
|
520
718
|
}
|
|
521
719
|
screenshotQueue = [];
|
|
522
720
|
};
|
|
523
|
-
if (
|
|
721
|
+
if (nuxt.options._generate) {
|
|
524
722
|
nitro.hooks.hook("rollup:before", async () => {
|
|
525
723
|
await captureScreenshots();
|
|
526
724
|
});
|
|
@@ -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);
|
|
@@ -17,10 +17,13 @@ export async function screenshot(browser, options) {
|
|
|
17
17
|
await page.waitForLoadState("networkidle");
|
|
18
18
|
} else {
|
|
19
19
|
await page.goto(`${options.host}${options.path}`, {
|
|
20
|
-
timeout: 1e4,
|
|
20
|
+
timeout: process.env.prerender || process.dev ? 1e4 : 3500,
|
|
21
21
|
waitUntil: "networkidle"
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
const screenshotOptions = {
|
|
25
|
+
timeout: process.env.prerender || process.dev ? 1e4 : 3500
|
|
26
|
+
};
|
|
24
27
|
if (options.delay)
|
|
25
28
|
await page.waitForTimeout(options.delay);
|
|
26
29
|
if (options.mask) {
|
|
@@ -30,6 +33,8 @@ 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
|
-
|
|
36
|
+
return await page.locator(options.selector).screenshot(screenshotOptions);
|
|
37
|
+
const screenshot2 = await page.screenshot(screenshotOptions);
|
|
38
|
+
await page.close();
|
|
39
|
+
return screenshot2;
|
|
35
40
|
}
|
|
@@ -32,6 +32,11 @@ const props = defineProps({
|
|
|
32
32
|
},
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
+
// inherited attrs can mess up the satori parser
|
|
36
|
+
defineOptions({
|
|
37
|
+
inheritAttrs: false,
|
|
38
|
+
})
|
|
39
|
+
|
|
35
40
|
const containerAttrs = computed(() => {
|
|
36
41
|
const isBackgroundTw = props.background?.startsWith('bg-')
|
|
37
42
|
const isColorTw = props.color?.startsWith('text-')
|