Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/zhengqbbb/x-satori

🌠 Using Astro or Vue file to generate cover, card, Open Graph ... image SVG, powered by Satori and Vite
https://github.com/zhengqbbb/x-satori

astro opengragh opengraph opengraph-images satori satori-html vite vue

Last synced: 2 days ago
JSON representation

🌠 Using Astro or Vue file to generate cover, card, Open Graph ... image SVG, powered by Satori and Vite

Awesome Lists containing this project

README

        

x-satori



version



Use Vue and Astro file to generate SVG image by Satori.

The image can be generated by running ESM script or CLI.







Vue



Open in StackBlitz





Astro



Open in StackBlitz






youtube guide demo






View on Twitter post




youtube guide demo






View on Twitter post


---

> Local running example demo

```sh
npx degit Zhengqbbb/x-satori/playground/vue # Vue
npx degit Zhengqbbb/x-satori/playground/astro # Astro

cd
pnpm install

# Development Model
pnpm dev:og

# [Generate] SVG
pnpm gen:svg

# [Generate] PNG
pnpm gen:png
```

## Usage

```sh
npm install -D x-satori
```

### ⭐ Vue

Using Vitepress buildEnd hook

- **Example**: [examples/vue-vitepress](./examples/vue-vitepress/) ⭐⭐⭐

Using CLI

**Example**: [playground/vue](./playground/vue/)

- Dependency: **Vue | Vite**

```sh
$ npx x-satori --help

SYNOPSIS:
x-satori --template --config [--props ]
x-satori --template --config [--props ] --output
x-satori --template --config [--props ] --dev [--host --port ]

OPTIONS:
-d|--dev Turn on Dev mode
--host Expose host in Dev mode
--port Specify port in Dev mode
-t|--template The Vue or Astro template file path
-c|--config The export satori configure file path
-o|--output Target output SVG path
--props Overwrite and use props in config

EXAMPLES:
x-satori --config "./satori.ts" --template "./Template.vue" --dev --host
x-satori --config "./satori.ts" --template "./Template.vue"
x-satori --config "./satori.js" --template "./Template.vue" --props '{"title": "Hello World"}'
x-satori --config "./satori.js" --template "./Template.vue" -o image.svg
```

#### Configure

- Extends Satori options and add Vue file props option

```mjs
import { defineSatoriConfig } from 'x-satori/vue'

export default defineSatoriConfig({
// ... Satori options
props: {
// ...Vue SFC props options
// title: "Hello world"
},
})
```

#### Vue template file

