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
@loadline, no data. The dictionary key is never consulted, and the reference stays unresolved. - References need a root. Resolving a document that uses
referencewithoutrootPaththrows — 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.