nuxt-og-image 2.0.0-beta.5 → 2.0.0-beta.50

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 (95) hide show
  1. package/README.md +265 -53
  2. package/dist/client/200.html +2 -2
  3. package/dist/client/404.html +2 -2
  4. package/dist/client/_nuxt/IconCSS.73da91e8.js +1 -0
  5. package/dist/client/_nuxt/IconCSS.8bbd2aa2.css +1 -0
  6. package/dist/client/_nuxt/ImageLoader.1dc2da65.js +1 -0
  7. package/dist/client/_nuxt/ImageLoader.7571516f.css +1 -0
  8. package/dist/client/_nuxt/entry.9627f7dd.js +5 -0
  9. package/dist/client/_nuxt/entry.f4586a2b.css +1 -0
  10. package/dist/client/_nuxt/{error-404.1ff52902.js → error-404.e8b1a738.js} +1 -1
  11. package/dist/client/_nuxt/{error-500.f7d30da5.js → error-500.34be4bd8.js} +1 -1
  12. package/dist/client/_nuxt/error-component.4733114a.js +3 -0
  13. package/dist/client/_nuxt/index.35b0e13d.js +1 -0
  14. package/dist/client/_nuxt/index.403133d8.css +1 -0
  15. package/dist/client/_nuxt/{options.56a3e5f9.js → options.bd244bf2.js} +1 -1
  16. package/dist/client/_nuxt/{png.37f3e77b.js → png.765461fb.js} +1 -1
  17. package/dist/client/_nuxt/{shiki.3a930bb8.js → shiki.474ce6f4.js} +1 -1
  18. package/dist/client/_nuxt/{svg.186c6bd1.js → svg.1511e50d.js} +1 -1
  19. package/dist/client/_nuxt/{vnodes.a799f183.js → vnodes.4f695971.js} +1 -1
  20. package/dist/client/index.html +2 -2
  21. package/dist/client/options/index.html +2 -2
  22. package/dist/client/png/index.html +2 -2
  23. package/dist/client/svg/index.html +2 -2
  24. package/dist/client/vnodes/index.html +2 -2
  25. package/dist/module.d.ts +22 -7
  26. package/dist/module.json +1 -1
  27. package/dist/module.mjs +312 -114
  28. package/dist/runtime/browserUtil.d.ts +1 -0
  29. package/dist/runtime/browserUtil.mjs +10 -5
  30. package/dist/runtime/components/OgImageBasic.island.vue +5 -0
  31. package/dist/runtime/components/OgImageDynamic.d.ts +1 -1
  32. package/dist/runtime/components/OgImageScreenshot.d.ts +1 -1
  33. package/dist/runtime/components/OgImageStatic.d.ts +1 -1
  34. package/dist/runtime/composables/defineOgImage.mjs +15 -12
  35. package/dist/runtime/nitro/middleware/og.png.mjs +51 -7
  36. package/dist/runtime/nitro/middleware/playground.mjs +4 -3
  37. package/dist/runtime/nitro/plugins/prerender.d.ts +3 -0
  38. package/dist/runtime/nitro/plugins/prerender.mjs +26 -0
  39. package/dist/runtime/nitro/providers/browser/lambda.d.ts +1 -1
  40. package/dist/runtime/nitro/providers/browser/lambda.mjs +3 -3
  41. package/dist/runtime/nitro/providers/browser/{node.mjs → playwright.mjs} +0 -9
  42. package/dist/runtime/nitro/providers/browser/universal.d.ts +1 -0
  43. package/dist/runtime/nitro/providers/browser/universal.mjs +33 -0
  44. package/dist/runtime/nitro/providers/png/resvg-node.d.ts +5 -0
  45. package/dist/runtime/nitro/providers/png/resvg-node.mjs +6 -0
  46. package/dist/runtime/nitro/providers/png/resvg-wasm.d.ts +4 -0
  47. package/dist/runtime/nitro/providers/png/resvg-wasm.mjs +11 -0
  48. package/dist/runtime/nitro/providers/png/svg2png.mjs +11 -0
  49. package/dist/runtime/nitro/providers/satori/{webworker.mjs → yoga-wasm.mjs} +4 -5
  50. package/dist/runtime/nitro/renderers/browser.d.ts +2 -2
  51. package/dist/runtime/nitro/renderers/browser.mjs +14 -7
  52. package/dist/runtime/nitro/renderers/satori/index.d.ts +2 -2
  53. package/dist/runtime/nitro/renderers/satori/index.mjs +15 -16
  54. package/dist/runtime/nitro/renderers/satori/plugins/emojis.d.ts +1 -1
  55. package/dist/runtime/nitro/renderers/satori/plugins/emojis.mjs +24 -9
  56. package/dist/runtime/nitro/renderers/satori/plugins/encoding.d.ts +1 -1
  57. package/dist/runtime/nitro/renderers/satori/plugins/encoding.mjs +2 -1
  58. package/dist/runtime/nitro/renderers/satori/plugins/flex.d.ts +1 -1
  59. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.d.ts +1 -1
  60. package/dist/runtime/nitro/renderers/satori/plugins/imageSrc.mjs +25 -3
  61. package/dist/runtime/nitro/renderers/satori/plugins/twClasses.d.ts +1 -1
  62. package/dist/runtime/nitro/renderers/satori/utils.d.ts +4 -4
  63. package/dist/runtime/nitro/renderers/satori/utils.mjs +26 -13
  64. package/dist/runtime/nitro/routes/debug.d.ts +4 -0
  65. package/dist/runtime/nitro/routes/debug.mjs +9 -0
  66. package/dist/runtime/nitro/routes/html.mjs +100 -25
  67. package/dist/runtime/nitro/routes/options.mjs +20 -22
  68. package/dist/runtime/nitro/routes/svg.mjs +3 -1
  69. package/dist/runtime/nitro/routes/vnode.mjs +3 -1
  70. package/dist/runtime/nitro/util-hostname.d.ts +2 -0
  71. package/dist/runtime/nitro/util-hostname.mjs +20 -0
  72. package/dist/runtime/nitro/utils-pure.d.ts +3 -2
  73. package/dist/runtime/nitro/utils-pure.mjs +9 -8
  74. package/dist/runtime/nitro/utils.d.ts +6 -8
  75. package/dist/runtime/nitro/utils.mjs +50 -47
  76. package/dist/runtime/public-assets/__nuxt_og_image__/browser-provider-not-supported.png +0 -0
  77. package/dist/runtime/public-assets-optional/resvg/resvg.wasm +0 -0
  78. package/dist/types.d.ts +6 -0
  79. package/package.json +37 -24
  80. package/dist/client/_nuxt/IconCSS.a041aca0.js +0 -1
  81. package/dist/client/_nuxt/ImageLoader.9bf39d71.js +0 -1
  82. package/dist/client/_nuxt/entry.74018bda.js +0 -5
  83. package/dist/client/_nuxt/entry.7a8c1ab2.css +0 -1
  84. package/dist/client/_nuxt/error-component.cf7543e5.js +0 -3
  85. package/dist/client/_nuxt/index.3f356409.js +0 -1
  86. package/dist/runtime/nitro/providers/svg2png/universal.mjs +0 -9
  87. /package/dist/runtime/nitro/providers/browser/{node.d.ts → playwright.d.ts} +0 -0
  88. /package/dist/runtime/nitro/providers/{svg2png/universal.d.ts → png/svg2png.d.ts} +0 -0
  89. /package/dist/runtime/nitro/providers/satori/{node.d.ts → default.d.ts} +0 -0
  90. /package/dist/runtime/nitro/providers/satori/{node.mjs → default.mjs} +0 -0
  91. /package/dist/runtime/nitro/providers/satori/{webworker.d.ts → yoga-wasm.d.ts} +0 -0
  92. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-400-normal.woff +0 -0
  93. /package/dist/runtime/{public-assets → public-assets-optional/inter-font}/inter-latin-ext-700-normal.woff +0 -0
  94. /package/dist/runtime/{public-assets → public-assets-optional/svg2png}/svg2png.wasm +0 -0
  95. /package/dist/runtime/{public-assets → public-assets-optional/yoga}/yoga.wasm +0 -0
