Wit

Embed the Wit parser

Run the Wit pipeline inside your own program — parse a document, feed it your own data, get HTML back — with no CLI, no subprocess and no config file.

Step 1 — Install

npm install @witlang/parser @witlang/runtime @witlang/render-html

Step 2 — Run the whole pipeline

parse, then loadExternalData (only if the document loads data), then resolve, expand and a renderer.

import { parse } from '@witlang/parser';
import { loadExternalData, resolve, expand } from '@witlang/runtime';
import { renderHtml } from '@witlang/render-html';

const src = `@load meta load@
@load sales load@

(h1 Release @meta.version h1)

Cut @meta.date.

@table(rows: @sales)
`;

// A plain dictionary pairs each @load alias with its data.
const doc = loadExternalData(parse(src, 'report.wit'), {
  meta:  { version: '0.4.0', date: '2026-07-05' },
  sales: [{ site: 'Dunmore', lit: 'yes' }, { site: 'Harbour', lit: 'no' }],
});

const html = renderHtml(expand(resolve(doc)), { mode: 'document', title: 'Release' });

Result. html is a full page whose article holds

<h1>Release 0.4.0</h1><p>Cut 2026-07-05.</p>
<table><thead><tr><th>site</th><th>lit</th></tr></thead>
<tbody><tr><td>Dunmore</td><td>yes</td></tr><tr><td>Harbour</td><td>no</td></tr></tbody></table>

Omit mode (or pass 'fragment') for just the <article class="wit-doc"> element; pass css to replace the inlined theme.

The data seam

A data source is a plain dictionary or a loader function (req) => value, where req carries alias, args (the use's other named parameters) and loc. A thrown error becomes E_LOAD_FAILED at the @load line. Strings from the loader are literal text — never read as Wit markup.

const doc = loadExternalData(parse(src, 'report.wit'), (req) => {
  if (req.alias === 'meta') return { version: '0.4.0', date: '2026-07-05' };
  throw new Error(`unknown alias ${req.alias}`);
});

Multi-file documents

If the source uses reference, pass resolve a rootPath (the root file's absolute path) and optionally a synchronous fileReader: (absPath) => string. Referenced files are parsed by the resolver; pass prepare to run loadExternalData on each of them too.

A use of a body-less definition from another file needs no close, but the parser only learns those names if you scan the graph first. Do what the CLI does:

import { parse } from '@witlang/parser';
import { scanCloseless, loadExternalData, resolve, expand } from '@witlang/runtime';
import { renderHtml } from '@witlang/render-html';

const closeless = scanCloseless(rootPath, source);          // Set<string>
const doc = loadExternalData(parse(source, rootPath, { closeless }), data);
const resolved = resolve(doc, {
  rootPath,
  closeless,
  prepare: (d) => loadExternalData(d, data),
});
const html = renderHtml(expand(resolved));

Pitfalls

  • No @load line, no data. The dictionary key is never consulted, and the reference stays unresolved.
  • References need a root. Resolving a document that uses reference without rootPath throws — the resolver never silently drops a reference.
  • Skipping scanCloseless. A use such as @sig, defined in another file and followed by more text in its paragraph, then reads as an unclosed container (E_UNCLOSED_NODE).

See also

Embed Wit in an app has the full API; Write a custom renderer shares this front half of the pipeline; A report with live data crosses the same seam through the CLI.