exportToSvg's font-embedding path (base64 @font-face rules) goes through the browser FontFace API, which jsdom doesn't implement -- that's what crashed fontFacesStylesGenerator after the previous global-shimming fix got past startup. skipInliningFonts avoids that path entirely; resvg already falls back to system fonts for rasterizing regardless, so embedded fonts were never going to affect the final PNG.
whiteboard-render
Local sidecar for whiteboard frame mode -- see server.js's own header
comment for the full "why Node, why not a headless browser" reasoning.
Not an independently deployed service: it runs as a second process
inside the main server's container (../Dockerfile installs Node,
../start.sh launches this in the background before exec-ing
uvicorn), reachable only at 127.0.0.1:3001 from the Python process in
that same container.
Not runtime-tested against a real npm install during development
-- this environment had no Node.js/npm available, only the npm registry
API (used to verify the dependency versions/licenses in package.json
actually exist and resolve). The code is written carefully against each
library's documented API (@excalidraw/utils's exportToSvg,
@resvg/resvg-js's Resvg class), but docker compose build+actually
running it is the first time this has executed end to end -- and the
first real run did in fact crash: @excalidraw/utils's bundle touches
bare browser globals (devicePixelRatio, location, matchMedia, ...)
the same way inline <script> code in a real page would, not as
window.foo -- jsdom only puts those on dom.window, so only
copying window/document/navigator onto Node's global (the
original version of this file) left everything else undefined. Fixed
by copying jsdom's entire window onto global plus a matchMedia
stub (jsdom doesn't implement it at all). If another crash like this
shows up, it's almost certainly the same shape -- another bare global
the bundle expects that this file hasn't stubbed yet. One other gap
worth knowing about going in: jsdom's <canvas> has no real 2D
rendering context (no node-canvas installed), so if Excalidraw's text
measurement path depends on canvas.getContext('2d').measureText(...)
rather than pure SVG/font-metrics math, that could be a next thing to
watch for -- not something confirmed broken, just not yet exercised.
Local development (if you have Node 20.19+/22.13+ installed)
cd render-service
npm install
npm start # listens on 127.0.0.1:3001
curl -X POST http://127.0.0.1:3001/render \
-H "Content-Type: application/json" \
-d '{"elements": [], "width": 800}' \
-o /tmp/test.png # an empty scene -- just checks the service comes up and returns a valid PNG