nuxt-og-image 1.0.0-beta.9 → 1.1.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.
@@ -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.2a315a2c.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
+ 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.df5ad46e.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.2a315a2c.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
+ 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.df5ad46e.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.2a315a2c.js";const k={__name:"nuxt-error-page",props:{error:Object},setup(t){(t.error.stack||"").split(`
1
+ import{d as n,_ as o,o as _,c as f,n as g,g as E,u as r}from"./entry.df5ad46e.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.0c49154a.js"),["./error-404.0c49154a.js","./entry.2a315a2c.js","./entry.dc5450bf.css","./error-404.68aa58b4.css"],import.meta.url).then(e=>e.default||e)),l=n(()=>o(()=>import("./error-500.f3eec0bb.js"),["./error-500.f3eec0bb.js","./entry.2a315a2c.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};
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.8c9bfe7d.js"),["./error-404.8c9bfe7d.js","./entry.df5ad46e.js","./entry.dc5450bf.css","./error-404.68aa58b4.css"],import.meta.url).then(e=>e.default||e)),l=n(()=>o(()=>import("./error-500.cb171c6a.js"),["./error-500.cb171c6a.js","./entry.df5ad46e.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};
@@ -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.2a315a2c.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.7c753c74.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"}
4
+ <meta name="viewport" content="width=device-width, initial-scale=1"><link rel="modulepreload" as="script" crossorigin href="/__nuxt_og_image__/client/_nuxt/entry.df5ad46e.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.c6b1c57b.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.2a315a2c.js" crossorigin></script></body>
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.df5ad46e.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
- satoriFonts: Array<Partial<SatoriOptions['fonts'][number]> & {
43
- publicPath: string;
44
- }>;
42
+ fonts: `${string}:${number}`[];
45
43
  satoriOptions: Partial<SatoriOptions>;
46
44
  forcePrerender: boolean;
47
45
  }
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "nuxt-og-image",
3
3
  "compatibility": {
4
- "nuxt": "^3.0.0",
4
+ "nuxt": "^3.1.0",
5
5
  "bridge": false
6
6
  },
7
7
  "configKey": "ogImage",
8
- "version": "1.0.0-beta.9"
8
+ "version": "1.1.1"
9
9
  }
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'fs';
3
- import { useNuxt, addTemplate, defineNuxtModule, createResolver, getNuxtVersion, useLogger, addServerHandler, addImports, addComponent } from '@nuxt/kit';
3
+ import { useNuxt, addTemplate, defineNuxtModule, createResolver, addServerHandler, addImports, addComponent } from '@nuxt/kit';
4
4
  import { execa } from 'execa';
5
5
  import chalk from 'chalk';
6
6
  import defu from 'defu';
@@ -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
 
@@ -57,24 +58,6 @@ async function screenshot(browser, url, options) {
57
58
  return await page.screenshot();
58
59
  }
59
60
 
