@aldinokemal2104/excalidraw-to-svg
v1.1.1
Published
Node Library to transform excalidraw diagrams into svgs
Readme
Excalidraw to SVG
Node.js library to convert Excalidraw diagrams into standalone SVGs with embedded fonts. Useful if you are storing Excalidraw diagrams in repos and want a pipeline to export them, or need server-side SVG rendering.
Note: This is a Node.js library. If you want to export SVGs in a web app, use the @excalidraw/utils library directly.
Features
- CLI & API — Use from the command line or as a library in your Node.js project.
- Embedded fonts — Excalidraw's custom fonts (Excalifont, Virgil, Cascadia, Comic Shanns, Liberation Sans, Lilita One, Nunito) are automatically detected and embedded as base64
@font-facerules, so SVGs render correctly without external font dependencies. - Isolated runtime — Uses JSDOM inside a worker thread so
@excalidraw/utilscan run in Node.js without mutating your app's globalwindow,document,fetch, or other browser APIs. - Warm worker architecture — Reuses a single worker across back-to-back exports so Excalidraw, JSDOM, and font tooling stay hot instead of paying cold-start cost on every call.
- Font caching — Caches font file reads and subset results inside the worker to speed up repeated exports with the same fonts and glyph sets.
- Timeout and abort support — Supports
timeoutMsandAbortSignal, and terminates the worker thread if an active export times out or is aborted. - String or Object input — Pass in a raw JSON string or a parsed JavaScript object.
Installation
npm install @aldinokemal2104/excalidraw-to-svgUsage
CLI
You can run this package as a CLI tool via npx (or as a locally installed dependency). It takes two arguments: the path to the .excalidraw file and an optional output path (file or directory).
# Output to the same directory as the input (replaces .excalidraw with .svg)
npx @aldinokemal2104/excalidraw-to-svg ./diagrams/example.excalidraw
# Output to a specific directory
npx @aldinokemal2104/excalidraw-to-svg ./diagrams/example.excalidraw ./output
# Output to a specific file
npx @aldinokemal2104/excalidraw-to-svg ./diagrams/example.excalidraw ./output/my-diagram.svgAPI
Install the package as a dependency and call the exported function with an Excalidraw JSON object or string. The function returns a DOM SVGElement — use .outerHTML to get the string representation.
const excalidrawToSvg = require("@aldinokemal2104/excalidraw-to-svg");
const diagram = {
type: "excalidraw",
version: 2,
source: "https://excalidraw.com",
elements: [
{
id: "vWrqOAfkind2qcm7LDAGZ",
type: "ellipse",
x: 414,
y: 237,
width: 214,
height: 214,
angle: 0,
strokeColor: "#000000",
backgroundColor: "#15aabf",
fillStyle: "hachure",
strokeWidth: 1,
strokeStyle: "solid",
roughness: 1,
opacity: 100,
groupIds: [],
strokeSharpness: "sharp",
seed: 1041657908,
version: 120,
versionNonce: 1188004276,
isDeleted: false,
boundElementIds: null,
},
],
appState: {
viewBackgroundColor: "#ffffff",
gridSize: null,
},
};
const svgElement = await excalidrawToSvg(diagram);
console.log(svgElement.outerHTML);You can also pass a raw JSON string:
const fs = require("fs");
const excalidrawToSvg = require("@aldinokemal2104/excalidraw-to-svg");
const json = fs.readFileSync("./diagrams/example.excalidraw", "utf8");
const svgElement = await excalidrawToSvg(json);
console.log(svgElement.outerHTML);You can optionally enforce a timeout or cancel an in-flight export:
const controller = new AbortController();
const svgElement = await excalidrawToSvg(diagram, {
timeoutMs: 30_000,
signal: controller.signal,
});How It Works
Architecture Summary
- Public API call —
excalidrawToSvg(diagram, options)enqueues a request in the main thread. - Warm worker reuse — A single worker is reused for sequential exports, so the Excalidraw runtime, JSDOM polyfills, and
subset-fontinitialization stay hot between calls. - Queued execution — Only one export runs at a time inside the worker. Additional calls wait in a queue until the active export finishes.
- Runtime export — Inside the worker,
@excalidraw/utilsrenders the SVG withskipInliningFonts: true. - Font embedding with caches — The worker scans used fonts, reads font files from disk once, caches subset results by
font + glyph set, and injects@font-facerules into the SVG. - Main-thread parse — The worker returns serialized SVG markup, and the main thread parses it back into a DOM
SVGElement. - Idle cleanup — If there is no more work queued, the warm worker is automatically torn down on the next idle tick.
Mermaid Diagram
flowchart TD
A[Caller invokes excalidrawToSvg] --> B[Main thread creates request id and queue item]
B --> C{Warm worker exists?}
C -- No --> D[Spawn worker thread]
C -- Yes --> E[Reuse existing worker]
D --> F[Post request to worker]
E --> F
F --> G[Worker runtime receives queued request]
G --> H[Load or reuse JSDOM globals and @excalidraw/utils]
H --> I[exportToSvg with skipInliningFonts true]
I --> J[Collect used font families and glyphs]
J --> K{Subset/font cache hit?}
K -- Yes --> L[Reuse cached embedded font data]
K -- No --> M[Read font file and subset glyphs]
M --> L
L --> N[Inject @font-face CSS into SVG]
N --> O[Return serialized SVG markup to main thread]
O --> P[Parse markup with reusable DOMParser]
P --> Q[Resolve Promise with SVGElement]Timeout and Cancellation Behavior
- Queued request timeout/abort — If a request has not started yet, it is removed from the queue and never sent to the worker.
- Active request timeout/abort — If a request is already rendering, the promise rejects and the worker thread is terminated.
- No overlap after timeout — The next queued export does not start until the old worker's
terminate()promise resolves, so a timed-out render is not left running in the background. - Process isolation — Because cancellation happens by terminating the worker thread, CPU and memory used by the in-flight render are released with that worker rather than left attached to the main process.
Development
Prerequisites
- Node.js (with
--experimental-vm-modulessupport for tests)
Running Tests
npm testPublishing to npm
This package is configured to publish from GitHub Actions using npm Trusted Publisher with provenance.
- Bump the version in
package.json(or usenpm version patch/npm version minor). - Push your changes to GitHub.
- Run the publish workflow from the Actions tab.
If you need a manual fallback publish, you can still use an npm token:
NPM_TOKEN=your_npm_token_here npm run npm:publishThe manual command automatically runs tests, authenticates with npm, publishes the package with public access, and cleans up the auth file afterward.
Project Structure
src/
├── excalidraw-to-svg.js # Public API, warm worker queue, timeout/abort handling, SVG parsing
├── excalidraw-runtime.js # Worker runtime: JSDOM, Excalidraw export, font embedding, caching
├── excalidraw-to-svg.worker.js # Worker thread entry point and in-worker render queue
├── build-svg-path.js # Output path resolution for CLI
├── build-svg-path.test.js # Tests for path resolution
├── excalidraw-to-svg.test.js # Tests for SVG conversion, worker reuse, timeout safety
├── cli.js # CLI entry point
└── index.js # Package entry point (re-exports core)
diagrams/ # Example .excalidraw filesDependencies
@excalidraw/utils— Excalidraw's official export utilitiesjsdom— Browser environment simulation used inside the isolated worker runtimesubset-font— Font subsetting to keep embedded SVG fonts small
License
MIT