@@ -1,20 +1,32 @@
1
+ import { Buffer } from "node:buffer";
1
2
  import { base64ToArrayBuffer, readPublicAsset } from "../../utils.mjs";
2
3
  import { useStorage } from "#internal/nitro";
3
4
  const cachedFonts = {};
4
- export async function loadFont(font) {
5
- if (cachedFonts[font])
6
- return cachedFonts[font];
5
+ export async function loadFont(baseURL, font) {
6
+ let fontKey = font;
7
+ if (typeof font === "object")
8
+ fontKey = `${font.name}:${font.weight}`;
9
+ if (cachedFonts[fontKey])
10
+ return cachedFonts[fontKey];
11
+ const [name, weight] = fontKey.split(":");
7
12
  let data;
8
- const storageKey = `assets:nuxt-og-imagee:font:${font}`;
9
- if (await useStorage().hasItem(storageKey)) {
13
+ const storageKey = `assets:nuxt-og-imagee:font:${fontKey}`;
14
+ if (await useStorage().hasItem(storageKey))
10
15
  data = base64ToArrayBuffer(await useStorage().getItem(storageKey));
11
- return cachedFonts[font] = { name: font, data, style: "normal" };
16
+ if (!data && name === "Inter" && ["400", "700"].includes(weight)) {
17
+ data = await readPublicAsset(`/inter-latin-ext-${weight}-normal.woff`);
12
18
  }
13
- const [name, weight] = font.split(":");
14
- if (name === "Inter" && ["400", "700"].includes(weight)) {
15
- const data2 = await readPublicAsset(`/inter-latin-ext-${weight}-normal.woff`);
16
- if (data2)
17
- return cachedFonts[font] = { name, weight: Number(weight), data: data2, style: "normal" };
19
+ if (font.path) {
20
+ data = await readPublicAsset(font.path);
21
+ if (!data) {
22
+ try {
23
+ data = await globalThis.$fetch(font.path, {
24
+ responseType: "arrayBuffer",
25
+ baseURL
26
+ });
27
+ } catch {
28
+ }
29
+ }
18
30
  }
19
31
  if (!data) {
20
32
  const fontUrl = await globalThis.$fetch("/api/og-image-font", {
@@ -24,8 +36,9 @@ export async function loadFont(font) {
24
36
  responseType: "arrayBuffer"
25
37
  });
26
38
  }
39
+ cachedFonts[fontKey] = { name, weight: Number(weight), data, style: "normal" };
27
40
  await useStorage().setItem(storageKey, Buffer.from(data).toString("base64"));
28
- return cachedFonts[font] = { name, weight: Number(weight), data, style: "normal" };
41
+ return cachedFonts[fontKey];
29
42
  }
30
43
  export async function walkSatoriTree(url, node, plugins) {
31
44
  if (!node.props?.children)
@@ -36,7 +49,7 @@ export async function walkSatoriTree(url, node, plugins) {
36
49
  }
37
50
  for (const child of node.props.children || []) {
38
51
  if (child) {
39
- for (const plugin of plugins) {
52
+ for (const plugin of plugins.flat()) {
40
53
  if (plugin.filter(child))
41
54
  await plugin.transform(child);
42
55
  }
@@ -0,0 +1,4 @@
1
+ declare const _default: import("h3").EventHandler<{
2
+ runtimeConfig: any;
3
+ }>;
4
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler, setHeader } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export default defineEventHandler(async (e) => {
4
+ setHeader(e, "Content-Type", "application/json");
5
+ const runtimeConfig = useRuntimeConfig()["nuxt-og-image"];
6
+ return {
7
+ runtimeConfig
8
+ };
9
+ });
@@ -1,33 +1,65 @@
1
1
  import { withBase } from "ufo";
2
2
  import { renderSSRHead } from "@unhead/ssr";
3
3
  import { createHeadCore } from "@unhead/vue";
4
- import { defineEventHandler, getQuery, sendRedirect } from "h3";
5
- import { fetchOptions, renderIsland, useHostname } from "../utils.mjs";
6
- import { useRuntimeConfig } from "#internal/nitro";
4
+ import { createError, defineEventHandler, getQuery, sendRedirect } from "h3";
5
+ import { hash } from "ohash";
6
+ import twemoji from "twemoji";
7
+ import { fetchOptions, useHostname } from "../utils.mjs";
8
+ import { useRuntimeConfig } from "#imports";
7
9
  export default defineEventHandler(async (e) => {
8
- const { fonts, defaults } = useRuntimeConfig()["nuxt-og-image"];
9
- const path = getQuery(e).path || "/";
10
- const scale = getQuery(e).scale;
11
- const mode = getQuery(e).mode || "light";
10
+ const { fonts, defaults, satoriOptions } = useRuntimeConfig()["nuxt-og-image"];
11
+ const query = getQuery(e);
12
+ const path = withBase(query.path || "/", useRuntimeConfig().app.baseURL);
13
+ const scale = query.scale;
14
+ const mode = query.mode || "light";
12
15
  let options;
13
- if (getQuery(e).options)
14
- options = JSON.parse(getQuery(e).options);
16
+ if (query.options) {
17
+ try {
18
+ options = JSON.parse(query.options);
19
+ } catch {
20
+ }
21
+ }
15
22
  if (!options)
16
23
  options = await fetchOptions(e, path);
17
- if (options.provider === "browser" && !options.component)
18
- return sendRedirect(e, withBase(path, useHostname(e)));
19
- const island = await renderIsland(options);
24
+ if (options.provider === "browser" && !options.component) {
25
+ const pathWithoutBase = path.replace(new RegExp(`^${useRuntimeConfig().app.baseURL}`), "");
26
+ return sendRedirect(e, withBase(pathWithoutBase, useHostname(e)));
27
+ }
28
+ if (!options.component) {
29
+ throw createError({
30
+ statusCode: 500,
31
+ statusMessage: `Nuxt OG Image trying to render an invalid component. Received options ${JSON.stringify(options)}`
32
+ });
33
+ }
34
+ const hashId = hash([options.component, options]);
35
+ const island = await $fetch(`/__nuxt_island/${options.component}:${hashId}`, {
36
+ params: {
37
+ props: JSON.stringify(options)
38
+ }
39
+ });
20
40
  const head = createHeadCore();
21
41
  head.push(island.head);
42
+ let defaultFontFamily = "sans-serif";
43
+ const firstFont = fonts[0];
44
+ if (firstFont)
45
+ defaultFontFamily = firstFont.name;
46
+ let html = island.html;
47
+ try {
48
+ html = twemoji.parse(html, {
49
+ folder: "svg",
50
+ ext: ".svg"
51
+ });
52
+ } catch (e2) {
53
+ }
22
54
  head.push({
23
55
  style: [
24
56
  {
25
57
  // default font is the first font family
26
- innerHTML: `body { font-family: '${fonts[0].split(":")[0].replace("+", " ")}', sans-serif; }`
58
+ innerHTML: `body { font-family: '${defaultFontFamily.replace("+", " ")}', sans-serif; }`
27
59
  },
28
- scale ? {
60
+ {
29
61
  innerHTML: `body {
30
- transform: scale(${scale});
62
+ transform: scale(${scale || 1});
31
63
  transform-origin: top left;
32
64
  max-height: 100vh;
33
65
  position: relative;
@@ -41,9 +73,18 @@ img.emoji {
41
73
  width: 1em;
42
74
  margin: 0 .05em 0 .1em;
43
75
  vertical-align: -0.1em;
44
- }
45
- `
46
- } : {}
76
+ }`
77
+ },
78
+ ...fonts.filter((font) => font.path).map((font) => {
79
+ return `
80
+ @font-face {
81
+ font-family: '${font.name}';
82
+ font-style: normal;
83
+ font-weight: ${font.weight};
84
+ src: url('${font.path}') format('truetype');
85
+ }
86
+ `;
87
+ })
47
88
  ],
48
89
  meta: [
49
90
  {
@@ -54,12 +95,12 @@ img.emoji {
54
95
  {
55
96
  src: "https://cdn.tailwindcss.com"
56
97
  },
57
- // @todo merge with users tailwind
58
98
  {
59
99
  innerHTML: `tailwind.config = {
60
100
  corePlugins: {
61
101
  preflight: false,
62
- }
102
+ },
103
+ theme: ${JSON.stringify(satoriOptions.tailwindConfig.theme || {})}
63
104
  }`
64
105
  }
65
106
  ],
@@ -70,19 +111,53 @@ img.emoji {
70
111
  rel: "stylesheet"
71
112
  },
72
113
  // have to add each weight as their own stylesheet
73
- ...fonts.map((font) => {
74
- const [name, weight] = font.split(":");
114
+ ...fonts.filter((font) => !font.path).map((font) => {
75
115
  return {
76
- href: `https://fonts.googleapis.com/css2?family=${name}:wght@${weight}&display=swap`,
116
+ href: `https://fonts.googleapis.com/css2?family=${font.name}:wght@${font.weight}&display=swap`,
77
117
  rel: "stylesheet"
78
118
  };
79
119
  })
80
120
  ]
81
121
  });
82
122
  const headChunk = await renderSSRHead(head);
83
- return `<!DOCTYPE html>
123
+ let htmlTemplate = `<!DOCTYPE html>
84
124
  <html ${headChunk.htmlAttrs}>
85
125
  <head>${headChunk.headTags}</head>
86
- <body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div style="position: relative; display: flex; margin: 0 auto; width: 1200px; height: 630px;">${island.html}</div>${headChunk.bodyTags}</body>
126
+ <body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div style="position: relative; display: flex; margin: 0 auto; width: 1200px; height: 630px;">${html}</div>${headChunk.bodyTags}</body>
87
127
  </html>`;
128
+ let hasInlineStyles = false;
129
+ const stylesheets = htmlTemplate.match(/<link rel="stylesheet" href=".*?">/g);
130
+ if (stylesheets) {
131
+ for (const stylesheet of stylesheets) {
132
+ if (!stylesheet.includes(`${options.component}.vue`)) {
133
+ htmlTemplate = htmlTemplate.replace(stylesheet, "");
134
+ } else {
135
+ const href = stylesheet.match(/href="(.*?)"/)[1];
136
+ try {
137
+ let css = await (await $fetch(href, {
138
+ baseURL: useHostname(e)
139
+ })).text();
140
+ if (css.includes("const __vite__css =")) {
141
+ css = css.match(/const __vite__css = "(.*)"/)[1].replace(/\\n/g, "\n");
142
+ }
143
+ htmlTemplate = htmlTemplate.replace(stylesheet, `<style>${css.replace(/\/\/# sourceMappingURL=.*/, "")}</style>`);
144
+ hasInlineStyles = true;
145
+ } catch {
146
+ }
147
+ }
148
+ }
149
+ }
150
+ try {
151
+ if (hasInlineStyles) {
152
+ const inlineCss = await import("inline-css").then((m) => m?.default || m);
153
+ htmlTemplate = inlineCss(htmlTemplate, {
154
+ url: useHostname(e),
155
+ applyLinkTags: false,
156
+ removeLinkTags: false,
157
+ removeStyleTags: false
158
+ });
159
+ }
160
+ } catch {
161
+ }
162
+ return htmlTemplate;
88
163
  });
@@ -1,17 +1,21 @@
1
- import { createError, defineEventHandler, getHeaders, getQuery } from "h3";
2
- import { extractOgImageOptions, useHostname } from "../utils.mjs";
3
- import { getRouteRules, useRuntimeConfig } from "#internal/nitro";
1
+ import { createError, defineEventHandler, getQuery } from "h3";
2
+ import { withoutBase } from "ufo";
3
+ import { defu } from "defu";
4
+ import { extractOgImageOptions } from "../utils.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 || "/";
7
- const fetchOptions = process.dev || process.env.prerender ? {
8
- headers: getHeaders(e)
9
- } : {
10
- baseURL: useHostname(e)
11
- };
12
- const html = await globalThis.$fetch(path, {
13
- ...fetchOptions
14
- });
9
+ const path = withoutBase(query.path || "/", useRuntimeConfig().app.baseURL);
10
+ let html;
11
+ try {
12
+ html = await globalThis.$fetch(path);
13
+ } catch (err) {
14
+ throw createError({
15
+ statusCode: 500,
16
+ statusMessage: `Failed to read the path ${path} for og-image extraction. ${err.message}.`
17
+ });
18
+ }
15
19
  const extractedPayload = extractOgImageOptions(html);
16
20
  if (!extractedPayload) {
17
21
  throw createError({
@@ -20,20 +24,14 @@ export default defineEventHandler(async (e) => {
20
24
  });
21
25
  }
22
26
  e.node.req.url = path;
27
+ const oldRouteRules = e.context._nitro.routeRules;
23
28
  e.context._nitro.routeRules = void 0;
24
29
  const routeRules = getRouteRules(e)?.ogImage;
30
+ e.context._nitro.routeRules = oldRouteRules;
25
31
  e.node.req.url = e.path;
26
32
  if (routeRules === false)
27
33
  return false;
28
34
  const { defaults } = useRuntimeConfig()["nuxt-og-image"];
29
- return {
30
- path,
31
- ...defaults,
32
- // use route rules
33
- ...routeRules || {},
34
- // use provided data
35
- ...extractedPayload,
36
- // use query data
37
- ...query
38
- };
35
+ const result = defu(extractedPayload, routeRules, { path }, defaults);
36
+ return result;
39
37
  });
@@ -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 path = getQuery(e).path || "/";
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 path = getQuery(e).path || "/";
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,2 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function useHostname(e: H3Event): string;
@@ -0,0 +1,20 @@
1
+ import { getRequestHost, getRequestProtocol } from "h3";
2
+ import { withBase, withoutProtocol } 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
+ let protocol = getRequestProtocol(e);
10
+ if (process.env.NUXT_VITE_NODE_OPTIONS) {
11
+ const envHost = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL.replace("__nuxt_vite_node__", "");
12
+ host = withoutProtocol(envHost);
13
+ protocol = envHost.includes("https") ? "https" : "http";
14
+ }
15
+ const useHttp = process.dev || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
16
+ let port = host.includes(":") ? host.split(":").pop() : false;
17
+ if ((process.dev || process.env.prerender || host.includes("localhost")) && !port)
18
+ port = process.env.NITRO_PORT || process.env.PORT;
19
+ return withBase(base, `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}`);
20
+ }
@@ -1,2 +1,3 @@
1
- export declare function extractOgImageOptions(html: string): false | Record<string, any>;
2
- export declare function stripOgImageOptions(html: string): string;
1
+ import type { OgImageOptions } from '../../types';
2
+ export declare function decodeHtml(html: string): string;
3
+ export declare function extractOgImageOptions(html: string): OgImageOptions | false;
@@ -1,8 +1,12 @@
1
- function decodeHtmlEntities(obj) {
1
+ export function decodeHtml(html) {
2
+ return html.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&cent;/g, "\xA2").replace(/&pound;/g, "\xA3").replace(/&yen;/g, "\xA5").replace(/&euro;/g, "\u20AC").replace(/&copy;/g, "\xA9").replace(/&reg;/g, "\xAE").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/").replace(/&#([0-9]+);/g, (full, int) => {
3
+ return String.fromCharCode(Number.parseInt(int));
4
+ });
5
+ }
6
+ function decodeObjectHtmlEntities(obj) {
2
7
  Object.entries(obj).forEach(([key, value]) => {
3
- if (typeof value === "string") {
4
- obj[key] = value.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x27;/g, "'").replace(/&#x2F;/g, "/");
5
- }
8
+ if (typeof value === "string")
9
+ obj[key] = decodeHtml(value);
6
10
  });
7
11
  return obj;
8
12
  }
