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:

  1. 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.

  2. SVG emit — each page frame is drawn with its children stacked top-to-bottom. A DrawContext carries the current x, y, and width so 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