HTMLとCSSからSVGを生成できるvercel/satoriを動かしてみる
はじめに
Next.jsではVercelのEdgeFunctions上で動作して動的にOGP画像を生成するライブラリに@vercel/og
というものがある。その中ではHTMLとCSSからSVGを生成するsatori
というライブラリが使われている。
去年の末にそのことを知ってからsatori
ずっと触ってみたかった。今回触ってみて実際に動かすことができたので紹介する。
動かし方
パッケージのインストール
今回はsatori
を動かすことだけが目的なので最小限の構成で行う。
npm i -D satori typescript ts-node react @types/react
TypeScriptを使いたいのでそれを実行するためのts-node
もインストールする。
また、satori
ではJSX構文を使ってHTML/CSSを記述することもでき、今回もJSX構文でsatori
を利用する。JSX構文で書く場合はreact
が必要なので一緒にインストールしておく。
tsconfig.jsonの準備
軽く動かしたいだけなのでtsc --init
でtsconfig.jsonを作成した。
./node_modules/.bin/tsc --init
また、JSXを利用するのでcompilerOptions
に"jsx": "react-jsx"
を指定する。
{
"compilerOptions": {
...その他の設定
"jsx": "react-jsx" // これを追加
}
}
フォントデータの準備
satori
ではSVG内で利用するフォントのデータを用意しておく必要がある。今回はGoogleFontsから適当にRobotoフォントをダウンロードして利用する。
これをsrc/assets/Roboto-Medium.ttf
に配置しておいた。
satoriの記述
src/App.tsx
に以下の処理を書いた。fs
でフォントデータを読み込み、satori
関数でSVGを生成する。第一引数でJSX形式でSVGのレイアウトを記述し、第二引数でSVGのサイズやフォントなどのオプションを設定する。
// src/App.tsx
import fs from 'fs'
import satori from 'satori'
async function App() {
const font = fs.readFileSync('./src/assets/Roboto-Medium.ttf')
const svg = await satori(
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
width: '100%',
color: 'black',
backgroundColor: 'green'
}}
>
hello, world
</div>,
{
width: 600,
height: 400,
fonts: [
{
name: 'Roboto',
data: font,
weight: 400,
style: 'normal',
},
],
},
)
console.log(svg)
}
export default App
src/main.ts
にこれを実行するための処理を書いた。軽く動かすだけならファイルを分割する必要はないと思われる。
// src/main.ts
import App from './App'
App()
いざ実行
package.jsonに実行スクリプトを用意しておく。
{
"scripts": {
"dev": "ts-node src/main.ts"
},
}
これを実行してみるとSVGが出力された。
$ npm run dev
<svg width="600" height="400" viewBox="0 0 600 400" xmlns="http://www.w3.org/2000/svg"><rect x="0" y="0" width="600" height="400" fill="green"/><path fill="black" d="M261.8 193.5L261.8 198.0Q262.8 196.9 264.2 196.9L264.2 196.9Q266.9 196.9 266.9 200.0L266.9 200.0L266.9 205.5L265.0 205.5L265.0 200.0Q265.0 199.2 264.7 198.8Q264.3 198.4 263.5 198.4L263.5 198.4Q262.4 198.4 261.8 199.4L261.8 199.4L261.8 205.5L259.9 205.5L259.9 193.5L261.8 193.5ZM272.6 205.6L272.6 205.6Q270.8 205.6 269.7 204.5Q268.5 203.4 268.5 201.5L268.5 201.5L268.5 201.2Q268.5 200.0 269.0 199.0Q269.5 198.0 270.4 197.4Q271.3 196.9 272.4 196.9L272.4 196.9Q274.1 196.9 275.0 198.0Q276.0 199.1 276.0 201.1L276.0 201.1L276.0 201.9L270.4 201.9Q270.5 202.9 271.1 203.5Q271.8 204.1 272.7 204.1L272.7 204.1Q274.0 204.1 274.8 203.1L274.8 203.1L275.8 204.0Q275.3 204.8 274.5 205.2Q273.6 205.6 272.6 205.6ZM272.4 198.4L272.4 198.4Q271.6 198.4 271.1 198.9Q270.6 199.5 270.5 200.5L270.5 200.5L274.1 200.5L274.1 200.3Q274.0 199.4 273.6 198.9Q273.1 198.4 272.4 198.4ZM279.5 193.5L279.5 205.5L277.6 205.5L277.6 193.5L279.5 193.5ZM283.5 193.5L283.5 205.5L281.6 205.5L281.6 193.5L283.5 193.5ZM285.3 201.3L285.3 201.2Q285.3 199.9 285.8 198.9Q286.2 197.9 287.1 197.4Q288.0 196.9 289.2 196.9L289.2 196.9Q290.9 196.9 292.0 198.0Q293.0 199.1 293.1 200.9L293.1 200.9L293.1 201.3Q293.1 202.6 292.6 203.6Q292.2 204.6 291.3 205.1Q290.4 205.6 289.2 205.6L289.2 205.6Q287.4 205.6 286.3 204.4Q285.3 203.3 285.3 201.3L285.3 201.3ZM287.2 201.3L287.2 201.3Q287.2 202.6 287.7 203.4Q288.2 204.1 289.2 204.1Q290.2 204.1 290.7 203.4Q291.2 202.6 291.2 201.2L291.2 201.2Q291.2 199.9 290.7 199.1Q290.1 198.4 289.2 198.4L289.2 198.4Q288.3 198.4 287.7 199.1Q287.2 199.9 287.2 201.3ZM295.0 208.0L295.0 208.0L294.0 207.4Q294.4 206.7 294.6 206.2Q294.8 205.7 294.8 205.1L294.8 205.1L294.8 203.6L296.5 203.6L296.5 205.0Q296.5 205.8 296.1 206.7Q295.6 207.5 295.0 208.0Z M307.9 197.0L309.6 202.9L311.0 197.0L312.8 197.0L310.5 205.5L309.0 205.5L307.2 199.7L305.4 205.5L303.8 205.5L301.5 197.0L303.4 197.0L304.7 202.8L306.5 197.0L307.9 197.0ZM313.8 201.3L313.8 201.2Q313.8 199.9 314.3 198.9Q314.7 197.9 315.6 197.4Q316.5 196.9 317.7 196.9L317.7 196.9Q319.4 196.9 320.5 198.0Q321.5 199.1 321.6 200.9L321.6 200.9L321.6 201.3Q321.6 202.6 321.1 203.6Q320.7 204.6 319.8 205.1Q318.9 205.6 317.7 205.6L317.7 205.6Q315.9 205.6 314.8 204.4Q313.8 203.3 313.8 201.3L313.8 201.3ZM315.7 201.3L315.7 201.3Q315.7 202.6 316.2 203.4Q316.7 204.1 317.7 204.1Q318.7 204.1 319.2 203.4Q319.7 202.6 319.7 201.2L319.7 201.2Q319.7 199.9 319.2 199.1Q318.6 198.4 317.7 198.4L317.7 198.4Q316.8 198.4 316.2 199.1Q315.7 199.9 315.7 201.3ZM327.7 197.0L327.6 198.8Q327.3 198.7 326.9 198.7L326.9 198.7Q325.6 198.7 325.1 199.7L325.1 199.7L325.1 205.5L323.2 205.5L323.2 197.0L325.0 197.0L325.1 198.0Q325.8 196.9 327.0 196.9L327.0 196.9Q327.4 196.9 327.7 197.0L327.7 197.0ZM330.9 193.5L330.9 205.5L329.0 205.5L329.0 193.5L330.9 193.5ZM332.6 201.2L332.6 201.2Q332.6 199.2 333.5 198.1Q334.4 196.9 335.9 196.9L335.9 196.9Q337.3 196.9 338.1 197.8L338.1 197.8L338.1 193.5L340.0 193.5L340.0 205.5L338.3 205.5L338.2 204.6Q337.3 205.6 335.9 205.6L335.9 205.6Q334.4 205.6 333.5 204.4Q332.6 203.2 332.6 201.2ZM334.5 201.4L334.5 201.4Q334.5 202.6 335.0 203.4Q335.5 204.1 336.4 204.1L336.4 204.1Q337.5 204.1 338.1 203.1L338.1 203.1L338.1 199.4Q337.6 198.4 336.4 198.4L336.4 198.4Q335.5 198.4 335.0 199.2Q334.5 199.9 334.5 201.4Z "/></svg>
これを表示してみたものが以下の画像だ。無事にsatori
を動かせたようだ。
今回のコードは以下のレポジトリに置いておいた。必要に応じて参考にしてみてほしい。
おわり
SVGをJSX構文で記述できて簡単に生成できたのはかなり便利で驚いた。satori
は内部で独自のレンダリングエンジンを実装しているっぽくて、完全にCSSのプロパティに対応できているというわけではないようだ。詳しくはREADMEを読んでみてほしい。