npm — @minjun0219/mdwire
The Rust core compiled to WASM, plus hand-written JS for React and an event list. Runs in Node, Bun and bundlers.
npm install @minjun0219/mdwire
The package picks its build by exports condition: Node gets a CommonJS build that loads the WASM
from disk, everything else gets the ESM bundler build. Under a bundler the WASM is imported as an
ES module — Vite needs vite-plugin-wasm and build.target: "esnext" (for the top-level await).
See examples/react-streaming/vite.config.js.
Channel and dialect names are the strings on Concepts.
render(input, channel, options?)
import { render } from "@minjun0219/mdwire";
const parts: string[] = render(markdown, "telegram-html");
Converts a finished document. Returns the parts to send — one string unless the channel’s limit (or
options.limit) splits it.
renderWithReport(input, channel, options?)
import { renderWithReport } from "@minjun0219/mdwire";
const out = renderWithReport(markdown, "slack-markdown", { from: "slack-mrkdwn" });
out.parts; // string[]
out.repairs.closedEmphasis; // see "Repairs" below
out.free(); // the result lives in WASM memory
render plus the repair report. The result and its repairs are WASM
objects: call free() when done, or use using (Symbol.dispose).
limit(channel)
The channel’s part limit as a number. html has none and returns 4294967295.
new Streamer(channel, options?)
import { Streamer } from "@minjun0219/mdwire";
const s = new Streamer("telegram-html");
let acc = "";
for await (const token of tokens) {
acc += s.push(token); // final — never rewritten
await edit(acc + s.preview()); // + what is held, drawn as if the input ended here
}
acc += s.finish();
if (s.revised()) await edit(acc); // skip when the last frame already equals the result
s.free();
| Method | Returns | |
|---|---|---|
push(chunk) |
string |
What is safe to send now. Final. |
preview() |
string |
The tail to append when redrawing the whole message. Does not change state; call it when you draw, not per chunk. |
closeOpen() |
string |
The tail that only closes block markup already sent. Does not change state. |
finish() |
string |
The rest, with open markup closed. |
revised() |
boolean |
After finish: does the result differ from the last preview() frame? |
repairs() |
Repairs |
What was repaired so far — the whole document after finish. |
free() |
Releases the WASM memory. |
For an append-only channel (Slack appendStream), send each push piece and never call preview().
See the streaming contract.
RenderOptions
interface RenderOptions {
from?: "markdown" | "slack-mrkdwn";
limit?: number; // a positive integer; below 256 is raised to 256
html?: { // the "html" channel only
lineBreaks?: "br" | "space"; // default "br"
images?: "link" | "load"; // default "link"
schemes?: string[]; // default ["http", "https", "mailto"]; replaces, does not add
};
}
An unknown channel or dialect name, an unknown html value, or a limit that is not a positive
integer throws an Error.
Repairs
| Field | |
|---|---|
closedEmphasis · closedFence · revertedCodeSpan · droppedMarker |
Repairs |
escapedChar · tagEmphasis · strippedHtml · rewrittenBullet · rewrittenTable · convertedMarker next release |
Channel rewrites |
What each counts: Concepts.
React — @minjun0219/mdwire/react
Builds React elements with createElement — no innerHTML. Escaping, the tag set and link schemes
are decided by the core’s html channel; you choose which component draws each tag.
import { Markdown, useMarkdownStream } from "@minjun0219/mdwire/react";
<Markdown text={answer} components={{ a: RouterLink }} />
const { elements, push, finish } = useMarkdownStream(); // push(token) … finish()
const settled = useMarkdownStream({ eager: false }); // only what is final
<Markdown text from? components? options? /> |
A finished answer. components maps a tag (a, code, …) to your component. |
useMarkdownStream({ from?, components?, options?, eager?, onSettled? }) |
Streaming. Returns { elements, push, finish }. eager (default true) draws held content early; false shows only what is final. onSettled(html, revised) runs after finish. Read once, except onSettled. |
toElements(html, components?, schemes?) |
html channel output (streaming: accumulated output + preview()) to React nodes. Pass the same schemes you gave the core. |
Events — @minjun0219/mdwire/events
import { toEvents } from "@minjun0219/mdwire/events";
toEvents(html); // [{ type: "open", tag: "p", attrs: {} }, { type: "text", text: "…" }, { type: "close", tag: "p" }, …]
The same html output as a flat open / text / close / void list, for other frameworks.
Attributes carry only what the core emits: href on a, class (language-…) on code, style
(text-align:…) on th and td, start on ol, and src · alt on img when images load.