nuxt-og-image 0.6.0 → 1.0.0-beta.1

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.
Files changed (49) hide show
  1. package/README.md +53 -52
  2. package/dist/client/200.html +7 -0
  3. package/dist/client/404.html +7 -0
  4. package/dist/client/_nuxt/Icon.4a9650c6.js +1 -0
  5. package/dist/client/_nuxt/Icon.e17ad835.css +1 -0
  6. package/dist/client/_nuxt/entry.0827acf4.css +1 -0
  7. package/dist/client/_nuxt/entry.ce848650.js +4 -0
  8. package/dist/client/_nuxt/error-404.556d8899.js +1 -0
  9. package/dist/client/_nuxt/error-404.68aa58b4.css +1 -0
  10. package/dist/client/_nuxt/error-500.70731718.js +1 -0
  11. package/dist/client/_nuxt/error-500.dc5710d1.css +1 -0
  12. package/dist/client/_nuxt/error-component.418ebd67.js +3 -0
  13. package/dist/client/index.html +7 -0
  14. package/dist/module.d.ts +13 -12
  15. package/dist/module.json +1 -1
  16. package/dist/module.mjs +199 -102
  17. package/dist/runtime/components/{OgImage.d.ts → OgImageDynamic.d.ts} +0 -0
  18. package/dist/runtime/components/OgImageDynamic.mjs +9 -0
  19. package/dist/runtime/components/OgImageStatic.d.ts +5 -0
  20. package/dist/runtime/components/{OgImage.mjs → OgImageStatic.mjs} +3 -3
  21. package/dist/runtime/components/OgImageTemplate.island.vue +8 -83
  22. package/dist/runtime/composables/defineOgImage.d.ts +2 -0
  23. package/dist/runtime/composables/defineOgImage.mjs +34 -10
  24. package/dist/runtime/{browsers → nitro/browsers}/default.d.ts +0 -0
  25. package/dist/runtime/{browsers → nitro/browsers}/default.mjs +0 -0
  26. package/dist/runtime/{browsers → nitro/browsers}/lambda.d.ts +0 -0
  27. package/dist/runtime/{browsers → nitro/browsers}/lambda.mjs +0 -0
  28. package/dist/runtime/nitro/providers/browser.d.ts +3 -0
  29. package/dist/runtime/nitro/providers/browser.mjs +16 -0
  30. package/dist/runtime/nitro/providers/satori.d.ts +3 -0
  31. package/dist/runtime/nitro/providers/satori.mjs +48 -0
  32. package/dist/runtime/nitro/routes/__og_image__/html.d.ts +2 -0
  33. package/dist/runtime/nitro/routes/__og_image__/html.mjs +43 -0
  34. package/dist/runtime/nitro/routes/__og_image__/index.d.ts +2 -0
  35. package/dist/runtime/nitro/routes/__og_image__/index.mjs +26 -0
  36. package/dist/runtime/nitro/routes/__og_image__/og.png.d.ts +2 -0
  37. package/dist/runtime/nitro/routes/__og_image__/og.png.mjs +23 -0
  38. package/dist/runtime/nitro/{html.d.ts → routes/__og_image__/payload.d.ts} +2 -1
  39. package/dist/runtime/nitro/routes/__og_image__/payload.mjs +53 -0
  40. package/dist/runtime/nitro/routes/__og_image__/svg.d.ts +2 -0
  41. package/dist/runtime/nitro/routes/__og_image__/svg.mjs +16 -0
  42. package/dist/runtime/nitro/utils.d.ts +8 -0
  43. package/dist/runtime/nitro/utils.mjs +17 -0
  44. package/dist/runtime/public/inter-latin-ext-400-normal.woff +0 -0
  45. package/dist/runtime/public/inter-latin-ext-700-normal.woff +0 -0
  46. package/package.json +18 -7
  47. package/dist/runtime/nitro/html.mjs +0 -55
  48. package/dist/runtime/nitro/image.d.ts +0 -3
  49. package/dist/runtime/nitro/image.mjs +0 -18