@@ -26,10 +30,7 @@ export function extractOgImageOptions(html) {
26
30
  else
27
31
  options.description = html.match(/<meta name="description" content="(.*?)">/)?.[1];
28
32
  }
29
- return decodeHtmlEntities(options);
33
+ return decodeObjectHtmlEntities(options);
30
34
  }
31
35
  return false;
32
36
  }
33
- export function stripOgImageOptions(html) {
34
- return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
35
- }
@@ -1,17 +1,15 @@
1
1
  /// <reference types="node" />
2
+ import { Buffer } from 'node:buffer';
2
3
  import type { H3Event } from 'h3';
3
4
  import type { OgImageOptions } from '../../types';
4
- export declare function wasmLoader(key: any, fallback: string, baseUrl: string): {
5
- loaded(): boolean | Promise<any>;
6
- load(): Promise<any>;
5
+ export * from './util-hostname';
6
+ export declare function wasmLoader(asyncModuleLoad: Promise<any> | Buffer | string, fallback: string): {
7
+ load(options: {
8
+ baseUrl: string;
9
+ }): Promise<any>;
7
10
  };
8
11
  export declare function fetchOptions(e: H3Event, path: string): Promise<OgImageOptions>;
9
12
  export declare function base64ToArrayBuffer(base64: string): ArrayBuffer;
10
- export declare function renderIsland(payload: OgImageOptions): Promise<{
11
- html: string;
12
- head: any;
13
- }>;
14
- export declare function useHostname(e: H3Event): any;
15
13
  export declare function readPublicAsset(file: string, encoding?: BufferEncoding): Promise<string | Buffer | undefined>;
16
14
  export declare function readPublicAssetBase64(file: string): Promise<string | undefined>;
17
15
  export * from './utils-pure';
@@ -1,77 +1,80 @@
1
1
  import { existsSync, promises as fsp } from "node:fs";
2
- import { getHeaders, getQuery, getRequestHeader } from "h3";
2
+ import { Buffer } from "node:buffer";
3
+ import { getQuery } from "h3";
3
4
  import { join } from "pathe";
4
- import { useRuntimeConfig } from "#internal/nitro";
5
- export function wasmLoader(key, fallback, baseUrl) {
5
+ import { prefixStorage } from "unstorage";
6
+ import { useRuntimeConfig, useStorage } from "#imports";
7
+ export * from "./util-hostname.mjs";
8
+ export function wasmLoader(asyncModuleLoad, fallback) {
6
9
  let promise;
7
- let loaded = false;
10
+ let wasm;
8
11
  return {
9
- loaded() {
10
- if (loaded)
11
- return true;
12
+ async load(options) {
12
13
  if (typeof promise !== "undefined")
13
14
  return promise;
14
- return false;
15
- },
16
- async load() {
15
+ if (wasm)
16
+ return wasm;
17
17
  promise = promise || new Promise(async (resolve) => {
18
- let wasm;
19
18
  try {
20
- wasm = await key;
19
+ wasm = await asyncModuleLoad;
21
20
  if (typeof wasm === "string")
22
21
  wasm = void 0;
23
22
  } catch (e) {
24
23
  }
25
- if (!wasm)
26
- wasm = await readPublicAsset(fallback);
27
24
  if (!wasm) {
28
- const url = new URL(baseUrl);
29
- wasm = await (await fetch(`${url.origin}${fallback}`)).arrayBuffer();
25
+ wasm = await readPublicAsset(fallback, "base64");
26
+ if (wasm)
27
+ wasm = Buffer.from(wasm, "base64");
28
+ }
29
+ if (!wasm) {
30
+ const url = new URL(options.baseUrl);
31
+ wasm = await (await globalThis.$fetch(fallback, { baseURL: url.origin })).arrayBuffer();
32
+ wasm = Buffer.from(wasm);
30
33
  }
31
- loaded = true;
32
34
  resolve(wasm);
33
35
  });
34
36
  return promise;
35
37
  }
36
38
  };
37
39
  }
38
- export function fetchOptions(e, path) {
39
- const fetchOptions2 = process.dev || process.env.prerender ? {
40
- headers: getHeaders(e)
41
- } : {
42
- baseURL: useHostname(e)
40
+ export async function fetchOptions(e, path) {
41
+ const { runtimeCacheStorage } = useRuntimeConfig()["nuxt-og-image"];
42
+ const cache = runtimeCacheStorage || process.env.prerender ? prefixStorage(useStorage(), "og-image-cache:options") : false;
43
+ let options;
44
+ if (cache && await cache.hasItem(path)) {
45
+ const cachedValue = await cache.getItem(path);
46
+ if (cachedValue && cachedValue.value && cachedValue.expiresAt < Date.now())
47
+ options = cachedValue.value;
48
+ else
49
+ await cache.removeItem(path);
50
+ }
51
+ if (!options) {
52
+ options = await globalThis.$fetch("/api/og-image-options", {
53
+ query: {
54
+ path
55
+ },
56
+ responseType: "json"
57
+ });
58
+ if (cache) {
59
+ await cache.setItem(path, {
60
+ value: options,
61
+ expiresAt: Date.now() + (options.static ? 60 * 60 * 1e3 : 5 * 1e3)
62
+ });
63
+ }
64
+ }
65
+ return {
66
+ ...options,
67
+ // use query data
68
+ ...getQuery(e)
43
69
  };
44
- return globalThis.$fetch("/api/og-image-options", {
45
- query: {
46
- ...getQuery(e),
47
- path
48
- },
49
- ...fetchOptions2
50
- });
51
70
  }
52
71
  export function base64ToArrayBuffer(base64) {
53
72
  const buffer = Buffer.from(base64, "base64");
54
73
  return new Uint8Array(buffer).buffer;
55
74
  }
56
- export function renderIsland(payload) {
57
- return globalThis.$fetch(`/__nuxt_island/${payload.component}`, {
58
- query: { props: JSON.stringify(payload) }
59
- });
60
- }
61
- export function useHostname(e) {
62
- const config = useRuntimeConfig()["nuxt-og-image"];
63
- if (!process.dev && config.siteUrl)
64
- return config.siteUrl;
65
- const host = getRequestHeader(e, "host") || process.env.NITRO_HOST || process.env.HOST || "localhost";
66
- const protocol = getRequestHeader(e, "x-forwarded-proto") || "http";
67
- const useHttp = process.env.NODE_ENV === "development" || host.includes("127.0.0.1") || host.includes("localhost") || protocol === "http";
68
- const port = host.includes(":") ? host.split(":").pop() : process.env.NITRO_PORT || process.env.PORT;
69
- const base = useRuntimeConfig().app.baseURL;
70
- return `http${useHttp ? "" : "s"}://${host.includes(":") ? host.split(":")[0] : host}${port ? `:${port}` : ""}${base}`;
71
- }
72
- const r = (base, key) => {
75
+ function r(base, key) {
73
76
  return join(base, key.replace(/:/g, "/"));
74
- };
77
+ }
75
78
  export async function readPublicAsset(file, encoding) {
76
79
  const { assetDirs } = useRuntimeConfig()["nuxt-og-image"];
77
80
  for (const assetDir of assetDirs) {
package/dist/types.d.ts CHANGED
@@ -7,5 +7,11 @@ declare module '@nuxt/schema' {
7
7
  interface NuxtHooks extends ModuleHooks {}
8
8
  }
9
9
 
10
+ declare module 'nuxt/schema' {
11
+ interface NuxtConfig { ['ogImage']?: Partial<ModuleOptions> }
12
+ interface NuxtOptions { ['ogImage']?: ModuleOptions }
13
+ interface NuxtHooks extends ModuleHooks {}
14
+ }
15
+
10
16
 
11
17
  export { ModuleHooks, ModuleOptions, default } from './module'