60
- function exposeConfig(alias, filename, config) {
61
- const exports = Object.entries(config).map(([k, v]) => `export const ${k} = ${JSON.stringify(v)}`).join("\n");
62
- useNuxt().options.alias[alias] = addTemplate({
63
- filename,
64
- getContents: () => exports
65
- }).dst;
66
- useNuxt().hooks.hook("nitro:config", (nitroConfig) => {
67
- nitroConfig.virtual[alias] = exports;
68
- });
69
- }
70
- function extractOgImageOptions(html) {
71
- const options = html.match(/<script id="nuxt-og-image-options" type="application\/json">(.+?)<\/script>/)?.[1];
72
- return options ? JSON.parse(options) : false;
73
- }
74
- function stripOgImageOptions(html) {
75
- return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
76
- }
77
-
78
61
  function setupPlaygroundRPC(nuxt, config) {
79
62
  const serverFunctions = {
80
63
  getConfig() {
@@ -157,6 +140,48 @@ function getBodyJson(req) {
157
140
  });
158
141
  }
159
142
 
143
+ function exposeModuleConfig(moduleName, config) {
144
+ const nuxt = useNuxt();
145
+ const jsExports = Object.entries(config).map(([k, v]) => `export const ${k} = ${JSON.stringify(v)}`).join("\n");
146
+ const alias = `#${moduleName}/config`;
147
+ nuxt.options.alias[alias] = addTemplate({
148
+ filename: `modules/config/${moduleName}.mjs`,
149
+ getContents: () => jsExports
150
+ }).dst;
151
+ nuxt.hooks.hook("nitro:config", (nitroConfig) => {
152
+ nitroConfig.virtual[alias] = jsExports;
153
+ });
154
+ const typeDefName = `modules/config/${moduleName}.d.ts`;
155
+ const tsExports = Object.keys(config).map((k) => {
156
+ if (nuxt.options.dev)
157
+ return ` export const ${k}: ${JSON.stringify(config[k])} | ModuleOptions['${k}']`;
158
+ return ` export const ${k}: ModuleOptions['${k}']`;
159
+ }).join("\n");
160
+ addTemplate({
161
+ filename: typeDefName,
162
+ getContents: () => {
163
+ return `// generated by ${moduleName}
164
+ import type { ModuleOptions } from '${moduleName}'
165
+ declare module '${alias}' {
166
+ ${tsExports}
167
+ }
168
+ `;
169
+ }
170
+ });
171
+ nuxt.hooks.hook("prepare:types", ({ references }) => {
172
+ references.push({ path: resolve(nuxt.options.buildDir, typeDefName) });
173
+ });
174
+ return alias;
175
+ }
176
+
177
+ function extractOgImageOptions(html) {
178
+ const options = html.match(/<script id="nuxt-og-image-options" type="application\/json">(.+?)<\/script>/)?.[1];
179
+ return options ? JSON.parse(options) : false;
180
+ }
181
+ function stripOgImageOptions(html) {
182
+ return html.replace(/<script id="nuxt-og-image-options" type="application\/json">(.*?)<\/script>/, "");
183
+ }
184
+
160
185
  const PATH = "/__nuxt_og_image__";
161
186
  const PATH_ENTRY = `${PATH}/entry`;
162
187
  const PATH_PLAYGROUND = `${PATH}/client`;
@@ -164,7 +189,7 @@ const module = defineNuxtModule({
164
189
  meta: {
165
190
  name: "nuxt-og-image",
166
191
  compatibility: {
167
- nuxt: "^3.0.0",
192
+ nuxt: "^3.1.0",
168
193
  bridge: false
169
194
  },
170
195
  configKey: "ogImage"
@@ -179,25 +204,14 @@ const module = defineNuxtModule({
179
204
  width: 1200,
180
205
  height: 630
181
206
  },
182
- satoriFonts: [
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
- ],
207
+ fonts: [],
196
208
  satoriOptions: {}
197
209
  };
198
210
  },
199
211
  async setup(config, nuxt) {
200
212
  const { resolve } = createResolver(import.meta.url);
213
+ if (!config.fonts.length)
214
+ config.fonts = ["Inter:400", "Inter:700"];
201
215
  const distResolve = (p) => {
202
216
  const cwd = resolve(".");
203
217
  if (cwd.endsWith("/dist"))
@@ -206,10 +220,6 @@ const module = defineNuxtModule({
206
220
  };
207
221
  nuxt.options.experimental.componentIslands = true;
208
222
  const isEdge = (process.env.NITRO_PRESET || "").includes("edge");
209
- const hasIslandSupport = getNuxtVersion(nuxt) !== "3.0.0";
210
- const logger = useLogger("nuxt-og-image");
211
- if (!hasIslandSupport)
212
- logger.warn("You are using Nuxt 3.0.0 with `nuxt-og-image`, which only supports screenshots.\nPlease upgrade to Nuxt 3.0.1 or the edge channel: https://nuxt.com/docs/guide/going-further/edge-channel.");
213
223
  addTemplate({
214
224
  filename: "nuxt-og-image.d.ts",
215
225
  getContents: () => {
@@ -233,6 +243,10 @@ export {}
233
243
  handler: resolve(`./runtime/nitro/routes/__og_image__/${type}`)
234
244
  });
235
245
  });
246
+ addServerHandler({
247
+ route: "/api/og-image-font",
248
+ handler: resolve("./runtime/nitro/routes/__og_image__/font")
249
+ });
236
250
  if (nuxt.options.dev) {
237
251
  const playgroundDir = distResolve("./client");
238
252
  const {
@@ -269,13 +283,18 @@ export {}
269
283
  });
270
284
  const runtimeDir = resolve("./runtime");
271
285
  nuxt.options.build.transpile.push(runtimeDir);
272
- exposeConfig("#nuxt-og-image/config", "nuxt-og-image-config.mjs", config);
286
+ const fontDir = resolve(nuxt.options.buildDir, "nuxt-og-image");
287
+ const publicDirs = [`${nuxt.options.rootDir}/public`, fontDir];
288
+ exposeModuleConfig("nuxt-og-image", { ...config, publicDirs });
289
+ nuxt.hooks.hook("build:before", async () => {
290
+ await copy(resolve("./runtime/public"), resolve(nuxt.options.buildDir, "nuxt-og-image"));
291
+ });
273
292
  nuxt.hooks.hook("nitro:config", (nitroConfig) => {
274
293
  nitroConfig.externals = defu(nitroConfig.externals || {}, {
275
294
  inline: [runtimeDir]
276
295
  });
277
296
  nitroConfig.publicAssets = nitroConfig.publicAssets || [];
278
- nitroConfig.publicAssets.push({ dir: resolve("./runtime/public"), maxAge: 31536e3 });
297
+ nitroConfig.publicAssets.push({ dir: fontDir, maxAge: 31536e3 });
279
298
  nitroConfig.virtual["#nuxt-og-image/browser"] = `export { createBrowser } from '${runtimeDir}/nitro/browsers/${isEdge ? "lambda" : "default"}'`;
280
299
  nitroConfig.virtual["#nuxt-og-image/provider"] = `
281
300
  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 { parseFont, walkSatoriTree } from "./utils.mjs";
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 { satoriFonts, satoriOptions } from "#nuxt-og-image/config";
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
- satoriOptions.fonts = satoriOptions.fonts || [];
23
- for (const font of satoriFonts)
24
- satoriOptions.fonts.push(await parseFont(url, font));
25
- const satoriTree = convertHtmlToSatori(body);
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,2 @@
1
+ declare const _default: (url: import("ufo").ParsedURL) => import("../../../../../types").SatoriTransformer;
2
+ export default _default;
@@ -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.children) && node.props.children.length >= 1) && (!node.props.style?.display && !node.props.class?.includes("flex")),
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 { dirname, resolve } from "pathe";
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 = getAsset(src);
14
- if (file) {
15
- const serverDir = dirname(fileURLToPath(import.meta.url));
16
- const path = resolve(serverDir, file.path);
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
- import type { SatoriOptions } from 'satori';
1
+ /// <reference types="node" />
2
2
  import type { ParsedURL } from 'ufo';
3
3
  import type { SatoriTransformer, VNode } from '../../../../types';
4
- export declare function parseFont(url: ParsedURL, font: SatoriOptions['fonts'][number] & {
5
- publicPath?: string;
6
- }): Promise<import("satori").Font & {
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 { fileURLToPath } from "node:url";
2
- import { promises as fsp } from "node:fs";
3
- import { dirname, resolve } from "pathe";
4
- import { withBase } from "ufo";
5
- import { getAsset } from "#internal/nitro/virtual/public-assets";
6
- export async function parseFont(url, font) {
7
- if (typeof font.publicPath === "string") {
8
- const file = getAsset(font.publicPath);
9
- if (file) {
10
- const serverDir = dirname(fileURLToPath(import.meta.url));
11
- font.data = await fsp.readFile(resolve(serverDir, file.path));
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
- return font;
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
- for (const plugin of plugins) {
23
- if (plugin.filter(child))
24
- await plugin.transform(child, url);
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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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: "body { font-family: 'Inter', sans-serif; }"
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
- href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap",
51
- rel: "stylesheet"
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-beta.9",
4
+ "version": "1.1.1",
5
5
  "packageManager": "pnpm@7.8.0",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/harlan-zw",
@@ -26,38 +26,40 @@
26
26
  "dist"
27
27
  ],
28
28
  "dependencies": {
29
- "@nuxt/kit": "3.0.0",
29
+ "@nuxt/kit": "3.1.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",
34
- "defu": "^6.1.1",
35
+ "defu": "^6.1.2",
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.0.0",
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": {
50
53
  "@antfu/eslint-config": "^0.34.1",
51
- "@nuxt/kit": "3.0.0",
52
54
  "@nuxt/module-builder": "^0.2.1",
53
- "@nuxt/test-utils": "3.0.0",
55
+ "@nuxt/test-utils": "3.1.0",
54
56
  "@nuxtjs/eslint-config-typescript": "^12.0.0",
55
57
  "@types/ws": "^8.5.4",
56
58
  "bumpp": "^8.2.1",
57
59
  "eslint": "8.32.0",
58
60
  "nuxt": "npm:nuxt3@latest",
59
- "puppeteer": "^19.5.2",
60
- "vitest": "^0.27.2"
61
+ "puppeteer": "^19.6.0",
62
+ "vitest": "^0.28.1"
61
63
  },
62
64
  "scripts": {
63
65
  "build": "pnpm dev:prepare && pnpm build:module && pnpm build:client",