54 lines · 1.9 KB
import satori, { init as initSatori } from "satori/standalone";
import { initWasm, Resvg } from "@resvg/resvg-wasm";
import type { ReactNode } from "react";
import { OG_HEIGHT, OG_WIDTH } from "~/lib/og";

/** Wasm modules and font bytes; the Worker passes bundled modules, tests and scripts read the same files from disk. */
export type OgRuntime = {
  yoga: WebAssembly.Module;
  resvg: WebAssembly.Module;
  fonts: { regular: ArrayBuffer; semibold: ArrayBuffer };
};

let satoriReady: Promise<void> | null = null;
let resvgReady: Promise<void> | null = null;

function alreadyInitialized(error: unknown): boolean {
  return error instanceof Error && error.message.startsWith("Already initialized");
}

/** Each wasm instance lives for the isolate and is initialized once; a failed init is retried on the next render. */
function ensureReady(runtime: OgRuntime): Promise<[void, void]> {
  satoriReady ??= initSatori(runtime.yoga).catch((error: unknown) => {
    satoriReady = null;
    throw error;
  });
  resvgReady ??= initWasm(runtime.resvg).catch((error: unknown) => {
    // A reloaded module (dev HMR) loses this flag while resvg keeps its instance.
    if (alreadyInitialized(error)) return;
    resvgReady = null;
    throw error;
  });
  return Promise.all([satoriReady, resvgReady]);
}

export async function renderPng(element: ReactNode, runtime: OgRuntime): Promise<Uint8Array> {
  await ensureReady(runtime);
  const svg = await satori(element, {
    width: OG_WIDTH,
    height: OG_HEIGHT,
    fonts: [