- **Only the template syntax is used**, and props are only used for hint completion
- [→ Satori supports common CSS features](https://github.com/vercel/satori#css)
- [→ Tailwindcss documentation](https://tailwindcss.com/docs/customizing-colors)

```html

const props = defineProps({
title: String,
})



{{ title }} 👋


```

Using ESM script

- Dependency: **Vue**

```mjs
import { defineSatoriConfig, satoriVue } from 'x-satori/vue'

function main() {
const _DIRNAME = typeof __dirname !== 'undefined'
? __dirname
: dirname(fileURLToPath(import.meta.url))
const _OUTPUT = resolve(_DIRNAME, './image/og.png')

const templateStr = await readFile(resolve(_DIRNAME, './Template.vue'), 'utf8')
const opt = defineSatoriConfig({
// ... Satori options
props: {
// ...Vue SFC props options
// title: "Hello world"
},
})
const strSVG = await satoriVue(opt, templateStr)
console.log(strSVG)
}
main()
```

- **Example**: [examples/vue-run-esm-script](./examples/vue-run-esm-script/)

```sh
npm run gen:svg
npm run gen:png
```

### ⭐ Astro

Using Astro file-endpoints

- **Example**: [examples/astro-file-endpoint](./examples/astro-file-endpoint/) ⭐⭐⭐
- **Example**: [Repo - Zhengqbbb/qbb.sh](https://github.com/Zhengqbbb/qbb.sh/blob/astro/src/pages/og/%5Bslug%5D.png.ts)

---

#### 1. Install Dependencies

```sh
npm install -D x-satori @resvg/resvg-js # Convert SVG to PNG
```

#### 2. Create Astro [file-endpoints](https://docs.astro.build/en/guides/endpoints/)

> If target is generate `dist/og/*.png`.

> So that touch a file `src/pages/og/[slug].png.ts`

```ts
import { readFile } from 'node:fs/promises'
import { type SatoriOptions, satoriAstro } from 'x-satori/astro'
import { Resvg } from '@resvg/resvg-js'
import type { APIRoute } from 'astro'
import { type CollectionEntry, getCollection } from 'astro:content';

export async function getStaticPaths() {
const posts = await getCollection('blog');

return posts
.map(post => ({
params: { slug: post.slug },
props: { ...post },
}));
}

async function getPostImageBuffer(props) {
const template = await readFile(/** .astro template file */, 'utf-8')
const config: SatoriOptions = {
//... satori options,
props: {
//...astro template file props
...props.data,
}
}
const svg = await satoriAstro(config, template)
const resvg = new Resvg(svg)
const pngData = resvg.render()
return pngData.asPng()
}

export const GET: APIRoute = async ({ props }) =>
new Response(
await getPostImageBuffer(props as CollectionEntry<'blog'>),
{
headers: { 'Content-Type': 'image/png' },
},
)
```

Using ESM script

- Dependency: **Astro**

```mjs
import { defineSatoriConfig, satoriAstro } from 'x-satori/astro'

function main() {
const _DIRNAME = typeof __dirname !== 'undefined'
? __dirname
: dirname(fileURLToPath(import.meta.url))
const _OUTPUT = resolve(_DIRNAME, './image/og.png')

const templateStr = await readFile(resolve(_DIRNAME, './Template.vue'), 'utf8')
const opt = defineSatoriConfig({
// ... Satori options
props: {
// ...Vue SFC props options
// title: "Hello world"
},
})
const strSVG = await satoriAstro(opt, templateStr)
console.log(strSVG)
}
main()
```

- **Example**: [examples/astro-run-esm-script](./examples/astro-run-esm-script/) ⭐⭐⭐

```sh
npm run gen:svg
npm run gen:png
```

- **Example**: [Repo - Zhengqbbb/[email protected]/.x-cmd/og/main.ts](https://github.com/Zhengqbbb/qbb.sh/blob/v2.1.1/.x-cmd/og/main.ts)

Using CLI

- Dependency: **Astro** | **Vite** (for dev mode)

```sh
$ npx x-satori --help

SYNOPSIS:
x-satori --template --config [--props ]
x-satori --template --config [--props ] --output
x-satori --template --config [--props ] --dev [--host --port ]

OPTIONS:
-d|--dev Turn on Dev mode
--host Expose host in Dev mode
--port Specify port in Dev mode
-t|--template The Vue or Astro template file path
-c|--config The export satori configure file path
-o|--output Target output SVG path
--props Overwrite and use props in config

EXAMPLES:
x-satori --config "./satori.ts" --template "./Template.astro" --dev --host
x-satori --config "./satori.ts" --template "./Template.astro"
x-satori --config "./satori.js" --template "./Template.astro" --props '{"title": "Hello World"}'
x-satori --config "./satori.js" --template "./Template.astro" -o image.svg
```

#### Configure

- Extends Satori options and add Vue file props option

```mjs
import { defineSatoriConfig } from 'x-satori/astro'

export default defineSatoriConfig({
// ... Satori options
props: {
// ...astro file props options
// title: "Hello world"
},
})
```

#### Astro template file

- **Only the template syntax is used**, and props are only used for hint completion
- [→ Satori supports common CSS features](https://github.com/vercel/satori#css)
- [→ Tailwindcss documentation](https://tailwindcss.com/docs/customizing-colors)

```astro
---
interface Props {
title: string
};

const { title = Hello world } = Astro.props;
---



{title}


```

- **Example**: [playground/astro](./playground/astro/) ⭐⭐⭐
- **Example (Advanced)** - Using `Shell Scripts` to batch image generation with `resvg-cli`:

[Repo - Zhengqbbb/qbb.sh/.x-cmd/og](https://github.com/Zhengqbbb/qbb.sh/blob/6fe03b051c1468417682da6e329be0e0b77c6468/.x-cmd/og#L37-L45)

### 🎼 Command-line Advanced Usage

pipeline generate png with resvg-cli

> TIP: You can install it globally or use `bunx` for replacement startup

```sh
npx x-satori --config "./satori.ts" --template "./Template.vue" --props '{"title": "Hello World"}' | \
npx resvg-cli - image.png
```

pipeline generate webp or more edit with resvg-cli and imagemagick

> TIP: You can install it globally or use `bunx` for replacement startup

```sh
npx x-satori --config "./satori.ts" --template "./Template.vue" --props '{"title": "Hello World"}' | \
npx resvg-cli - | \
magick - webp:image.webp
```

## How it works
1. ▲ [Satori](https://github.com/vercel/satori) is an amazing library for generating SVG strings from pure HTML and CSS.
2. Unfortunately, it is built on top of React's JSX and expects ["React-elements-like objects"](https://github.com/vercel/satori#use-without-jsx).
3. Thanks an library [natemoo-re/satori-html](https://github.com/natemoo-re/satori-html) can to generate the necessary VDOM object from a string of HTML.
4. So the key is to **convert the Vue SFC file to an HTML string**, and here I used transform so that I could generate it via script (Only the template syntax is used)
- `@vue/compiler-sfc`: to parse Vue SFC file
- `vue - createSSRApp` and `vue/server-renderer`: transform HTML string
5. Astro: a similar method:
- `@astrojs/compiler`: to transform `.astro` to `ts`
- `AstroContainer`: renderToString to obtain HTML string

## Why developed

> My Weekend Pilot Project

1. This processing logic, **initially used in my Vite-SSG person website** [qbb.sh](https://github.com/Zhengqbbb/qbb.sh/blob/790c47026cb1baac34dee8642150ec1729fb0f39/package.json#L18), I prefer to run the script to generate e.g `tsx gen-og.mts` at my building time rather than the edge Fn
2. And personally, I think Vue SFC File would be better in expressing this SVG structure, but I only use the template syntax and props, and the css would use tailwindcss.
3. I did a experiment this weekend, using Vite HRM to improve DX, and developed a CLI so that I could run command and generated the SVG directly.

I'm happy that I finally finished this series of experiments and results this weekend.



demo-img-1


demo-img-2




demo-img-3


demo-img-4




Vue


Vitepress buildEnd example:


examples/vue-vitepress/


ESM script code example:


qbb.sh@vitesse/node/og/main.mts


Astro


File endpoints example:


https://satori-astro.vercel.app


ESM script code example:


qbb.sh@astro/.x-cmd/og/main.ts

## Related Links

- [nuxt-modules/og-image](https://github.com/nuxt-modules/og-image) - Nuxt or want to use edge Fn
- [Vercel / Open Graph (OG) Image Generation](https://vercel.com/docs/functions/og-image-generation)

## FAQ

CJS support ?

**Not supported**, waiting for upstream library [natemoo-re/ultrahtml](https://github.com/natemoo-re/ultrahtml/tree/main)

## Contributing

> I did it step by step according to the documentation of Astro, Vue and Vite, if you are interested, PR welcome 🤗

```sh
pnpm install
pnpm dev # dev mode
pnpm x --help # start up the CLI and development
```

## LICENSE

MIT
Copyright (c) 2023-2024 [Q.Ben Zheng](https://github.com/Zhengqbbb)







I just try my best to make thing well.

Could you give a star ⭐ to encourage me 🤗


If possible, can to be my 💖 Sponsor 💖 to support my work