nuxt-og-image 1.0.0-beta.9 → 1.0.0
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 +335 -162
- package/dist/client/200.html +2 -2
- package/dist/client/404.html +2 -2
- package/dist/client/_nuxt/{Icon.a3c98859.js → Icon.65a4f5bf.js} +1 -1
- package/dist/client/_nuxt/{entry.2a315a2c.js → entry.9b6fba4b.js} +2 -2
- package/dist/client/_nuxt/{error-404.0c49154a.js → error-404.67bc6c65.js} +1 -1
- package/dist/client/_nuxt/{error-500.f3eec0bb.js → error-500.e3a6cf7c.js} +1 -1
- package/dist/client/_nuxt/{error-component.7c753c74.js → error-component.6baa49ee.js} +2 -2
- package/dist/client/index.html +2 -2
- package/dist/module.d.ts +1 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +15 -16
- package/dist/runtime/nitro/providers/satori/index.mjs +16 -6
- package/dist/runtime/nitro/providers/satori/plugins/emojis.d.ts +2 -0
- package/dist/runtime/nitro/providers/satori/plugins/emojis.mjs +13 -0
- package/dist/runtime/nitro/providers/satori/plugins/flex.mjs +1 -1
- package/dist/runtime/nitro/providers/satori/plugins/imageSrc.mjs +5 -12
- package/dist/runtime/nitro/providers/satori/utils.d.ts +4 -6
- package/dist/runtime/nitro/providers/satori/utils.mjs +56 -18
- package/dist/runtime/nitro/routes/__og_image__/font.d.ts +2 -0
- package/dist/runtime/nitro/routes/__og_image__/font.mjs +21 -0
- package/dist/runtime/nitro/routes/__og_image__/html.mjs +21 -6
- package/package.json +5 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{s as n,v as a,o as r,l,x as e,t as s,y as d,z as c,A as p,B as f,C as x,D as h}from"./entry.
|
|
1
|
+
import{s as n,v as a,o as r,l,x as e,t as s,y as d,z as c,A as p,B as f,C as x,D as h}from"./entry.9b6fba4b.js";const m=t=>(x("data-v-18337f8d"),t=t(),h(),t),u={class:"font-sans antialiased bg-white dark:bg-black text-black dark:text-white grid min-h-screen place-content-center overflow-hidden"},g=m(()=>e("div",{class:"fixed left-0 right-0 spotlight z-10"},null,-1)),_={class:"max-w-520px text-center z-20"},b=["textContent"],y=["textContent"],w={class:"w-full flex items-center justify-center"},S={__name:"error-404",props:{appName:{type:String,default:"Nuxt"},version:{type:String,default:""},statusCode:{type:Number,default:404},statusMessage:{type:String,default:"Not Found"},description:{type:String,default:"Sorry, the page you are looking for could not be found."},backHome:{type:String,default:"Go back home"}},setup(t){const o=t;return a({title:`${o.statusCode} - ${o.statusMessage} | ${o.appName}`,script:[],style:[{children:'*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}a{color:inherit;text-decoration:inherit}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}'}]}),(k,v)=>{const i=p;return r(),l("div",u,[g,e("div",_,[e("h1",{class:"text-8xl sm:text-10xl font-medium mb-8",textContent:s(t.statusCode)},null,8,b),e("p",{class:"text-xl px-8 sm:px-0 sm:text-4xl font-light mb-16 leading-tight",textContent:s(t.description)},null,8,y),e("div",w,[d(i,{to:"/",class:"gradient-border text-md sm:text-xl py-2 px-4 sm:py-3 sm:px-6 cursor-pointer"},{default:c(()=>[f(s(t.backHome),1)]),_:1})])])])}}},z=n(S,[["__scopeId","data-v-18337f8d"]]);export{z as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{s as i,v as a,o as r,l as n,x as e,t as s,C as l,D as d}from"./entry.
|
|
1
|
+
import{s as i,v as a,o as r,l as n,x as e,t as s,C as l,D as d}from"./entry.9b6fba4b.js";const c=t=>(l("data-v-428e244b"),t=t(),d(),t),p={class:"font-sans antialiased bg-white dark:bg-black text-black dark:text-white grid min-h-screen place-content-center overflow-hidden"},h=c(()=>e("div",{class:"fixed -bottom-1/2 left-0 right-0 h-1/2 spotlight"},null,-1)),f={class:"max-w-520px text-center"},g=["textContent"],m=["textContent"],x={__name:"error-500",props:{appName:{type:String,default:"Nuxt"},version:{type:String,default:""},statusCode:{type:Number,default:500},statusMessage:{type:String,default:"Server error"},description:{type:String,default:"This page is temporarily unavailable."}},setup(t){const o=t;return a({title:`${o.statusCode} - ${o.statusMessage} | ${o.appName}`,script:[],style:[{children:'*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e0e0e0}*{--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(14, 165, 233, .5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{margin:0;font-family:inherit;line-height:inherit}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}h1,p{margin:0}h1{font-size:inherit;font-weight:inherit}'}]}),(b,u)=>(r(),n("div",p,[h,e("div",f,[e("h1",{class:"text-8xl sm:text-10xl font-medium mb-8",textContent:s(t.statusCode)},null,8,g),e("p",{class:"text-xl px-8 sm:px-0 sm:text-4xl font-light mb-16 leading-tight",textContent:s(t.description)},null,8,m)])]))}},w=i(x,[["__scopeId","data-v-428e244b"]]);export{w as default};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{d as n,_ as o,o as _,c as f,n as g,g as E,u as r}from"./entry.
|
|
1
|
+
import{d as n,_ as o,o as _,c as f,n as g,g as E,u as r}from"./entry.9b6fba4b.js";const k={__name:"nuxt-error-page",props:{error:Object},setup(t){(t.error.stack||"").split(`
|
|
2
2
|
`).splice(1).map(e=>({text:e.replace("webpack:/","").replace(".vue",".js").trim(),internal:e.includes("node_modules")&&!e.includes(".cache")||e.includes("internal")||e.includes("new Promise")})).map(e=>`<span class="stack${e.internal?" internal":""}">${e.text}</span>`).join(`
|
|
3
|
-
`);const s=Number(t.error.statusCode||500),a=s===404,c=t.error.statusMessage??(a?"Page Not Found":"Internal Server Error"),u=t.error.message||t.error.toString(),i=void 0,d=n(()=>o(()=>import("./error-404.
|
|
3
|
+
`);const s=Number(t.error.statusCode||500),a=s===404,c=t.error.statusMessage??(a?"Page Not Found":"Internal Server Error"),u=t.error.message||t.error.toString(),i=void 0,d=n(()=>o(()=>import("./error-404.67bc6c65.js"),["./error-404.67bc6c65.js","./entry.9b6fba4b.js","./entry.dc5450bf.css","./error-404.68aa58b4.css"],import.meta.url).then(e=>e.default||e)),l=n(()=>o(()=>import("./error-500.e3a6cf7c.js"),["./error-500.e3a6cf7c.js","./entry.9b6fba4b.js","./entry.dc5450bf.css","./error-500.dc5710d1.css"],import.meta.url).then(e=>e.default||e)),m=a?d:l;return(e,p)=>(_(),f(r(m),g(E({statusCode:r(s),statusMessage:r(c),description:r(u),stack:r(i)})),null,16))}},v=k;export{v as default};
|
package/dist/client/index.html
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html >
|
|
3
3
|
<head><meta charset="utf-8">
|
|
4
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"><link rel="modulepreload" as="script" crossorigin href="/__nuxt_og_image__/client/_nuxt/entry.
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"><link rel="modulepreload" as="script" crossorigin href="/__nuxt_og_image__/client/_nuxt/entry.9b6fba4b.js"><link rel="preload" as="style" href="/__nuxt_og_image__/client/_nuxt/entry.dc5450bf.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt_og_image__/client/_nuxt/error-component.6baa49ee.js"><link rel="stylesheet" href="/__nuxt_og_image__/client/_nuxt/entry.dc5450bf.css"><script>"use strict";const w=window,de=document.documentElement,knownColorSchemes=["dark","light"],preference=window.localStorage.getItem("nuxt-color-mode")||"system";let value=preference==="system"?getColorScheme():preference;const forcedColorMode=de.getAttribute("data-color-mode-forced");forcedColorMode&&(value=forcedColorMode),addColorScheme(value),w["__NUXT_COLOR_MODE__"]={preference,value,getColorScheme,addColorScheme,removeColorScheme};function addColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.add(o):de.className+=" "+o,t&&de.setAttribute("data-"+t,e)}function removeColorScheme(e){const o=""+e+"",t="";de.classList?de.classList.remove(o):de.className=de.className.replace(new RegExp(o,"g"),""),t&&de.removeAttribute("data-"+t)}function prefersColorScheme(e){return w.matchMedia("(prefers-color-scheme"+e+")")}function getColorScheme(){if(w.matchMedia&&prefersColorScheme("").media!=="not all"){for(const e of knownColorSchemes)if(prefersColorScheme(":"+e).matches)return e}return"light"}
|
|
5
5
|
</script></head>
|
|
6
|
-
<body ><div id="__nuxt"></div><script>window.__NUXT__={serverRendered:false,config:{public:{},app:{baseURL:"\u002F__nuxt_og_image__\u002Fclient",buildAssetsDir:"\u002F_nuxt\u002F",cdnURL:""}},data:{},state:{}}</script><script type="module" src="/__nuxt_og_image__/client/_nuxt/entry.
|
|
6
|
+
<body ><div id="__nuxt"></div><script>window.__NUXT__={serverRendered:false,config:{public:{},app:{baseURL:"\u002F__nuxt_og_image__\u002Fclient",buildAssetsDir:"\u002F_nuxt\u002F",cdnURL:""}},data:{},state:{}}</script><script type="module" src="/__nuxt_og_image__/client/_nuxt/entry.9b6fba4b.js" crossorigin></script></body>
|
|
7
7
|
</html>
|
package/dist/module.d.ts
CHANGED
|
@@ -39,9 +39,7 @@ interface ModuleOptions {
|
|
|
39
39
|
host: string;
|
|
40
40
|
defaults: OgImageOptions;
|
|
41
41
|
experimentalNitroBrowser: boolean;
|
|
42
|
-
|
|
43
|
-
publicPath: string;
|
|
44
|
-
}>;
|
|
42
|
+
fonts: `${string}:${number}`[];
|
|
45
43
|
satoriOptions: Partial<SatoriOptions>;
|
|
46
44
|
forcePrerender: boolean;
|
|
47
45
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { joinURL } from 'ufo';
|
|
|
9
9
|
import { resolve, relative } from 'pathe';
|
|
10
10
|
import { tinyws } from 'tinyws';
|
|
11
11
|
import sirv from 'sirv';
|
|
12
|
+
import { copy } from 'fs-extra';
|
|
12
13
|
import { createBirpcGroup } from 'birpc';
|
|
13
14
|
import { stringify, parse } from 'flatted';
|
|
14
15
|
|
|
@@ -179,25 +180,14 @@ const module = defineNuxtModule({
|
|
|
179
180
|
width: 1200,
|
|
180
181
|
height: 630
|
|
181
182
|
},
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
name: "Inter",
|
|
185
|
-
weight: 400,
|
|
186
|
-
style: "normal",
|
|
187
|
-
publicPath: "/inter-latin-ext-400-normal.woff"
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: "Inter",
|
|
191
|
-
weight: 700,
|
|
192
|
-
style: "normal",
|
|
193
|
-
publicPath: "/inter-latin-ext-700-normal.woff"
|
|
194
|
-
}
|
|
195
|
-
],
|
|
183
|
+
fonts: [],
|
|
196
184
|
satoriOptions: {}
|
|
197
185
|
};
|
|
198
186
|
},
|
|
199
187
|
async setup(config, nuxt) {
|
|
200
188
|
const { resolve } = createResolver(import.meta.url);
|
|
189
|
+
if (!config.fonts.length)
|
|
190
|
+
config.fonts = ["Inter:400", "Inter:700"];
|
|
201
191
|
const distResolve = (p) => {
|
|
202
192
|
const cwd = resolve(".");
|
|
203
193
|
if (cwd.endsWith("/dist"))
|
|
@@ -233,6 +223,10 @@ export {}
|
|
|
233
223
|
handler: resolve(`./runtime/nitro/routes/__og_image__/${type}`)
|
|
234
224
|
});
|
|
235
225
|
});
|
|
226
|
+
addServerHandler({
|
|
227
|
+
route: "/api/og-image-font",
|
|
228
|
+
handler: resolve("./runtime/nitro/routes/__og_image__/font")
|
|
229
|
+
});
|
|
236
230
|
if (nuxt.options.dev) {
|
|
237
231
|
const playgroundDir = distResolve("./client");
|
|
238
232
|
const {
|
|
@@ -269,13 +263,18 @@ export {}
|
|
|
269
263
|
});
|
|
270
264
|
const runtimeDir = resolve("./runtime");
|
|
271
265
|
nuxt.options.build.transpile.push(runtimeDir);
|
|
272
|
-
|
|
266
|
+
const fontDir = resolve(nuxt.options.buildDir, "nuxt-og-image");
|
|
267
|
+
const publicDirs = [`${nuxt.options.rootDir}/public`, fontDir];
|
|
268
|
+
exposeConfig("#nuxt-og-image/config", "nuxt-og-image-config.mjs", { ...config, publicDirs });
|
|
269
|
+
nuxt.hooks.hook("build:before", async () => {
|
|
270
|
+
await copy(resolve("./runtime/public"), resolve(nuxt.options.buildDir, "nuxt-og-image"));
|
|
271
|
+
});
|
|
273
272
|
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
274
273
|
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
275
274
|
inline: [runtimeDir]
|
|
276
275
|
});
|
|
277
276
|
nitroConfig.publicAssets = nitroConfig.publicAssets || [];
|
|
278
|
-
nitroConfig.publicAssets.push({ dir:
|
|
277
|
+
nitroConfig.publicAssets.push({ dir: fontDir, maxAge: 31536e3 });
|
|
279
278
|
nitroConfig.virtual["#nuxt-og-image/browser"] = `export { createBrowser } from '${runtimeDir}/nitro/browsers/${isEdge ? "lambda" : "default"}'`;
|
|
280
279
|
nitroConfig.virtual["#nuxt-og-image/provider"] = `
|
|
281
280
|
import satori from '${runtimeDir}/nitro/providers/satori'
|
|
@@ -2,11 +2,13 @@ import { html as convertHtmlToSatori } from "satori-html";
|
|
|
2
2
|
import satori from "satori";
|
|
3
3
|
import { parseURL } from "ufo";
|
|
4
4
|
import { Resvg } from "@resvg/resvg-js";
|
|
5
|
-
import
|
|
5
|
+
import twemoji from "twemoji";
|
|
6
|
+
import { loadFont, walkSatoriTree } from "./utils.mjs";
|
|
6
7
|
import imageSrc from "./plugins/imageSrc.mjs";
|
|
7
8
|
import twClasses from "./plugins/twClasses.mjs";
|
|
8
9
|
import flex from "./plugins/flex.mjs";
|
|
9
|
-
import
|
|
10
|
+
import emojis from "./plugins/emojis.mjs";
|
|
11
|
+
import { fonts, satoriOptions } from "#nuxt-og-image/config";
|
|
10
12
|
export default {
|
|
11
13
|
name: "satori",
|
|
12
14
|
createPng: async function createPng(baseUrl, options) {
|
|
@@ -19,11 +21,13 @@ export default {
|
|
|
19
21
|
const url = parseURL(baseUrl);
|
|
20
22
|
const html = await $fetch(url.pathname);
|
|
21
23
|
const body = html.match(/<body[^>]*>([\s\S]*)<\/body>/)?.[1];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const emojiedFont = twemoji.parse(body, {
|
|
25
|
+
folder: "svg",
|
|
26
|
+
ext: ".svg"
|
|
27
|
+
});
|
|
28
|
+
const satoriTree = convertHtmlToSatori(emojiedFont);
|
|
26
29
|
await walkSatoriTree(url, satoriTree, [
|
|
30
|
+
emojis(url),
|
|
27
31
|
twClasses(url),
|
|
28
32
|
imageSrc(url),
|
|
29
33
|
flex(url)
|
|
@@ -31,9 +35,15 @@ export default {
|
|
|
31
35
|
return satoriTree;
|
|
32
36
|
},
|
|
33
37
|
createSvg: async function createSvg(baseUrl, options) {
|
|
38
|
+
const url = parseURL(baseUrl);
|
|
34
39
|
const vnodes = await this.createVNode(baseUrl, options);
|
|
40
|
+
const satoriFonts = [];
|
|
41
|
+
for (const font of fonts)
|
|
42
|
+
satoriFonts.push(await loadFont(url, font));
|
|
35
43
|
return await satori(vnodes, {
|
|
36
44
|
...satoriOptions,
|
|
45
|
+
fonts: satoriFonts,
|
|
46
|
+
embedFont: true,
|
|
37
47
|
width: options.width,
|
|
38
48
|
height: options.height
|
|
39
49
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
|
+
export default defineSatoriTransformer(() => {
|
|
3
|
+
return {
|
|
4
|
+
filter: (node) => node.type === "img" && node.props?.class?.includes("emoji"),
|
|
5
|
+
transform: async (node) => {
|
|
6
|
+
node.props.style = node.props.style || {};
|
|
7
|
+
node.props.style.height = "1em";
|
|
8
|
+
node.props.style.width = "1em";
|
|
9
|
+
node.props.style.margin = "0 .05em 0 .1em";
|
|
10
|
+
node.props.style.verticalAlign = "0.1em";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineSatoriTransformer } from "../utils.mjs";
|
|
2
2
|
export default defineSatoriTransformer(() => {
|
|
3
3
|
return {
|
|
4
|
-
filter: (node) => node.type === "div" && (Array.isArray(node.props
|
|
4
|
+
filter: (node) => node.type === "div" && (Array.isArray(node.props?.children) && node.props?.children.length >= 1) && (!node.props.style?.display && !node.props?.class?.includes("flex")),
|
|
5
5
|
transform: async (node) => {
|
|
6
6
|
node.props.style = node.props.style || {};
|
|
7
7
|
node.props.style.display = "flex";
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
import { promises as fsp } from "node:fs";
|
|
3
1
|
import { withBase } from "ufo";
|
|
4
|
-
import {
|
|
5
|
-
import { defineSatoriTransformer } from "../utils.mjs";
|
|
6
|
-
import { getAsset } from "#internal/nitro/virtual/public-assets";
|
|
2
|
+
import { defineSatoriTransformer, readPublicAssetBase64 } from "../utils.mjs";
|
|
7
3
|
export default defineSatoriTransformer((url) => {
|
|
8
4
|
return {
|
|
9
5
|
filter: (node) => node.type === "img",
|
|
10
6
|
transform: async (node) => {
|
|
11
7
|
const src = node.props?.src;
|
|
12
8
|
if (src && src.startsWith("/")) {
|
|
13
|
-
const file =
|
|
14
|
-
if (file)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
node.props.src = `data:${file.type};base64,${await fsp.readFile(path, { encoding: "base64" })}`;
|
|
18
|
-
} else {
|
|
9
|
+
const file = await readPublicAssetBase64(src);
|
|
10
|
+
if (file)
|
|
11
|
+
node.props.src = file;
|
|
12
|
+
else
|
|
19
13
|
node.props.src = withBase(src, `${url.protocol}//${url.host}`);
|
|
20
|
-
}
|
|
21
14
|
}
|
|
22
15
|
}
|
|
23
16
|
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="node" />
|
|
2
2
|
import type { ParsedURL } from 'ufo';
|
|
3
3
|
import type { SatoriTransformer, VNode } from '../../../../types';
|
|
4
|
-
export declare function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
publicPath?: string | undefined;
|
|
8
|
-
}>;
|
|
4
|
+
export declare function readPublicAsset(file: string, encoding?: BufferEncoding): Promise<string | Buffer | undefined>;
|
|
5
|
+
export declare function readPublicAssetBase64(file: string): Promise<string | undefined>;
|
|
6
|
+
export declare function loadFont(url: ParsedURL, font: string): Promise<any>;
|
|
9
7
|
export declare function walkSatoriTree(url: ParsedURL, node: VNode, plugins: SatoriTransformer[]): Promise<void>;
|
|
10
8
|
export declare function defineSatoriTransformer(transformer: (url: ParsedURL) => SatoriTransformer): (url: ParsedURL) => SatoriTransformer;
|
|
@@ -1,29 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { existsSync, promises as fsp } from "fs";
|
|
2
|
+
import { join } from "pathe";
|
|
3
|
+
import { useStorage } from "#internal/nitro";
|
|
4
|
+
import { publicDirs } from "#nuxt-og-image/config";
|
|
5
|
+
const cachedFonts = {};
|
|
6
|
+
const r = (base, key) => {
|
|
7
|
+
return join(base, key.replace(/:/g, "/"));
|
|
8
|
+
};
|
|
9
|
+
export async function readPublicAsset(file, encoding) {
|
|
10
|
+
for (const d of publicDirs) {
|
|
11
|
+
const path = r(d, file);
|
|
12
|
+
if (existsSync(path))
|
|
13
|
+
return await fsp.readFile(path, { encoding });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function readPublicAssetBase64(file) {
|
|
17
|
+
const base64 = await readPublicAsset(file, "base64");
|
|
18
|
+
if (base64) {
|
|
19
|
+
let type = "image/jpeg";
|
|
20
|
+
const ext = file.split(".").pop();
|
|
21
|
+
if (ext === "svg")
|
|
22
|
+
type = "image/svg+xml";
|
|
23
|
+
else if (ext === "png")
|
|
24
|
+
type = "image/png";
|
|
25
|
+
return `data:${type};base64,${base64}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function loadFont(url, font) {
|
|
29
|
+
if (cachedFonts[font])
|
|
30
|
+
return cachedFonts[font];
|
|
31
|
+
const [name, weight] = font.split(":");
|
|
32
|
+
if (name === "Inter" && ["400", "700"].includes(weight)) {
|
|
33
|
+
const data2 = await readPublicAsset(`/inter-latin-ext-${weight}-normal.woff`);
|
|
34
|
+
if (data2) {
|
|
35
|
+
return cachedFonts[font] = { name, weight: weight === "400" ? "500" : weight, data: data2, style: "normal" };
|
|
12
36
|
}
|
|
13
|
-
if (!font.data)
|
|
14
|
-
font.data = await (await $fetch(withBase(font.publicPath, `${url.protocol}//${url.host}`))).arrayBuffer();
|
|
15
37
|
}
|
|
16
|
-
|
|
38
|
+
const fontUrl = await $fetch("/api/og-image-font", {
|
|
39
|
+
query: { name, weight }
|
|
40
|
+
});
|
|
41
|
+
let data;
|
|
42
|
+
const storageKey = `nuxt-og-image:font:${font}`;
|
|
43
|
+
const hasStoredFont = await useStorage().hasItem(storageKey);
|
|
44
|
+
if (!hasStoredFont) {
|
|
45
|
+
data = await $fetch(fontUrl, {
|
|
46
|
+
responseType: "arrayBuffer"
|
|
47
|
+
});
|
|
48
|
+
await useStorage().setItem(storageKey, data);
|
|
49
|
+
} else {
|
|
50
|
+
data = await useStorage().getItem(storageKey);
|
|
51
|
+
}
|
|
52
|
+
return cachedFonts[font] = { name, weight, data, style: "normal" };
|
|
17
53
|
}
|
|
18
54
|
export async function walkSatoriTree(url, node, plugins) {
|
|
19
55
|
if (!node.props?.children)
|
|
20
56
|
return;
|
|
21
57
|
for (const child of node.props.children || []) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
if (child) {
|
|
59
|
+
for (const plugin of plugins) {
|
|
60
|
+
if (plugin.filter(child))
|
|
61
|
+
await plugin.transform(child);
|
|
62
|
+
}
|
|
63
|
+
await walkSatoriTree(url, child, plugins);
|
|
25
64
|
}
|
|
26
|
-
await walkSatoriTree(url, child, plugins);
|
|
27
65
|
}
|
|
28
66
|
}
|
|
29
67
|
export function defineSatoriTransformer(transformer) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getQuery } from "h3";
|
|
2
|
+
import { defineCachedEventHandler } from "#internal/nitro";
|
|
3
|
+
export default defineCachedEventHandler(async (e) => {
|
|
4
|
+
const { name, weight } = getQuery(e);
|
|
5
|
+
if (!name || !weight)
|
|
6
|
+
return "Provide a font name and weight";
|
|
7
|
+
const css = await await $fetch(`https://fonts.googleapis.com/css2?family=${name}:wght@${weight}`, {
|
|
8
|
+
headers: {
|
|
9
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/);
|
|
13
|
+
if (!resource)
|
|
14
|
+
return;
|
|
15
|
+
return resource[1];
|
|
16
|
+
}, {
|
|
17
|
+
getKey: (e) => {
|
|
18
|
+
const query = getQuery(e);
|
|
19
|
+
return `nuxt-og-image:font-url:${query.name}:${query.weight}`;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -3,7 +3,7 @@ import { renderSSRHead } from "@unhead/ssr";
|
|
|
3
3
|
import { createHeadCore } from "@unhead/vue";
|
|
4
4
|
import { defineEventHandler, getQuery, sendRedirect } from "h3";
|
|
5
5
|
import { fetchOptions, renderIsland, useHostname } from "../../utils.mjs";
|
|
6
|
-
import { defaults } from "#nuxt-og-image/config";
|
|
6
|
+
import { defaults, fonts } from "#nuxt-og-image/config";
|
|
7
7
|
export default defineEventHandler(async (e) => {
|
|
8
8
|
const path = parseURL(e.path).pathname;
|
|
9
9
|
if (!path.endsWith("__og_image__/html"))
|
|
@@ -19,16 +19,28 @@ export default defineEventHandler(async (e) => {
|
|
|
19
19
|
head.push({
|
|
20
20
|
style: [
|
|
21
21
|
{
|
|
22
|
-
innerHTML:
|
|
22
|
+
innerHTML: `body { font-family: '${fonts[0].split(":")[0].replace("+", " ")}', sans-serif; }`
|
|
23
23
|
},
|
|
24
24
|
scale ? {
|
|
25
25
|
innerHTML: `body {
|
|
26
26
|
transform: scale(${scale});
|
|
27
27
|
transform-origin: top left;
|
|
28
28
|
max-height: 100vh;
|
|
29
|
+
}
|
|
30
|
+
img.emoji {
|
|
31
|
+
height: 1em;
|
|
32
|
+
width: 1em;
|
|
33
|
+
margin: 0 .05em 0 .1em;
|
|
34
|
+
vertical-align: -0.1em;
|
|
35
|
+
}
|
|
29
36
|
`
|
|
30
37
|
} : {}
|
|
31
38
|
],
|
|
39
|
+
meta: [
|
|
40
|
+
{
|
|
41
|
+
charset: "utf-8"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
32
44
|
script: [
|
|
33
45
|
{
|
|
34
46
|
src: "https://cdn.tailwindcss.com"
|
|
@@ -46,10 +58,13 @@ export default defineEventHandler(async (e) => {
|
|
|
46
58
|
href: "https://cdn.jsdelivr.net/npm/gardevoir",
|
|
47
59
|
rel: "stylesheet"
|
|
48
60
|
},
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
...fonts.map((font) => {
|
|
62
|
+
const [name, weight] = font.split(":");
|
|
63
|
+
return {
|
|
64
|
+
href: `https://fonts.googleapis.com/css2?family=${name}:wght@${weight}&display=swap`,
|
|
65
|
+
rel: "stylesheet"
|
|
66
|
+
};
|
|
67
|
+
})
|
|
53
68
|
]
|
|
54
69
|
});
|
|
55
70
|
const headChunk = await renderSSRHead(head);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-og-image",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"packageManager": "pnpm@7.8.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/harlan-zw",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@nuxt/kit": "3.0.0",
|
|
30
30
|
"@resvg/resvg-js": "^2.2.0",
|
|
31
|
+
"@types/fs-extra": "^11.0.1",
|
|
31
32
|
"birpc": "^0.2.3",
|
|
32
33
|
"chalk": "^5.2.0",
|
|
33
34
|
"chrome-launcher": "^0.15.1",
|
|
@@ -35,15 +36,17 @@
|
|
|
35
36
|
"execa": "^6.1.0",
|
|
36
37
|
"fast-glob": "^3.2.12",
|
|
37
38
|
"flatted": "^3.2.7",
|
|
39
|
+
"fs-extra": "^11.1.0",
|
|
38
40
|
"launch-editor": "^2.6.0",
|
|
39
41
|
"ohash": "^1.0.0",
|
|
40
|
-
"pathe": "^1.
|
|
42
|
+
"pathe": "^1.1.0",
|
|
41
43
|
"playwright-core": "^1.29.2",
|
|
42
44
|
"radix3": "^1.0.0",
|
|
43
45
|
"satori": "^0.1.1",
|
|
44
46
|
"satori-html": "^0.3.2",
|
|
45
47
|
"sirv": "^2.0.2",
|
|
46
48
|
"tinyws": "^0.1.0",
|
|
49
|
+
"twemoji": "^14.0.2",
|
|
47
50
|
"ufo": "^1.0.1"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|