How it works
Wiremark parses a DSL string, resolves named declarations, and renders an SVG — entirely in JavaScript, with no DOM dependency.
The pipeline
Every call to exportSVG runs four stages in sequence:
source string
│
▼
parse() — tokenise + build AST
│
▼
resolve() — expand refs, validate IDs
│
▼
render() — grid layout + SVG emit
│
▼
SVG string Each stage is independently exported so you can inspect or intercept at any point.
parse()
parse(source: string): Document
The lexer scans the source character-by-character, emitting typed tokens: KEYWORD, IDENTIFIER, STRING, ARROW, LBRACE, RBRACE, COMMA, NEWLINE. The parser then builds an AST of PageNode, ComponentNode, FlowNode, and Declaration nodes.
Errors are non-fatal: if the parser encounters an unexpected token it records a ParseError on the document and attempts to continue. This means a partially broken document still renders as much as it can.
import { parse } from "wiremark";
const doc = parse(`wiremarkUI\n page "Home" { }`);
console.log(doc.errors); // [] — no errors
console.log(doc.pages); // [{ kind: "page", title: "Home", children: [] }] resolve()
resolve(ast: Document): Document
The resolver walks every page’s children and replaces RefNode references with deep copies of the matching root-level Declaration. It also validates that every page ID used in a flow declaration actually exists.
import { parse, resolve } from "wiremark";
const doc = resolve(parse(`
wiremarkUI
sidebar SD { nav "Users" active, "Settings" }
page "Dashboard" {
SD
}
`));
// The RefNode "SD" has been replaced with a full ComponentNode copy. Unresolved references are recorded as UnresolvedReferenceError on the document rather than throwing.
render()
render(doc: Document): string
The renderer has two phases:
Grid layout — flow declarations (
A --> B -d-> C) are used to assign each page a{col, row}position. Pages not in any flow are placed in a remainder column to the right. Row and column heights are measured before drawing.SVG emit — each page frame is drawn with its children stacked top-to-bottom. A
DrawContextcarries the currentx,y, andwidthso every child component knows exactly where to place itself.
The output is a self-contained SVG string with embedded fonts via <defs> and a drop-shadow filter. No external resources are needed unless you add import "https://..." to your document.
exportSVG / exportPNG
exportSVG is a thin convenience wrapper:
export function exportSVG(source: string): string {
return render(resolve(parse(source)));
} exportPNG rasterises the SVG via resvg — a Rust SVG renderer compiled to WebAssembly for browser environments and used as a native Node.js addon in Node. The function is async because the WASM module is loaded on first call.
import { exportPNG } from "wiremark";
const buffer = await exportPNG(source); // Buffer in Node, Uint8Array in browser