x-satori
v0.2.0
Published
use Vue or Astro file to generate SVG image using Satori
Downloads
698
Maintainers
Readme
Local running example demo
npx degit Zhengqbbb/x-satori/playground/vue <file_name> # Vue
npx degit Zhengqbbb/x-satori/playground/astro <file_name> # Astro
cd <file_name>
pnpm install
# Development Model
pnpm dev:og
# [Generate] SVG
pnpm gen:svg
# [Generate] PNG
pnpm gen:png
Usage
npm install -D x-satori
⭐ Vue
- Example: examples/vue-vitepress ⭐⭐⭐
Example: playground/vue
- Dependency: Vue | Vite
$ npx x-satori --help
SYNOPSIS:
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>]
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>] --output <svg_path>
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>] --dev [--host --port <num>]
OPTIONS:
-d|--dev Turn on Dev mode
--host Expose host in Dev mode
--port <num> Specify port in Dev mode
-t|--template <path> The Vue or Astro template file path
-c|--config <path> The export satori configure file path
-o|--output <path> Target output SVG path
--props <JSON_str> 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
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
- → Tailwindcss documentation
<script setup lang="ts">
const props = defineProps({
title: String,
})
</script>
<template>
<div class="w-full h-full flex text-white bg-blue-500 items-center justify-center">
<h1 :style="{ fontSize: '70px' }">
{{ title }} 👋
</h1>
</div>
</template>
- Dependency: Vue
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
npm run gen:svg npm run gen:png
⭐ Astro
- Example: examples/astro-file-endpoint ⭐⭐⭐
- Example: Repo - Zhengqbbb/qbb.sh
1. Install Dependencies
npm install -D x-satori @resvg/resvg-js # Convert SVG to PNG
2. Create Astro file-endpoints
If target is generate
dist/og/*.png
. So that touch a filesrc/pages/og/[slug].png.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' },
},
)
- Dependency: Astro
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 ⭐⭐⭐
npm run gen:svg npm run gen:png
Example: Repo - Zhengqbbb/[email protected]/.x-cmd/og/main.ts
- Dependency: Astro | Vite (for dev mode)
$ npx x-satori --help
SYNOPSIS:
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>]
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>] --output <svg_path>
x-satori --template <template_file_path> --config <satori_config_path> [--props <JSON>] --dev [--host --port <num>]
OPTIONS:
-d|--dev Turn on Dev mode
--host Expose host in Dev mode
--port <num> Specify port in Dev mode
-t|--template <path> The Vue or Astro template file path
-c|--config <path> The export satori configure file path
-o|--output <path> Target output SVG path
--props <JSON_str> 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
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
- → Tailwindcss documentation
---
interface Props {
title: string
};
const { title = Hello world } = Astro.props;
---
<div class="w-full h-full text-1.4rem text-white flex flex-col items-center justify-between">
<h2 >
{title}
</h2>
</div>
- Example: playground/astro ⭐⭐⭐
- Example (Advanced) - Using
Shell Scripts
to batch image generation withresvg-cli
: Repo - Zhengqbbb/qbb.sh/.x-cmd/og
🎼 Command-line Advanced Usage
TIP: You can install it globally or use
bunx
for replacement startup
npx x-satori --config "./satori.ts" --template "./Template.vue" --props '{"title": "Hello World"}' | \
npx resvg-cli - image.png
TIP: You can install it globally or use
bunx
for replacement startup
npx x-satori --config "./satori.ts" --template "./Template.vue" --props '{"title": "Hello World"}' | \
npx resvg-cli - | \
magick - webp:image.webp
How it works
- ▲ Satori is an amazing library for generating SVG strings from pure HTML and CSS.
- Unfortunately, it is built on top of React's JSX and expects "React-elements-like objects".
- Thanks an library natemoo-re/satori-html can to generate the necessary VDOM object from a string of HTML.
- 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 filevue - createSSRApp
andvue/server-renderer
: transform HTML string
- Astro: a similar method:
@astrojs/compiler
: to transform.astro
tots
AstroContainer
: renderToString to obtain HTML string
Why developed
My Weekend Pilot Project
- This processing logic, initially used in my Vite-SSG person website qbb.sh, I prefer to run the script to generate e.g
tsx gen-og.mts
at my building time rather than the edge Fn - 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.
- 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.
Related Links
- nuxt-modules/og-image - Nuxt or want to use edge Fn
- Vercel / Open Graph (OG) Image Generation
FAQ
Not supported, waiting for upstream library natemoo-re/ultrahtml
Contributing
I did it step by step according to the documentation of Astro, Vue and Vite, if you are interested, PR welcome 🤗
pnpm install
pnpm dev # dev mode
pnpm x --help # start up the CLI and development
LICENSE
MIT Copyright (c) 2023-2024 Q.Ben Zheng