package/dist/module.mjs CHANGED
@@ -1,13 +1,16 @@
1
- import { readFile, writeFile, rm, mkdir } from 'node:fs/promises';
2
- import { defineNuxtModule, createResolver, addTemplate, getNuxtVersion, addServerHandler, addImports, addComponent } from '@nuxt/kit';
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { useNuxt, addTemplate, defineNuxtModule, createResolver, addServerHandler, addImports, addComponent } from '@nuxt/kit';
3
4
  import { execa } from 'execa';
4
- import { hash } from 'ohash';
5
5
  import chalk from 'chalk';
6
6
  import defu from 'defu';
7
7
  import { toRouteMatcher, createRouter } from 'radix3';
8
- import { withBase, joinURL } from 'ufo';
9
- import fg from 'fast-glob';
10
- import { join } from 'pathe';
8
+ import { joinURL } from 'ufo';
9
+ import { resolve, relative } from 'pathe';
10
+ import { tinyws } from 'tinyws';
11
+ import sirv from 'sirv';
12
+ import { createBirpcGroup } from 'birpc';
13
+ import { stringify, parse } from 'flatted';
11
14
 
12
15
  async function createBrowser() {
13
16
  try {
@@ -54,19 +57,104 @@ async function screenshot(browser, url, options) {
54
57
  return await page.screenshot();
55
58
  }
56
59
 
57
- const HtmlRendererRoute = "__og_image";
58
60
  const PayloadScriptId = "nuxt-og-image-payload";
59
- const MetaOgImageContentPlaceholder = "__NUXT_OG_IMAGE_PLACEHOLDER__";
60
- const LinkPrerenderId = "nuxt-og-image-screenshot-path";
61
- const DefaultRuntimeImageSuffix = "og-image.png";
62
61
  const Constants = {
63
- HtmlRendererRoute,
64
- PayloadScriptId,
65
- MetaOgImageContentPlaceholder,
66
- LinkPrerenderId,
67
- DefaultRuntimeImageSuffix
62
+ PayloadScriptId
68
63
  };
69
64
 
65
+ function exposeConfig(alias, filename, config) {
66
+ const exports = Object.entries(config).map(([k, v]) => `export const ${k} = '${v}'`).join("\n");
67
+ useNuxt().options.alias[alias] = addTemplate({
68
+ filename,
69
+ getContents: () => exports
70
+ }).dst;
71
+ useNuxt().hooks.hook("nitro:config", (nitroConfig) => {
72
+ nitroConfig.virtual[alias] = exports;
73
+ });
74
+ }
75
+
76
+ function setupPlaygroundRPC(nuxt, config) {
77
+ const serverFunctions = {
78
+ getConfig() {
79
+ return config;
80
+ },
81
+ async openInEditor(input) {
82
+ if (input.startsWith("./"))
83
+ input = resolve(process.cwd(), input);
84
+ const match = input.match(/^(.*?)([:\d]*)$/);
85
+ let suffix = "";
86
+ if (match) {
87
+ input = match[1];
88
+ suffix = match[2];
89
+ }
90
+ const file = [
91
+ input,
92
+ `${input}.js`,
93
+ `${input}.mjs`,
94
+ `${input}.ts`
95
+ ].find((i) => existsSync(i));
96
+ if (file) {
97
+ await import('launch-editor').then((r) => (r.default || r)(file + suffix));
98
+ } else {
99
+ console.error("File not found:", input);
100
+ }
101
+ }
102
+ };
103
+ const clients = /* @__PURE__ */ new Set();
104
+ const birpc = createBirpcGroup(serverFunctions, []);
105
+ nuxt.hook("builder:watch", (e, path) => {
106
+ if (e === "change")
107
+ birpc.boardcast.refresh.asEvent(path);
108
+ });
109
+ const middleware = async (req, res) => {
110
+ if (req.ws) {
111
+ const ws = await req.ws();
112
+ clients.add(ws);
113
+ const channel = {
114
+ post: (d) => ws.send(d),
115
+ on: (fn) => ws.on("message", fn),
116
+ serialize: stringify,
117
+ deserialize: parse
118
+ };
119
+ birpc.updateChannels((c) => {
120
+ c.push(channel);
121
+ });
122
+ ws.on("close", () => {
123
+ clients.delete(ws);
124
+ birpc.updateChannels((c) => {
125
+ const index = c.indexOf(channel);
126
+ if (index >= 0)
127
+ c.splice(index, 1);
128
+ });
129
+ });
130
+ } else if (req.method === "POST") {
131
+ const body = await getBodyJson(req);
132
+ if (body.method === "setPayload") ; else {
133
+ res.statusCode = 400;
134
+ }
135
+ res.end();
136
+ }
137
+ };
138
+ return {
139
+ middleware,
140
+ birpc
141
+ };
142
+ }
143
+ function getBodyJson(req) {
144
+ return new Promise((resolve2, reject) => {
145
+ let body = "";
146
+ req.on("data", (chunk) => body += chunk);
147
+ req.on("error", reject);
148
+ req.on("end", () => {
149
+ try {
150
+ resolve2(JSON.parse(body) || {});
151
+ } catch (e) {
152
+ reject(e);
153
+ }
154
+ });
155
+ });
156
+ }
157
+
70
158
  function extractOgPayload(html) {
71
159
  const payload = html.match(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.+?)<\/script>`))?.[1];
72
160
  if (payload) {
@@ -74,6 +162,9 @@ function extractOgPayload(html) {
74
162
  }
75
163
  return false;
76
164
  }
165
+ const PATH = "/__nuxt_og_image__";
166
+ const PATH_ENTRY = `${PATH}/entry`;
167
+ const PATH_PLAYGROUND = `${PATH}/client`;
77
168
  const module = defineNuxtModule({
78
169
  meta: {
79
170
  name: "nuxt-og-image",
@@ -85,57 +176,74 @@ const module = defineNuxtModule({
85
176
  },
86
177
  defaults(nuxt) {
87
178
  return {
179
+ experimentalNitroBrowser: false,
180
+ forcePrerender: !nuxt.options.dev && nuxt.options._generate,
88
181
  host: nuxt.options.runtimeConfig.public?.siteUrl,
89
182
  width: 1200,
90
- height: 630,
91
- defaultIslandComponent: "OgImageTemplate",
92
- outputDir: "_og-images",
93
- serverSideRender: nuxt.options.dev || (process.env.NITRO_PRESET || "").includes("edge")
183
+ height: 630
94
184
  };
95
185
  },
96
186
  async setup(config, nuxt) {
97
187
  const { resolve } = createResolver(import.meta.url);
188
+ const distResolve = (p) => {
189
+ const cwd = resolve(".");
190
+ if (cwd.endsWith("/dist"))
191
+ return resolve(p);
192
+ return resolve(`../dist/${p}`);
193
+ };
98
194
  nuxt.options.experimental.componentIslands = true;
99
195
  const isEdge = (process.env.NITRO_PRESET || "").includes("edge");
100
196
  addTemplate({
101
197
  filename: "nuxt-og-image.d.ts",
102
198
  getContents: () => {
103
199
  return `// Generated by nuxt-og-image
200
+ interface NuxtOgImageNitroRules {
201
+ ogImage?: false | Record<string, any>
202
+ }
104
203
  declare module 'nitropack' {
105
- interface NitroRouteRules {
106
- ogImage?: 'screenshot' | string | false
107
- }
204
+ interface NitroRouteRules extends NuxtOgImageNitroRules {}
205
+ interface NitroRouteConfig extends NuxtOgImageNitroRules {}
108
206
  }
207
+ export {}
109
208
  `;
110
209
  }
111
210
  });
112
211
  nuxt.hooks.hook("prepare:types", ({ references }) => {
113
212
  references.push({ path: resolve(nuxt.options.buildDir, "nuxt-og-image.d.ts") });
114
213
  });
115
- if (getNuxtVersion(nuxt) !== "3.0.0") {
214
+ ["html", "payload", "svg", "og.png"].forEach((type) => {
116
215
  addServerHandler({
117
- handler: resolve("./runtime/nitro/html")
216
+ handler: resolve(`./runtime/nitro/routes/__og_image__/${type}`)
118
217
  });
119
- if (config.serverSideRender) {
120
- addServerHandler({
121
- handler: resolve("./runtime/nitro/image")
122
- });
123
- }
124
- }
125
- addImports({
126
- name: "defineOgImage",
127
- from: resolve("./runtime/composables/defineOgImage")
128
218
  });
129
- addImports({
130
- name: "defineOgImageScreenshot",
131
- from: resolve("./runtime/composables/defineOgImage")
219
+ if (nuxt.options.dev) {
220
+ const playgroundDir = distResolve("./client");
221
+ const {
222
+ middleware: rpcMiddleware
223
+ } = setupPlaygroundRPC(nuxt, config);
224
+ nuxt.hook("vite:serverCreated", (server) => {
225
+ server.middlewares.use(PATH_ENTRY, tinyws());
226
+ server.middlewares.use(PATH_ENTRY, rpcMiddleware);
227
+ if (existsSync(playgroundDir))
228
+ server.middlewares.use(PATH_PLAYGROUND, sirv(playgroundDir, { single: true, dev: true }));
229
+ });
230
+ addServerHandler({
231
+ handler: resolve("./runtime/nitro/routes/__og_image__/index")
232
+ });
233
+ }
234
+ ["defineOgImageDynamic", "defineOgImageStatic", "defineOgImageScreenshot"].forEach((name) => {
235
+ addImports({
236
+ name,
237
+ from: resolve("./runtime/composables/defineOgImage")
238
+ });
132
239
  });
133
240
  await addComponent({
134
241
  name: "OgImageTemplate",
135
242
  filePath: resolve("./runtime/components/OgImageTemplate.island.vue"),
243
+ global: true,
136
244
  island: true
137
245
  });
138
- ["OgImage", "OgImageScreenshot"].forEach((name) => {
246
+ ["OgImageStatic", "OgImageDynamic", "OgImageScreenshot"].forEach((name) => {
139
247
  addComponent({
140
248
  name,
141
249
  filePath: resolve(`./runtime/components/${name}`),
@@ -144,67 +252,65 @@ declare module 'nitropack' {
144
252
  });
145
253
  const runtimeDir = resolve("./runtime");
146
254
  nuxt.options.build.transpile.push(runtimeDir);
147
- const constScript = Object.entries(Constants).map(([k, v]) => `export const ${k} = '${v}'`).join("\n");
148
- nuxt.options.alias["#nuxt-og-image/constants"] = addTemplate({
149
- filename: "nuxt-og-image-constants.mjs",
150
- getContents: () => constScript
151
- }).dst;
255
+ exposeConfig("#nuxt-og-image/constants", "nuxt-og-image-constants.mjs", Constants);
256
+ exposeConfig("#nuxt-og-image/config", "nuxt-og-image-config.mjs", config);
152
257
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
153
258
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
154
259
  inline: [runtimeDir]
155
260
  });
156
- nitroConfig.virtual["#nuxt-og-image/constants"] = constScript;
157
- nitroConfig.virtual["#nuxt-og-image/browser"] = `export { createBrowser } from '${runtimeDir}/browsers/${isEdge ? "lambda" : "default"}'`;
158
- if (isEdge) {
159
- ["puppeteer", "bufferutil", "utf-8-validate"].forEach((name) => {
160
- nitroConfig.alias[name] = "unenv/runtime/mock/proxy";
161
- });
261
+ nitroConfig.publicAssets = nitroConfig.publicAssets || [];
262
+ nitroConfig.publicAssets.push({ dir: resolve("./runtime/public"), maxAge: 31536e3 });
263
+ nitroConfig.virtual["#nuxt-og-image/browser"] = `export { createBrowser } from '${runtimeDir}/nitro/browsers/${isEdge ? "lambda" : "default"}'`;
264
+ nitroConfig.virtual["#nuxt-og-image/provider"] = `
265
+ import satori from '${runtimeDir}/nitro/providers/satori'
266
+ import browser from '${runtimeDir}/nitro/providers/browser'
267
+
268
+ export function useProvider(provider) {
269
+ if (provider === 'satori')
270
+ return satori
271
+ if (provider === 'browser')
272
+ return browser
273
+ }
274
+ `;
275
+ if (config.experimentalNitroBrowser) {
276
+ nitroConfig.virtual["#nuxt-og-image/providers/browser"] = `export * from '${runtimeDir}/nitro/providers/browser'`;
277
+ if (isEdge) {
278
+ ["puppeteer", "bufferutil", "utf-8-validate"].forEach((name) => {
279
+ nitroConfig.alias[name] = "unenv/runtime/mock/proxy";
280
+ });
281
+ }
162
282
  }
163
283
  });
164
284
  nuxt.hooks.hook("nitro:init", async (nitro) => {
165
- let entries = [];
166
- let cleanupEntries = [];
285
+ let screenshotQueue = [];
167
286
  const _routeRulesMatcher = toRouteMatcher(
168
287
  createRouter({ routes: nitro.options.routeRules })
169
288
  );
170
- const outputPath = join(nitro.options.output.publicDir, config.outputDir);
171
289
  nitro.hooks.hook("prerender:generate", async (ctx) => {
172
- if (ctx.route.includes(".") || ctx.route.endsWith(HtmlRendererRoute))
290
+ if (ctx.route.includes(".") || ctx.route.endsWith("__og_image__/html"))
173
291
  return;
174
- let html = ctx.contents;
292
+ const html = ctx.contents;
175
293
  if (!html)
176
294
  return;
177
- if (!html.includes(`id="${PayloadScriptId}"`))
178
- return;
295
+ const extractedPayload = extractOgPayload(html);
296
+ ctx.contents = html.replace(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.*?)<\/script>`), "");
179
297
  const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
180
- if (routeRules.ogImage === false)
298
+ if (!extractedPayload || routeRules.ogImage === false)
181
299
  return;
182
- const screenshotPath = ctx._contents.match(new RegExp(`<link id="${LinkPrerenderId}" rel="prerender" href="(.*?)">`))?.[1];
183
- const fileName = `${hash({ route: ctx.route })}.png`;
184
- const absoluteUrl = withBase(`${config.outputDir}/${fileName}`, config.host);
185
- const entry = {
186
- fileName,
187
- absoluteUrl,
188
- outputPath: joinURL(nitro.options.output.publicDir, config.outputDir, fileName),
189
- linkingHtml: joinURL(nitro.options.output.publicDir, ctx.fileName),
190
- route: ctx.route,
191
- payload: extractOgPayload(ctx._contents),
192
- routeRules: routeRules.ogImage || "",
193
- screenshotPath: screenshotPath || ctx.route
300
+ const payload = {
301
+ path: ctx.route,
302
+ ...extractedPayload,
303
+ ...routeRules.ogImage || {},
304
+ ctx
194
305
  };
195
- entries.push(entry);
196
- html = html.replace(MetaOgImageContentPlaceholder, entry.absoluteUrl);
197
- ctx.contents = html;
306
+ if ((nuxt.options._generate || payload.prerender) && payload.provider === "browser")
307
+ screenshotQueue.push(payload);
198
308
  });
