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
@@ -0,0 +1,48 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { promises as fsp } from "node:fs";
3
+ import { html as convertHtmlToSatori } from "satori-html";
4
+ import satori from "satori";
5
+ import { parseURL } from "ufo";
6
+ import { Resvg } from "@resvg/resvg-js";
7
+ import { dirname, resolve } from "pathe";
8
+ import { height, width } from "#nuxt-og-image/config";
9
+ import { getAsset } from "#internal/nitro/virtual/public-assets";
10
+ export default {
11
+ name: "satori",
12
+ createPng: async function createPng(baseUrl) {
13
+ const svg = await this.createSvg(baseUrl);
14
+ const resvg = new Resvg(svg, {});
15
+ const pngData = resvg.render();
16
+ return pngData.asPng();
17
+ },
18
+ createSvg: async function createSvg(baseUrl) {
19
+ const url = parseURL(baseUrl);
20
+ const html = await $fetch(url.pathname);
21
+ const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1];
22
+ const font = async (weight) => {
23
+ let fontData;
24
+ const file = getAsset(`/inter-latin-ext-${weight}-normal.woff`);
25
+ if (file) {
26
+ const serverDir = dirname(fileURLToPath(import.meta.url));
27
+ fontData = await fsp.readFile(resolve(serverDir, file.path));
28
+ }
29
+ if (!fontData)
30
+ fontData = await (await $fetch(`${url.protocol}//${url.host}/inter-latin-ext-${weight}-normal.woff`)).arrayBuffer();
31
+ return {
32
+ name: "Inter",
33
+ weight,
34
+ style: "normal",
35
+ data: fontData
36
+ };
37
+ };
38
+ const satoriTree = convertHtmlToSatori(body);
39
+ return await satori(satoriTree, {
40
+ width,
41
+ height,
42
+ fonts: [
43
+ await font(400),
44
+ await font(700)
45
+ ]
46
+ });
47
+ }
48
+ };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<string | void>;
2
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import { parseURL, withoutTrailingSlash } from "ufo";
2
+ import { renderSSRHead } from "@unhead/ssr";
3
+ import { createHeadCore } from "@unhead/vue";
4
+ import { defineEventHandler, sendRedirect } from "h3";
5
+ import { fetchPayload, renderIsland, useHostname } from "../../utils.mjs";
6
+ import { height, width } from "#nuxt-og-image/config";
7
+ export default defineEventHandler(async (e) => {
8
+ const path = parseURL(e.path).pathname;
9
+ if (!path.endsWith("__og_image__/html"))
10
+ return;
11
+ const basePath = withoutTrailingSlash(path.replace("__og_image__/html", ""));
12
+ const payload = await fetchPayload(basePath);
13
+ if (payload.provider === "browser")
14
+ return sendRedirect(e, useHostname(e) + basePath);
15
+ const component = payload.component || "OgImageTemplate";
16
+ delete payload.component;
17
+ const island = await renderIsland(component, payload);
18
+ const head = createHeadCore();
19
+ head.push(island.head);
20
+ head.push({
21
+ style: [
22
+ {
23
+ innerHTML: "body { font-family: 'Inter', sans-serif; }"
24
+ }
25
+ ],
26
+ link: [
27
+ {
28
+ href: "https://cdn.jsdelivr.net/npm/gardevoir",
29
+ rel: "stylesheet"
30
+ },
31
+ {
32
+ href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap",
33
+ rel: "stylesheet"
34
+ }
35
+ ]
36
+ });
37
+ const headChunk = await renderSSRHead(head);
38
+ return `<!DOCTYPE html>
39
+ <html ${headChunk.htmlAttrs}>
40
+ <head>${headChunk.headTags}</head>
41
+ <body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div style="width: ${width}px; height: ${height}px; display: flex; margin: 0 auto;">${island.html}</div>${headChunk.bodyTags}</body>
42
+ </html>`;
43
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<string | undefined>;
2
+ export default _default;
@@ -0,0 +1,26 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { parseURL, withoutTrailingSlash } from "ufo";
3
+ import { fetchPayload, useHostname } from "../../utils.mjs";
4
+ export default defineEventHandler(async (e) => {
5
+ const path = parseURL(e.path).pathname;
6
+ if (!path.endsWith("/__og_image__"))
7
+ return;
8
+ const basePath = withoutTrailingSlash(path.replace("__og_image__", ""));
9
+ const payload = await fetchPayload(basePath);
10
+ if (!payload)
11
+ return `The route ${basePath} has not been set up for og:image generation.`;
12
+ return `
13
+ <style>
14
+ body {
15
+ margin: 0;
16
+ padding: 0;
17
+ }
18
+ iframe {
19
+ border: none;
20
+ width: 100%;
21
+ height: 100%;
22
+ }
23
+ </style>
24
+ <title>Og Image Playground</title>
25
+ <iframe src="${useHostname(e)}/__nuxt_og_image__/client/?&path=${withoutTrailingSlash(path.replace("__og_image__", ""))}"></iframe>`;
26
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<any>;
2
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { defineEventHandler, setHeader } from "h3";
2
+ import { parseURL, withBase, withoutTrailingSlash } from "ufo";
3
+ import { fetchPayload, useHostname } from "../../utils.mjs";
4
+ import { useProvider } from "#nuxt-og-image/provider";
5
+ export default defineEventHandler(async (e) => {
6
+ const path = parseURL(e.path).pathname;
7
+ if (!path.endsWith("__og_image__/og.png"))
8
+ return;
9
+ const basePath = withoutTrailingSlash(
10
+ path.replace("__og_image__/og.png", "")
11
+ );
12
+ const { provider: providerName, prerender } = await fetchPayload(basePath);
13
+ setHeader(e, "Content-Type", "image/png");
14
+ if (prerender && !process.dev) {
15
+ setHeader(e, "Cache-Control", "public, max-age=86400");
16
+ } else {
17
+ setHeader(e, "Cache-Control", "no-cache, no-store, must-revalidate");
18
+ setHeader(e, "Pragma", "no-cache");
19
+ setHeader(e, "Expires", "0");
20
+ }
21
+ const provider = await useProvider(providerName);
22
+ return provider.createPng(withBase(`${basePath}/__og_image__/html`, useHostname(e)));
23
+ });
@@ -1,4 +1,5 @@
1
+ import type { OgImagePayload } from '../../../../types';
1
2
  export declare const extractOgPayload: (html: string) => any;
2
3
  export declare const inferOgPayload: (html: string) => Record<string, any>;
3
- declare const _default: import("h3").EventHandler<string | undefined>;
4
+ declare const _default: import("h3").EventHandler<false | OgImagePayload | undefined>;
4
5
  export default _default;
@@ -0,0 +1,53 @@
1
+ import { parseURL, withoutTrailingSlash } from "ufo";
2
+ import { defineEventHandler, getQuery } from "h3";
3
+ import { PayloadScriptId } from "#nuxt-og-image/constants";
4
+ import { getRouteRules } from "#internal/nitro";
5
+ export const extractOgPayload = (html) => {
6
+ const payload = html.match(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.+?)<\/script>`))?.[1];
7
+ if (payload) {
8
+ return JSON.parse(payload);
9
+ }
10
+ return false;
11
+ };
12
+ export const inferOgPayload = (html) => {
13
+ const payload = {};
14
+ const title = html.match(/<meta property="og:title" content="(.*?)">/)?.[1];
15
+ if (title)
16
+ payload.title = title;
17
+ const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
18
+ if (description)
19
+ payload.description = description;
20
+ return payload;
21
+ };
22
+ export default defineEventHandler(async (e) => {
23
+ const path = parseURL(e.path).pathname;
24
+ if (!path.endsWith("__og_image__/payload"))
25
+ return;
26
+ const basePath = withoutTrailingSlash(path.replace("__og_image__/payload", ""));
27
+ const html = await $fetch(basePath);
28
+ const extractedPayload = extractOgPayload(html);
29
+ if (!extractedPayload)
30
+ return false;
31
+ e.node.req.url = basePath;
32
+ e.context._nitro.routeRules = void 0;
33
+ const routeRules = getRouteRules(e)?.ogImage;
34
+ e.node.req.url = e.path;
35
+ if (routeRules === false)
36
+ return false;
37
+ let payload = {
38
+ path: basePath,
39
+ ...extractOgPayload(html),
40
+ ...inferOgPayload(html),
41
+ ...routeRules || {},
42
+ ...getQuery(e)
43
+ };
44
+ if (payload.provider === "satori") {
45
+ payload = {
46
+ title: "Hello World",
47
+ description: "Example description",
48
+ image: "https://example.com/image.png",
49
+ ...payload
50
+ };
51
+ }
52
+ return payload;
53
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<any>;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineEventHandler, setHeader } from "h3";
2
+ import { parseURL, withBase, withoutTrailingSlash } from "ufo";
3
+ import { fetchPayload, useHostname } from "../../utils.mjs";
4
+ import { useProvider } from "#nuxt-og-image/provider";
5
+ export default defineEventHandler(async (e) => {
6
+ const path = parseURL(e.path).pathname;
7
+ if (!path.endsWith("__og_image__/svg"))
8
+ return;
9
+ const basePath = withoutTrailingSlash(
10
+ path.replace("__og_image__/svg", "")
11
+ );
12
+ const { provider: providerName } = await fetchPayload(basePath);
13
+ setHeader(e, "Content-Type", "image/svg+xml");
14
+ const provider = await useProvider(providerName);
15
+ return provider.createSvg(withBase(`${basePath}/__og_image__/html`, useHostname(e)));
16
+ });
@@ -0,0 +1,8 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { OgImagePayload } from '../../types';
3
+ export declare function fetchPayload(path: string): Promise<OgImagePayload>;
4
+ export declare function renderIsland(island: string, payload: Record<string, any>): Promise<{
5
+ html: string;
6
+ head: any;
7
+ }>;
8
+ export declare function useHostname(e: H3Event): string;
@@ -0,0 +1,17 @@
1
+ import { joinURL, withQuery } from "ufo";
2
+ import { getRequestHeader } from "h3";
3
+ export function fetchPayload(path) {
4
+ return $fetch(joinURL(path, "__og_image__/payload"));
5
+ }
6
+ export function renderIsland(island, payload) {
7
+ return $fetch(withQuery(`/__nuxt_island/${island}`, {
8
+ props: JSON.stringify(payload)
9
+ }));
10
+ }
11
+ export function useHostname(e) {
12
+ const host = getRequestHeader(e, "host") || "localhost:3000";
13
+ const protocol = getRequestHeader(e, "x-forwarded-proto") || "http";
14
+ if (protocol.startsWith("http"))
15
+ return `${protocol}://${host}`;
16
+ return `http${process.env.NODE_ENV === "development" ? "" : "s"}://${host}`;
17
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "1.0.0-beta.1",
5
5
  "packageManager": "pnpm@7.8.0",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -27,32 +27,43 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "@nuxt/kit": "3.0.0",
30
+ "@resvg/resvg-js": "^2.2.0",
31
+ "birpc": "^0.2.3",
30
32
  "chalk": "^5.2.0",
31
33
  "chrome-launcher": "^0.15.1",
32
34
  "defu": "^6.1.1",
33
35
  "execa": "^6.1.0",
34
36
  "fast-glob": "^3.2.12",
37
+ "flatted": "^3.2.7",
38
+ "launch-editor": "^2.6.0",
35
39
  "ohash": "^1.0.0",
36
40
  "pathe": "^1.0.0",
37
- "playwright-core": "^1.29.1",
41
+ "playwright-core": "^1.29.2",
38
42
  "radix3": "^1.0.0",
43
+ "satori": "^0.1.1",
44
+ "satori-html": "^0.3.2",
45
+ "sirv": "^2.0.2",
46
+ "tinyws": "^0.1.0",
39
47
  "ufo": "^1.0.1"
40
48
  },
41
49
  "devDependencies": {
42
- "@antfu/eslint-config": "^0.34.0",
50
+ "@antfu/eslint-config": "^0.34.1",
43
51
  "@nuxt/kit": "3.0.0",
44
52
  "@nuxt/module-builder": "^0.2.1",
45
53
  "@nuxt/test-utils": "3.0.0",
46
54
  "@nuxtjs/eslint-config-typescript": "^12.0.0",
55
+ "@types/ws": "^8.5.4",
47
56
  "bumpp": "^8.2.1",
48
- "eslint": "8.31.0",
57
+ "eslint": "8.32.0",
49
58
  "nuxt": "npm:nuxt3@latest",
50
- "puppeteer": "^19.4.1",
51
- "vitest": "^0.26.3"
59
+ "puppeteer": "^19.5.2",
60
+ "vitest": "^0.27.2"
52
61
  },
53
62
  "scripts": {
63
+ "build": "pnpm dev:prepare && pnpm build:module && pnpm build:client",
64
+ "build:client": "nuxi generate client",
65
+ "build:module": "nuxt-build-module",
54
66
  "lint": "eslint \"**/*.{ts,vue,json,yml}\"",
55
- "build": "nuxi prepare .playground && nuxt-module-build",
56
67
  "dev": "nuxi dev .playground",
57
68
  "dev:build": "nuxi build .playground",
58
69
  "dev:prepare": "nuxt-module-build --stub && nuxi prepare .playground",
@@ -1,55 +0,0 @@
1
- import { withQuery, withoutTrailingSlash } from "ufo";
2
- import { renderSSRHead } from "@unhead/ssr";
3
- import { createHeadCore } from "@unhead/vue";
4
- import { defineEventHandler, getQuery } from "h3";
5
- import { HtmlRendererRoute, PayloadScriptId } from "#nuxt-og-image/constants";
6
- export const extractOgPayload = (html) => {
7
- const payload = html.match(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.+?)<\/script>`))?.[1];
8
- if (payload) {
9
- return JSON.parse(payload);
10
- }
11
- return false;
12
- };
13
- export const inferOgPayload = (html) => {
14
- const payload = {};
15
- const title = html.match(/<meta property="og:title" content="(.*?)">/)?.[1];
16
- if (title)
17
- payload.title = title;
18
- const description = html.match(/<meta property="og:description" content="(.*?)">/)?.[1];
19
- if (description)
20
- payload.description = description;
21
- return payload;
22
- };
23
- export default defineEventHandler(async (req) => {
24
- if (!req.path?.endsWith(HtmlRendererRoute))
25
- return;
26
- const path = req.path.replace(HtmlRendererRoute, "");
27
- const html = await $fetch(withoutTrailingSlash(path));
28
- const payload = {
29
- path,
30
- title: "Hello World",
31
- description: "Example description",
32
- image: "https://example.com/image.png",
33
- ...extractOgPayload(html),
34
- ...inferOgPayload(html),
35
- ...getQuery(req)
36
- };
37
- const result = await $fetch(withQuery(`/__nuxt_island/${payload.component || "OgImageTemplate"}`, {
38
- props: JSON.stringify(payload)
39
- }));
40
- const head = createHeadCore();
41
- head.push(result.head);
42
- head.push({
43
- style: [
44
- {
45
- innerHTML: "body { margin: 0; padding: 0; } .og-image-container { width: 1200px; height: 630px; display: flex; margin: 0 auto; }"
46
- }
47
- ]
48
- });
49
- const headChunk = await renderSSRHead(head);
50
- return `<!DOCTYPE html>
51
- <html ${headChunk.htmlAttrs}>
52
- <head>${headChunk.headTags}</head>
53
- <body ${headChunk.bodyAttrs}>${headChunk.bodyTagsOpen}<div class="og-image-container">${result.html}</div>${headChunk.bodyTags}</body>
54
- </html>`;
55
- });
@@ -1,3 +0,0 @@
1
- /// <reference types="node" />
2
- declare const _default: import("h3").EventHandler<Buffer | import("h3").H3Error | undefined>;
3
- export default _default;
@@ -1,18 +0,0 @@
1
- import { createError, defineEventHandler, getRequestHeader, setHeader } from "h3";
2
- import { screenshot } from "../browserUtil.mjs";
3
- import { DefaultRuntimeImageSuffix, HtmlRendererRoute } from "#nuxt-og-image/constants";
4
- import { createBrowser } from "#nuxt-og-image/browser";
5
- export default defineEventHandler(async (e) => {
6
- if (!e.path?.endsWith(DefaultRuntimeImageSuffix))
7
- return;
8
- const path = e.path.replace(DefaultRuntimeImageSuffix, HtmlRendererRoute);
9
- const host = getRequestHeader(e, "host") || "localhost:3000";
10
- const browser = await createBrowser();
11
- if (!browser)
12
- return createError("Could not create browser");
13
- setHeader(e, "Content-Type", "image/png");
14
- return await screenshot(browser, `http${host.startsWith("localhost") ? "" : "s"}://${host}/${path}`, {
15
- width: 1200,
16
- height: 630
17
- });
18
- });