Satori for Edge Scripting for bunny.net 🐰
Satori with resvg
Satori is a library to convert HTML and CSS to SVG.
resvg is a high-performance SVG renderer and toolkit.
Quickstart
Installation
mkdir satori-examplecd satori-examplenpm init -ynpm install @bunny.net/edgescript-sdk bunny-satori bunny-resvg react react-domnpm install --save-dev esbuild esbuild-plugin-node-protocol-imports tsx @tsconfig/node20 @types/react @types/react-dom satori yoga-wasm-web
Add a build script to your package.json
file:
{ "scripts": { "build": "npx tsx src/esbuild" }}
Example
Add TypeScript support with React:
tsconfig.json
{ "extends": "@tsconfig/node20/tsconfig.json", "include": ["src"], "compilerOptions": { "noEmit": true, "jsx": "react-jsx", "jsxImportSource": "react", "resolvePackageJsonExports": true }}
Write the edge script:
src/index.tsx
import * as BunnySDK from '@bunny.net/edgescript-sdk'import { initSatori } from 'bunny-satori'import { initResvg } from 'bunny-resvg'let satori: Awaited<ReturnType<typeof initSatori>>let resvg: Awaited<ReturnType<typeof initResvg>>BunnySDK.net.http.serve(async (request: Request): Promise<Response> => { // keep script startup time under 500ms limit if (!satori || !resvg) { ;[satori, resvg] = await Promise.all([initSatori(), initResvg()]) } // jsx elements const element = <div style={{ color: 'black' }}>hello, world</div> // generate svg image const svg = await satori(element, { width: 600, height: 400, fonts: [ // if text is rendered, at least one font must be loaded { name: 'ABeeZee', weight: 400, data: await fetch( 'https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/abeezee/ABeeZee-Regular.ttf', ).then((res) => res.arrayBuffer()), style: 'normal', }, ], }) // generate png image const resvgJS = new resvg.Resvg(svg, {}) const pngData = resvgJS.render() const pngBuffer = pngData.asPng() // return image response return new Response(pngBuffer, { headers: { 'Content-Type': 'image/png', }, })})
Create a build script:
src/esbuild.ts
import fs from 'node:fs/promises'import { builtinModules } from 'node:module'import { build } from 'esbuild'import esbuildPluginNodeProtocolImports from 'esbuild-plugin-node-protocol-imports'const MINIFY = process.env.MINIFY === 'false' ? false : trueconst allBuiltinModules = [ ...builtinModules, ...builtinModules.map((builtinModule) => `node:${builtinModule}`),]await fs.rm('dist', { recursive: true, force: true,})await build({ alias: { '@bunny.net/edgescript-sdk': './node_modules/@bunny.net/edgescript-sdk/esm-bunny/lib.mjs', }, banner: { js: `import * as process from "node:process";import { Buffer } from "node:buffer";globalThis.process ??= process;globalThis.Buffer ??= Buffer;globalThis.global ??= globalThis;`, }, bundle: true, define: { 'process.env.NODE_ENV': '"production"' }, entryPoints: ['src/index.tsx'], external: allBuiltinModules, format: 'esm', keepNames: !MINIFY, minify: MINIFY, outdir: 'dist', packages: 'bundle', platform: 'node', sourcemap: !MINIFY, target: 'node20.17.0', plugins: [esbuildPluginNodeProtocolImports],})
For more information on bundling for edge scripts, please read the guide.
Deployment
Build
npm run build
Deploy
npx --yes bunny-launcher@latest --interactive