Markdown parser with Aozora bunko ruby support and HTML passthrough
npm install @minamorl/markdown-nextA markdown parser that outputs either HTML strings or an AST you can render however you want. Built with Parsimmon.
What makes it different:
- AST output for React/Vue/custom renderers (not just HTML strings)
- Aozora bunko ruby syntax: |漢字《かんじ》
- HTML passthrough: write or directly in your markdown
- Plugin system: define custom @[plugin:args] blocks
``bash`
npm install @minamorl/markdown-next
The simplest way to get HTML:
`typescript
import { parse } from '@minamorl/markdown-next'
parse('# Hello world')
// '
Getting an AST Instead
If you're building a React app or need fine-grained control over rendering, use the AST output:
`typescript
import { Parser, asAST } from '@minamorl/markdown-next'const parser = new Parser({ export: asAST })
const ast = parser.parse('# Hello world')
`The AST uses a simple tuple format:
[tagName, attributes, children]`typescript
// Input: '# Hello world'
// Output:
[
['h1', null, ['Hello ', ['strong', null, 'world']]]
]
`$3
Here's a minimal renderer:
`tsx
import { Parser, asAST, ASTNode } from '@minamorl/markdown-next'function renderNode(node: ASTNode, key: number = 0): React.ReactNode {
if (typeof node === 'string') return node
if (!Array.isArray(node)) return null
const [tag, attrs, children] = node
// Self-closing tags
if (tag === 'img' || tag === 'br' || tag === 'hr') {
return React.createElement(tag, { ...attrs, key })
}
// Recursively render children
const kids = Array.isArray(children)
? children.map((child, i) => renderNode(child, i))
: renderNode(children, 0)
return React.createElement(tag, { ...attrs, key }, kids)
}
// Usage
const parser = new Parser({ export: asAST })
function Markdown({ content }: { content: string }) {
const ast = parser.parse(content)
return <>{ast.map((node, i) => renderNode(node, i))}>
}
`Japanese Ruby Annotations
Supports Aozora bunko ruby syntax, commonly used in Japanese publishing:
`typescript
parse('|漢字《かんじ》')
// '漢字
'parse('This is |日本語《にほんご》 text')
// '
This is 日本語 text
'
`The
| (full-width pipe) marks the start of the base text, and 《》 contains the ruby reading.HTML Passthrough
Sometimes you need HTML that markdown doesn't support. Just write it:
`typescript
parse('The Art of Computer Programming')
// 'The Art of Computer Programming
'parse('東京')
// '
東京
'parse('Click me
Hidden content')
// '
Click me
Hidden content
'
`Plugins
Define custom syntax with plugins. Useful for embeds, custom components, or domain-specific markup.
`typescript
const parser = new Parser({
export: asHTML,
plugins: {
youtube: (args) => {
return
},
note: (args, content, mapper) => {
return mapper('div', { class: note note-${args || 'info'} })(content)
}
}
})// Inline plugin
parser.parse('Check this video: @[youtube:dQw4w9WgXcQ]')
// Block plugin with content
parser.parse(
)
`Plugin function signature:
`typescript
type Plugin = (
args: string, // Everything after the colon
content: Content, // Parsed content (for block plugins)
mapper: ElementMapper, // Helper to create elements
join: JoinFunction // Helper to join content
) => string | ASTNode
`Supported Markdown
| Syntax | Example |
|--------|---------|
| Headers |
# H1 through ###### H6 |
| Alt headers | Header\n=== or Header\n--- |
| Bold | text or __text__ |
| Italic | text or _text_ |
| Code | inline or fenced blocks |
| Links | text |
| Images | !alt |
| Lists | - item or 1. item |
| Nested lists | 2-space indent |
| Blockquotes | > text |
| Tables | | a | b | with |---|---| separator |
| Ruby | |base《reading》 |
| HTML | Any valid HTML tag |API Reference
$3
Quick function for HTML output.
$3
`typescript
const parser = new Parser({
export: asHTML, // or asAST
plugins: { ... } // optional
})parser.parse(markdown) // returns T (string for asHTML, ASTNode[] for asAST)
`$3
Export type that produces HTML strings.
$3
Export type that produces an array of AST nodes.
$3
`typescript
type ASTNode = string | [string, Record | null, ASTNode | ASTNode[]]
``I needed:
1. Ruby annotation support for Japanese text
2. AST output for React (not just HTML strings)
3. Extensibility without forking
Most parsers give you HTML. That's fine until you need to render in React, handle custom components, or process the structure programmatically. The AST output solves this.
The ruby and HTML passthrough features came from working with Japanese content where standard markdown falls short.
MIT