199
309
  if (nuxt.options.dev)
200
310
  return;
201
- const outputOgImages = async () => {
202
- if (entries.length === 0)
311
+ const captureScreenshots = async () => {
312
+ if (screenshotQueue.length === 0)
203
313
  return;
204
- try {
205
- await mkdir(outputPath, { recursive: true });
206
- } catch (e) {
207
- }
208
314
  const previewProcess = execa("npx", ["serve", nitro.options.output.publicDir]);
209
315
  let browser = null;
210
316
  try {
@@ -218,24 +324,30 @@ declare module 'nitropack' {
218
324
  })).trim();
219
325
  browser = await createBrowser();
220
326
  if (browser) {
221
- nitro.logger.info(`Generating ${entries.length} og:images...`);
222
- for (const k in entries) {
223
- const entry = entries[k];
327
+ nitro.logger.info(`Pre-rendering ${screenshotQueue.length} og:image screenshots...`);
328
+ for (const k in screenshotQueue) {
329
+ const entry = screenshotQueue[k];
224
330
  const start = Date.now();
225
331
  let hasError = false;
332
+ const dirname = joinURL(nitro.options.output.publicDir, `${entry.ctx.fileName.replace("index.html", "")}__og_image__/`);
333
+ const filename = joinURL(dirname, "/og.png");
226
334
  try {
227
- const imgBuffer = await screenshot(browser, `${host}${entry.screenshotPath}`, {
335
+ const imgBuffer = await screenshot(browser, `${host}${entry.path}`, {
228
336
  ...config,
229
- ...entry.payload || {}
337
+ ...entry
230
338
  });
231
- await writeFile(entry.outputPath, imgBuffer);
339
+ try {
340
+ await mkdir(dirname, { recursive: true });
341
+ } catch (e) {
342
+ }
343
+ await writeFile(filename, imgBuffer);
232
344
  } catch (e) {
233
345
  hasError = true;
234
346
  console.error(e);
235
347
  }
236
348
  const generateTimeMS = Date.now() - start;
237
349
  nitro.logger.log(chalk[hasError ? "red" : "gray"](
238
- ` ${Number(k) === entries.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} /${config.outputDir}/${entry.fileName} (${generateTimeMS}ms) ${Math.round(Number(k) / (entries.length - 1) * 100)}%`
350
+ ` ${Number(k) === screenshotQueue.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} ${relative(nitro.options.output.publicDir, filename)} (${generateTimeMS}ms) ${Math.round((Number(k) + 1) / screenshotQueue.length * 100)}%`
239
351
  ));
240
352
  }
241
353
  } else {
@@ -247,28 +359,13 @@ declare module 'nitropack' {
247
359
  await browser?.close();
248
360
  previewProcess.kill();
249
361
  }
250
- cleanupEntries = [...entries];
251
- entries = [];
362
+ screenshotQueue = [];
252
363
  };
253
364
  nitro.hooks.hook("rollup:before", async () => {
254
- await outputOgImages();
365
+ await captureScreenshots();
255
366
  });
256
367
  nitro.hooks.hook("close", async () => {
257
- await outputOgImages();
258
- for (const entry of cleanupEntries) {
259
- try {
260
- const html = await readFile(entry.linkingHtml, "utf-8");
261
- const newHtml = html.replace("__OG_IMAGE_SCREENSHOT_ALT", `Web page screenshot of ${entry.route}.`).replace(new RegExp(`<link id="${LinkPrerenderId}" rel="prerender" href="(.*?)">`), "").replace(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.*?)<\/script>`), "").replace("\n\n", "\n");
262
- if (html !== newHtml) {
263
- await writeFile(entry.linkingHtml, newHtml, { encoding: "utf-8" });
264
- }
265
- } catch (e) {
266
- console.error(e);
267
- }
268
- }
269
- const ogImageFolders = await fg([`**/${HtmlRendererRoute}`], { cwd: nitro.options.output.publicDir, onlyDirectories: true });
270
- for (const ogImageFolder of ogImageFolders)
271
- await rm(join(nitro.options.output.publicDir, ogImageFolder), { recursive: true, force: true });
368
+ await captureScreenshots();
272
369
  });
273
370
  });
274
371
  }
@@ -0,0 +1,9 @@
1
+ import { defineComponent } from "vue";
2
+ import { defineOgImageDynamic } from "#imports";
3
+ export default defineComponent({
4
+ name: "OgImageDynamic",
5
+ setup(_, { attrs }) {
6
+ defineOgImageDynamic(attrs);
7
+ return () => null;
8
+ }
9
+ });
@@ -0,0 +1,5 @@
1
+ import type { OgImagePayload } from '../../types';
2
+ declare const _default: import("vue").DefineComponent<OgImagePayload, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<OgImagePayload>>, {
3
+ [x: string]: any;
4
+ }>;
5
+ export default _default;
@@ -1,9 +1,9 @@
1
1
  import { defineComponent } from "vue";
2
- import { defineOgImage } from "#imports";
2
+ import { defineOgImageStatic } from "#imports";
3
3
  export default defineComponent({
4
- name: "OgImage",
4
+ name: "OgImageStatic",
5
5
  setup(_, { attrs }) {
6
- defineOgImage(attrs);
6
+ defineOgImageStatic(attrs);
7
7
  return () => null;
8
8
  }
9
9
  });
@@ -7,89 +7,14 @@ const props = defineProps({
7
7
  </script>
8
8
 
9
9
  <template>
10
- <div class="wrap">
11
- <div class="bg1" />
12
- <div class="bg2" />
13
- <div>
14
- <p>This is the default og:image template from <a href="https://github.com/harlan-zw/nuxt-og-image" target="_blank">nuxt-og-image</a>.</p>
15
- <p>Create your own at <code>components/islands/OgImageTemplate.vue</code>.</p>
16
- </div>
17
- <div>
18
- <strong>Payload</strong>
19
- <code>
20
- <pre>{{ props }}</pre>
21
- </code>
10
+ <div :style="{ padding: '0 60px', width: '100%', height: '100%', backgroundColor: '#0c0c0c', backgroundImage: 'linear-gradient(to bottom, #dbf4ff, #fff1f1)', display: 'flex', alignItems: 'center' }">
11
+ <div :style="{ padding: '0 30px', display: 'flex', flexDirection: 'column' }">
12
+ <p :style="{ fontSize: '60px', fontWeight: 'bold', marginBottom: '20px' }">
13
+ {{ title }}
14
+ </p>
15
+ <p :style="{ fontSize: '26px' }">
16
+ {{ description }}
17
+ </p>
22
18
  </div>
23
19
  </div>
24
20
  </template>
25
-
26
- <style scoped>
27
- .wrap {
28
- width: 100%;
29
- height: 100%;
30
- display: flex;
31
- align-items: center;
32
- flex-direction: column;
33
- color: white;
34
- font-weight: bold;
35
- font-family: sans-serif;
36
- background-color: #0c0c0c;
37
- position: relative;
38
- }
39
-
40
- .bg1 {
41
- top: 0;
42
- left: 0;
43
- display: block;
44
- position: absolute;
45
- width: 100%;
46
- height: 100%;
47
- padding: 0 !important;
48
- margin: 0 !important;
49
- background-color: #0c0c0c;
50
- }
51
-
52
- .bg2 {
53
- top: 0;
54
- left: 0;
55
- z-index: 1;
56
- display: block;
57
- position: absolute;
58
- width: 100%;
59
- height: 100%;
60
- padding: 0 !important;
61
- margin: 0 !important;
62
- background: radial-gradient(at 100% 100%, #0f766e, rgba(12, 12, 12, 0.1) 60%);
63
- }
64
-
65
- a {
66
- color: inherit;
67
- padding-bottom: 3px;
68
- text-decoration: none;
69
- border-bottom: 3px solid #ff8235;
70
- }
71
-
72
- .wrap > div {
73
- z-index: 2;
74
- padding: 2rem;
75
- }
76
-
77
- code pre {
78
- background: #333;
79
- color: white;
80
- padding: 1rem;
81
- border-radius: 0.5rem;
82
- font-weight: lighter;
83
- font-size: 1.1rem;
84
- }
85
-
86
- p {
87
- font-size: 1.5em;
88
- font-weight: normal;
89
- }
90
-
91
- h1 {
92
- font-size: 4rem;
93
- margin: 0;
94
- }
95
- </style>
@@ -1,3 +1,5 @@
1
1
  import type { OgImagePayload, OgImageScreenshotPayload } from '../../types';
2
2
  export declare function defineOgImageScreenshot(options?: OgImageScreenshotPayload): void;
3
+ export declare function defineOgImageDynamic(options?: OgImageScreenshotPayload): void;
4
+ export declare function defineOgImageStatic(options?: OgImageScreenshotPayload): void;
3
5
  export declare function defineOgImage(options?: OgImagePayload): void;
@@ -1,9 +1,29 @@
1
1
  import { useServerHead } from "@vueuse/head";
2
+ import { withBase } from "ufo";
3
+ import { useRequestEvent } from "#app";
2
4
  import { useRouter } from "#imports";
3
- import { DefaultRuntimeImageSuffix, HtmlRendererRoute, LinkPrerenderId, MetaOgImageContentPlaceholder, PayloadScriptId } from "#nuxt-og-image/constants";
5
+ import { PayloadScriptId } from "#nuxt-og-image/constants";
6
+ import { forcePrerender, height, host, width } from "#nuxt-og-image/config";
4
7
  export function defineOgImageScreenshot(options = {}) {
8
+ const router = useRouter();
9
+ const route = router?.currentRoute?.value?.path || "";
5
10
  defineOgImage({
6
- alt: "__OG_IMAGE_SCREENSHOT_ALT",
11
+ alt: `Web page screenshot${route ? ` of ${route}` : ""}.`,
12
+ provider: "browser",
13
+ prerender: true,
14
+ ...options
15
+ });
16
+ }
17
+ export function defineOgImageDynamic(options = {}) {
18
+ defineOgImage({
19
+ provider: "satori",
20
+ ...options
21
+ });
22
+ }
23
+ export function defineOgImageStatic(options = {}) {
24
+ defineOgImage({
25
+ provider: "satori",
26
+ prerender: true,
7
27
  ...options
8
28
  });
9
29
  }
@@ -11,6 +31,9 @@ export function defineOgImage(options = {}) {
11
31
  if (process.server) {
12
32
  const router = useRouter();
13
33
  const route = router?.currentRoute?.value?.path || "";
34
+ const e = useRequestEvent();
35
+ if ((forcePrerender || options.prerender) && options.provider === "satori")
36
+ e.res.setHeader("x-nitro-prerender", `${route === "/" ? "" : route}/__og_image__/og.png`);
14
37
  const meta = [
15
38
  {
16
39
  property: "twitter:card",
@@ -18,7 +41,15 @@ export function defineOgImage(options = {}) {
18
41
  },
19
42
  {
20
43
  property: "og:image",
21
- content: () => options.runtime ? `${route}/${DefaultRuntimeImageSuffix}` : MetaOgImageContentPlaceholder
44
+ content: () => withBase(`${route}/__og_image__/og.png`, host)
45
+ },
46
+ {
47
+ property: "og:image:width",
48
+ content: width
49
+ },
50
+ {
51
+ property: "og:image:height",
52
+ content: height
22
53
  }
23
54
  ];
24
55
  if (options.alt) {
@@ -29,13 +60,6 @@ export function defineOgImage(options = {}) {
29
60
  }
30
61
  useServerHead({
31
62
  meta,
32
- link: !options.runtime && options.component ? [
33
- {
34
- id: LinkPrerenderId,
35
- rel: "prerender",
36
- href: `${route}/${HtmlRendererRoute}`
37
- }
38
- ] : [],
39
63
  script: [
40
64
  {
41
65
  id: PayloadScriptId,
@@ -0,0 +1,3 @@
1
+ import type { Provider } from '../../../types';
2
+ declare const _default: Provider;
3
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { screenshot } from "../../browserUtil.mjs";
2
+ import { height, width } from "#nuxt-og-image/config";
3
+ import { createBrowser } from "#nuxt-og-image/browser";
4
+ export default {
5
+ name: "browser",
6
+ createSvg: function createSvg() {
7
+ throw new Error("Browser provider isn't able to create SVGs.");
8
+ },
9
+ createPng: async function createPng(basePath) {
10
+ const browser = await createBrowser();
11
+ return screenshot(browser, basePath, {
12
+ width: Number(width),
13
+ height: Number(height)
14
+ });
15
+ }
16
+ };
@@ -0,0 +1,3 @@
1
+ import type { Provider } from '../../../types';
2
+ declare const _default: Provider;
3
+ export default _default;