Incremental Markdown parser and compiler
Dependencies
npm install @mizchi/markdownimport { parse, toHtml, toMarkdown } from "@mizchi/markdown";
// Parse to AST
const ast = parse("# Hello\n\n**Bold** text");
console.log(ast.children[0].type); // "heading"
// Convert to HTML
const html = toHtml("See https://example.com/docs\n");
// => '<p>See <a href="https://example.com/docs">https://example.com/docs</a></p>\n'
// Disable bare URL links when you need plain text output
const plain = toHtml("See https://example.com/docs\n", { autolink: false });
// => "<p>See https://example.com/docs</p>\n"
// Normalize markdown
const normalized = toMarkdown("# Hello\n\n\n\nWorld");
// => "# Hello\n\nWorld\n"import { parse, toHtml } from "@mizchi/markdown";
const ast = parse("[[MoonBit#syntax|MoonBit syntax]]", { wikilinks: true });
// ast.children[0].children[0].type === "wikiLink"
const html = toHtml("[[MoonBit|MoonBit notes]]", { wikilinks: true });
// => '<p><a href="MoonBit">MoonBit notes</a></p>\n'import { createDocument, insertEdit } from "@mizchi/markdown";
// Create document handle
const doc = createDocument("# Hello");
// Access AST, HTML, or Markdown
console.log(doc.ast); // Parsed AST
console.log(doc.toHtml()); // "<h1>Hello</h1>\n"
console.log(doc.toMarkdown()); // "# Hello\n"
// Incremental update (faster than full re-parse)
const edit = insertEdit(7, 6); // Insert 6 chars at position 7
const newDoc = doc.update("# Hello World", edit);
// Free resources when done
doc.dispose();
newDoc.dispose();import { parse, Document, Block, Inline } from "@mizchi/markdown";
const ast: Document = parse("# Hello");
const heading = ast.children[0] as HeadingBlock;
console.log(heading.level); // 1moon add mizchi/markdown// Parse markdown
let result = @markdown.parse("# Hello\n\nWorld")
let doc = result.document
// Serialize back (lossless)
let output = @markdown.serialize(doc)
// Render to HTML
let html = @markdown.render_html(doc)
// Or use convenience function
let html = @markdown.md_to_html("# Hello\n\nWorld")
let linked = @markdown.md_to_html("See https://example.com/docs\n")
let plain = @markdown.md_to_html("See https://example.com/docs\n", autolink=false)
// Enable the WikiLink extension explicitly
let wiki_html = @markdown.md_to_html("[[MoonBit|MoonBit notes]]", wikilinks=true)// Initial parse
let result = @markdown.parse(source)
let doc = result.document
// Create edit info
let edit = @markdown.EditInfo::replace(
change_start, // Start position
old_length, // Length of replaced text
new_length // Length of new text
)
// Incremental update (reuses unchanged blocks)
let inc_result = @markdown.parse_incremental(doc, old_source, new_source, edit)
let new_doc = inc_result.documentpnpm install
moon build --target js
pnpm exec vitepnpm add @mizchi/markdown @luna_ui/lunaimport { SyntaxHighlightEditor } from "@mizchi/markdown/editor";
import "@mizchi/markdown/editor/style.css";
<SyntaxHighlightEditor
value={() => markdown}
onChange={(next) => setMarkdown(next)}
/>;import { loadHighlighter } from "@mizchi/markdown/highlight";
const highlightMoonBit = await loadHighlighter("moonbit");
const html = highlightMoonBit?.("fn main { println(\"hi\") }");| Document | Full Parse | Incremental | Speedup |
|---|---|---|---|
| 10 paragraphs | 68.89µs | 7.36µs | 9.4x |
| 50 paragraphs | 327.99µs | 8.67µs | 37.8x |
| 100 paragraphs | 651.14µs | 15.25µs | 42.7x |
pub(all) enum Block {
ThematicBreak(Char, Int, Span, Trivia, Trivia)
Heading(Int, HeadingStyle, Array[Inline], Int, Span, Trivia, Trivia)
Paragraph(Array[Inline], Span, Trivia, Trivia)
FencedCode(FenceMarker, Int, String, String, Int, Span, Trivia, Trivia)
IndentedCode(String, Span, Trivia, Trivia)
Blockquote(Array[Block], Span, Trivia, Trivia)
BulletList(BulletMarker, Bool, Array[ListItem], Span, Trivia, Trivia)
OrderedList(Int, OrderedDelimiter, Bool, Array[ListItem], Span, Trivia, Trivia)
HtmlBlock(String, Span, Trivia, Trivia)
Table(Array[TableCell], Array[TableAlign], Array[Array[TableCell]], Span, Trivia, Trivia)
BlankLines(Int, Span)
FootnoteDefinition(String, Array[Block], Span, Trivia, Trivia)
}impl Show for BulletMarkerpub(all) struct CodeBlockInfo {
lang : String
filename : String
meta : String
}pub(all) struct EditInfo {
offset : Int
old_len : Int
new_len : Int
}impl Show for EmphasisMarkerimpl Show for FenceMarkerimpl Show for HardBreakStyleimpl Show for HeadingStylepub(all) struct IncrementalResult {
document : Document
reused_before : Int
reparsed : Int
reused_after : Int
}pub(all) enum Inline {
Text(String, Span)
SoftBreak(Span)
HardBreak(HardBreakStyle, Span)
Emphasis(EmphasisMarker, Array[Inline], Span)
Strong(EmphasisMarker, Array[Inline], Span)
Strikethrough(Array[Inline], Span)
Code(String, Int, Span)
WikiLink(String, String, String, Span)
Link(Array[Inline], String, String, Span)
RefLink(Array[Inline], String, Span)
Autolink(String, Bool, Span)
Image(String, String, String, Span)
RefImage(String, String, Span)
HtmlInline(String, Span)
FootnoteReference(String, Span)
}impl Show for OrderedDelimiterfn RenderOptions::with_highlighter(highlighter : (CodeBlockInfo, String) -> String) -> RenderOptionsfn RenderOptions::with_simple_highlighter(highlighter : (String, String) -> String) -> RenderOptionsimpl Show for TableAlignfn is_unicode_punctuation(c : Char) -> Boolfn is_unicode_whitespace(c : Char) -> Boolfn md_parse_and_render(source : String, strict? : Bool, wikilinks? : Bool) -> Stringfn md_to_html(source : String, wikilinks? : Bool, autolink? : Bool) -> Stringfn md_to_html_literal(source : String, wikilinks? : Bool, positions? : Bool, image_preview? : Bool) -> Stringfn md_to_html_strict(source : String, wikilinks? : Bool, autolink? : Bool) -> Stringfn parse_incremental(old_doc : Document, old_source : String, new_source : String, edit : EditInfo, wikilinks? : Bool) -> IncrementalResultIncremental Markdown parser and compiler
Dependencies