tessl install tessl/npm-marked@17.0.0A markdown parser built for speed
The Parser converts tokens to output using a Renderer. The default Renderer generates HTML, but custom renderers can produce other formats.
class Parser {
constructor(options?: MarkedOptions);
/**
* Parse tokens to HTML (static method)
* Creates new Parser instance internally
* @param tokens - Array of tokens from lexer
* @param options - Parser options
* @returns HTML string
* @throws Error if tokens malformed and silent:false
*/
static parse(tokens: Token[], options?: MarkedOptions): string;
/**
* Parse inline tokens only (static method)
* Creates new Parser instance internally
* @param tokens - Array of inline tokens
* @param options - Parser options
* @returns HTML string (no wrapper tags)
*/
static parseInline(tokens: Token[], options?: MarkedOptions): string;
/**
* Parse tokens to HTML (instance method)
* Processes tokens sequentially using renderer
* @param tokens - Array of tokens
* @returns HTML string
*/
parse(tokens: Token[]): string;
/**
* Parse inline tokens (instance method)
* @param tokens - Array of inline tokens
* @param renderer - Optional custom renderer (defaults to this.renderer)
* @returns HTML string
*/
parseInline(tokens: Token[], renderer?: Renderer | TextRenderer): string;
/**
* Parser options
* Affects rendering behavior
*/
options: MarkedOptions;
/**
* Renderer instance for generating output
* Can be swapped for different output formats
*/
renderer: Renderer;
/**
* Text-only renderer instance
* Used for extracting plain text from tokens
*/
textRenderer: TextRenderer;
}class Renderer {
constructor(options?: MarkedOptions);
/**
* Renderer options
*/
options: MarkedOptions;
/**
* Reference to parser (set automatically)
* Use in renderer methods to parse nested tokens
*/
parser: Parser;
// Block-level rendering methods
/**
* Render space token (returns empty string)
* @param token - Space token
* @returns Empty string (whitespace handled in raw)
*/
space(token: Tokens.Space): string;
/**
* Render code block
* @param token - Code token with text, lang, escaped properties
* @returns HTML string: <pre><code class="language-*">...</code></pre>
*/
code(token: Tokens.Code): string;
/**
* Render blockquote
* @param token - Blockquote token with nested tokens
* @returns HTML string: <blockquote>...</blockquote>
*/
blockquote(token: Tokens.Blockquote): string;
/**
* Render HTML block/tag (pass-through)
* @param token - HTML token with text property
* @returns HTML string (raw HTML pass-through, not escaped)
*/
html(token: Tokens.HTML | Tokens.Tag): string;
/**
* Render link definition (returns empty string)
* @param token - Def token
* @returns Empty string (definitions don't render output)
*/
def(token: Tokens.Def): string;
/**
* Render heading
* @param token - Heading token with depth (1-6) and nested tokens
* @returns HTML string: <h1>...</h1> through <h6>...</h6>
*/
heading(token: Tokens.Heading): string;
/**
* Render horizontal rule
* @param token - Hr token
* @returns HTML string: <hr>
*/
hr(token: Tokens.Hr): string;
/**
* Render list
* @param token - List token with items array, ordered flag, start number
* @returns HTML string: <ul>...</ul> or <ol start="*">...</ol>
*/
list(token: Tokens.List): string;
/**
* Render list item
* @param item - ListItem token with nested tokens, task/checked flags
* @returns HTML string: <li>...</li>
*/
listitem(item: Tokens.ListItem): string;
/**
* Render task list checkbox (GFM)
* @param token - Checkbox token with checked property
* @returns HTML string: <input type="checkbox" checked?> (disabled by default)
*/
checkbox(token: Tokens.Checkbox): string;
/**
* Render paragraph
* @param token - Paragraph token with nested tokens
* @returns HTML string: <p>...</p>
*/
paragraph(token: Tokens.Paragraph): string;
/**
* Render table (GFM)
* @param token - Table token with header cells and row arrays
* @returns HTML string: <table><thead>...</thead><tbody>...</tbody></table>
*/
table(token: Tokens.Table): string;
/**
* Render table row
* @param token - TableRow token with text property (already rendered cells)
* @returns HTML string: <tr>...</tr>
*/
tablerow(token: Tokens.TableRow): string;
/**
* Render table cell
* @param token - TableCell token with tokens, header flag, align property
* @returns HTML string: <td align="*">...</td> or <th align="*">...</th>
*/
tablecell(token: Tokens.TableCell): string;
// Inline-level rendering methods
/**
* Render strong/bold
* @param token - Strong token with nested tokens
* @returns HTML string: <strong>...</strong>
*/
strong(token: Tokens.Strong): string;
/**
* Render emphasis/italic
* @param token - Em token with nested tokens
* @returns HTML string: <em>...</em>
*/
em(token: Tokens.Em): string;
/**
* Render inline code
* @param token - Codespan token with text property (already escaped)
* @returns HTML string: <code>...</code>
*/
codespan(token: Tokens.Codespan): string;
/**
* Render line break
* @param token - Br token
* @returns HTML string: <br>
*/
br(token: Tokens.Br): string;
/**
* Render strikethrough (GFM)
* @param token - Del token with nested tokens
* @returns HTML string: <del>...</del>
*/
del(token: Tokens.Del): string;
/**
* Render link
* @param token - Link token with href, title, nested tokens
* @returns HTML string: <a href="*" title="*">...</a>
*/
link(token: Tokens.Link): string;
/**
* Render image
* @param token - Image token with href, title, text/tokens
* @returns HTML string: <img src="*" alt="*" title="*">
*/
image(token: Tokens.Image): string;
/**
* Render text
* @param token - Text or Escape token with text property
* @returns HTML string (escaped if not already escaped)
*/
text(token: Tokens.Text | Tokens.Escape): string;
}Text-only renderer that extracts text content without HTML tags.
class TextRenderer {
/**
* Extract text from strong token (removes emphasis, keeps text)
* @param token - Strong token
* @returns Plain text string
*/
strong(token: Tokens.Strong): string;
/**
* Extract text from emphasis token (removes emphasis, keeps text)
* @param token - Em token
* @returns Plain text string
*/
em(token: Tokens.Em): string;
/**
* Extract text from inline code token (removes code formatting)
* @param token - Codespan token
* @returns Plain text string
*/
codespan(token: Tokens.Codespan): string;
/**
* Extract text from strikethrough token (keeps text, removes strikethrough)
* @param token - Del token
* @returns Plain text string
*/
del(token: Tokens.Del): string;
/**
* Extract text from HTML token (removes HTML)
* @param token - HTML token
* @returns Empty string (HTML not preserved in text mode)
*/
html(token: Tokens.HTML): string;
/**
* Extract text from text token
* @param token - Text token
* @returns Plain text string
*/
text(token: Tokens.Text): string;
/**
* Extract text from link token (returns link text, not URL)
* @param token - Link token
* @returns Plain text of link text
*/
link(token: Tokens.Link): string;
/**
* Extract text from image token (returns alt text)
* @param token - Image token
* @returns Alt text string
*/
image(token: Tokens.Image): string;
/**
* Render line break (returns empty string)
* @returns Empty string (breaks not preserved in text mode)
*/
br(): string;
/**
* Render checkbox (returns raw markdown)
* @param token - Checkbox token
* @returns String: [x] or [ ]
*/
checkbox(token: Tokens.Checkbox): string;
}import { Lexer, Parser } from "marked";
const markdown = '# Hello\n\nThis is **markdown**.';
// Tokenize
const tokens = Lexer.lex(markdown);
// Parse to HTML
const html = Parser.parse(tokens);
console.log(html);
// <h1>Hello</h1>
// <p>This is <strong>markdown</strong>.</p>Two-Phase Process:
Why Separate?
import { marked, Renderer } from "marked";
const renderer = new Renderer();
// Override heading rendering with anchor links
renderer.heading = ({ tokens, depth }) => {
const text = renderer.parser.parseInline(tokens);
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${depth} id="${id}">
<a href="#${id}" class="anchor" aria-hidden="true">#</a>
${text}
</h${depth}>\n`;
};
// Override link rendering for external links
renderer.link = ({ href, title, tokens }) => {
const text = renderer.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : '';
const external = href.startsWith('http') ? ' target="_blank" rel="noopener noreferrer"' : '';
return `<a href="${href}"${titleAttr}${external}>${text}</a>`;
};
marked.setOptions({ renderer });
const html = marked.parse('# Title\n\n[Link](https://example.com)');Renderer Method Context:
this.parser: Access to parser instancethis.parser.parse(tokens): Render block tokensthis.parser.parseInline(tokens): Render inline tokensthis.options: Current marked optionsimport { marked, Renderer } from "marked";
import hljs from "highlight.js";
const renderer = new Renderer();
renderer.code = ({ text, lang, escaped }) => {
// Validate language
if (lang && hljs.getLanguage(lang)) {
try {
const highlighted = hljs.highlight(text, { language: lang }).value;
return `<pre><code class="hljs language-${lang}">${highlighted}</code></pre>\n`;
} catch (err) {
console.error(`Highlight error for ${lang}:`, err);
// Fall through to default
}
}
// Escape if not already escaped
const code = escaped ? text : escapeHtml(text);
const langClass = lang ? ` class="language-${lang}"` : '';
return `<pre><code${langClass}>${code}</code></pre>\n`;
};
marked.setOptions({ renderer });
function escapeHtml(html) {
return html
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}Code Renderer Notes:
escaped is true before escapingimport { marked, Renderer } from "marked";
const renderer = new Renderer();
renderer.image = ({ href, title, text, tokens }) => {
// Parse tokens to get alt text (if tokens provided)
if (tokens && tokens.length > 0) {
text = renderer.parser.parseInline(tokens, renderer.parser.textRenderer);
}
const titleAttr = title ? ` title="${title}"` : '';
// Add lazy loading and responsive class
return `<img src="${href}" alt="${text}"${titleAttr} loading="lazy" class="responsive-img">`;
};
// Wrap images in figure with caption
const originalImage = renderer.image.bind(renderer);
renderer.image = (token) => {
const img = originalImage(token);
if (token.title) {
return `<figure>${img}<figcaption>${token.title}</figcaption></figure>`;
}
return img;
};
marked.setOptions({ renderer });Image Renderer Tips:
tokens for formatted alt texttext for plain alt textimport { Parser, TextRenderer, Lexer } from "marked";
const markdown = '# Heading\n\nThis is **bold** and *italic*.';
const tokens = Lexer.lex(markdown);
const textRenderer = new TextRenderer();
// Extract text from first token (heading)
const parser = new Parser();
const text = parser.parseInline(tokens[0].tokens, textRenderer);
console.log(text);
// "Heading" (no HTML tags, no formatting)
// Extract text from entire document
function extractText(markdown) {
const tokens = Lexer.lex(markdown);
let result = '';
function processTokens(tokens) {
tokens.forEach(token => {
if (token.type === 'text') {
result += token.text + ' ';
} else if (token.tokens) {
processTokens(token.tokens);
}
});
}
processTokens(tokens);
return result.trim();
}
console.log(extractText(markdown));
// "Heading This is bold and italic."Text Extraction Use Cases:
import { marked, Renderer } from "marked";
const renderer = new Renderer();
const headingNumbers = { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 };
renderer.heading = ({ tokens, depth }) => {
// Reset lower-level counters
for (let i = depth + 1; i <= 6; i++) {
headingNumbers[`h${i}`] = 0;
}
// Increment current level
headingNumbers[`h${depth}`]++;
// Build number prefix (e.g., "1.2.3")
const numbers = [];
for (let i = 1; i <= depth; i++) {
numbers.push(headingNumbers[`h${i}`]);
}
const numberPrefix = numbers.join('.') + '. ';
const text = renderer.parser.parseInline(tokens);
return `<h${depth}>${numberPrefix}${text}</h${depth}>\n`;
};
marked.setOptions({ renderer });
const html = marked.parse(`
# Chapter
## Section
### Subsection
## Another Section
`);
// <h1>1. Chapter</h1>
// <h2>1.1. Section</h2>
// <h3>1.1.1. Subsection</h3>
// <h2>1.2. Another Section</h2>Context State Management:
import { marked, Renderer } from "marked";
const renderer = new Renderer();
renderer.code = ({ text, lang, escaped }) => {
// Special handling for different languages
switch (lang) {
case 'mermaid':
return `<div class="mermaid">\n${text}\n</div>\n`;
case 'math':
case 'latex':
return `<div class="math-block">$$\n${text}\n$$</div>\n`;
case 'graphviz':
case 'dot':
return `<div class="graphviz" data-dot="${escapeAttr(text)}"></div>\n`;
default:
// Default code rendering
const code = escaped ? text : escapeHtml(text);
const langClass = lang ? ` class="language-${lang}"` : '';
return `<pre><code${langClass}>${code}</code></pre>\n`;
}
};
marked.setOptions({ renderer });
function escapeHtml(html) {
return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
function escapeAttr(text) {
return text.replace(/"/g, '"').replace(/'/g, ''');
}Renderer methods have access to the parser via this.parser:
import { marked, Renderer } from "marked";
const renderer = new Renderer();
renderer.listitem = (item) => {
// Use parser to process nested tokens
const content = renderer.parser.parse(item.tokens);
// Add custom class for task list items
const taskClass = item.task ? ' class="task-item"' : '';
const checked = item.checked ? ' data-checked="true"' : '';
return `<li${taskClass}${checked}>${content}</li>\n`;
};
// Custom blockquote with parsed content
renderer.blockquote = (token) => {
const body = renderer.parser.parse(token.tokens);
const type = detectQuoteType(body); // Custom logic
return `<blockquote class="quote-${type}">${body}</blockquote>\n`;
};
function detectQuoteType(html) {
if (html.includes('Warning')) return 'warning';
if (html.includes('Note')) return 'info';
return 'default';
}
marked.setOptions({ renderer });Parser Methods Available:
this.parser.parse(tokens): Parse block tokensthis.parser.parseInline(tokens): Parse inline tokensthis.parser.parseInline(tokens, renderer): Parse inline with custom rendererimport { Parser, Lexer } from "marked";
// Custom renderer for Markdown -> Plain Text
class PlainTextRenderer {
constructor() {
this.parser = null; // Set by parser
}
code({ text }) { return text + '\n\n'; }
blockquote({ tokens }) { return this.parser.parse(tokens); }
html({ text }) { return ''; } // Strip HTML
heading({ tokens }) { return this.parser.parseInline(tokens) + '\n\n'; }
hr() { return '---\n\n'; }
list({ items }) {
return items.map(item => '• ' + this.parser.parse(item.tokens)).join('') + '\n';
}
listitem({ tokens }) { return this.parser.parse(tokens); }
paragraph({ tokens }) { return this.parser.parseInline(tokens) + '\n\n'; }
table({ header, rows }) { return ''; } // Skip tables in plain text
tablerow() { return ''; }
tablecell() { return ''; }
strong({ tokens }) { return this.parser.parseInline(tokens); }
em({ tokens }) { return this.parser.parseInline(tokens); }
codespan({ text }) { return text; }
br() { return '\n'; }
del({ tokens }) { return this.parser.parseInline(tokens); }
link({ tokens }) { return this.parser.parseInline(tokens); }
image({ text }) { return `[Image: ${text}]`; }
text({ text }) { return text; }
checkbox({ checked }) { return checked ? '[x] ' : '[ ] '; }
def() { return ''; }
space() { return ''; }
}
// Use custom renderer
const tokens = Lexer.lex('# Hello\n\nThis is **markdown**.');
const parser = new Parser({ renderer: new PlainTextRenderer() });
const plainText = parser.parse(tokens);
console.log(plainText);
// Hello
//
// This is markdown.Non-HTML Format Examples:
import { marked, Renderer } from "marked";
// Renderer that outputs normalized markdown
class MarkdownRenderer extends Renderer {
code({ text, lang }) {
return `\`\`\`${lang || ''}\n${text}\n\`\`\`\n\n`;
}
heading({ tokens, depth }) {
const text = this.parser.parseInline(tokens);
return `${'#'.repeat(depth)} ${text}\n\n`;
}
list({ items, ordered, start }) {
let result = '';
items.forEach((item, i) => {
const marker = ordered ? `${start + i}. ` : '- ';
const content = this.parser.parse(item.tokens).trim();
result += marker + content + '\n';
});
return result + '\n';
}
paragraph({ tokens }) {
return this.parser.parseInline(tokens) + '\n\n';
}
strong({ tokens }) {
return `**${this.parser.parseInline(tokens)}**`;
}
em({ tokens }) {
return `*${this.parser.parseInline(tokens)}*`;
}
link({ href, title, tokens }) {
const text = this.parser.parseInline(tokens);
const titlePart = title ? ` "${title}"` : '';
return `[${text}](${href}${titlePart})`;
}
// ... implement other methods
}
marked.setOptions({ renderer: new MarkdownRenderer() });import { Parser, Renderer, Lexer } from "marked";
// Static method (creates new Parser instance internally)
const tokens = Lexer.lex(markdown);
const html1 = Parser.parse(tokens);
// Instance method (reuses instance and renderer)
const parser = new Parser();
const html2 = parser.parse(tokens);
// Custom instance with custom renderer
const customRenderer = new Renderer();
customRenderer.heading = ({ tokens, depth }) => {
return `<h${depth} class="custom">${this.parser.parseInline(tokens)}</h${depth}>\n`;
};
const customParser = new Parser({ renderer: customRenderer });
const html3 = customParser.parse(tokens);When to Use Instances:
Renderers can throw errors or use silent mode:
import { marked, Renderer } from "marked";
const renderer = new Renderer();
renderer.link = ({ href, title, tokens }) => {
// Validate links
if (!href) {
if (this.options.silent) {
return '<span class="broken-link">[broken link]</span>';
}
throw new Error('Link has no href');
}
const text = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : '';
return `<a href="${href}"${titleAttr}>${text}</a>`;
};
// With silent mode, errors are caught and displayed
marked.setOptions({ renderer, silent: true });
const html = marked.parse('[Invalid link]()');
// Returns: <p><span class="broken-link">[broken link]</span></p>
// Without silent mode, throws error
marked.setOptions({ renderer, silent: false });
try {
const html = marked.parse('[Invalid link]()');
} catch (err) {
console.error('Render error:', err);
}Error Handling Best Practices:
import { Parser, Lexer, Renderer } from "marked";
// Efficient: cache expensive operations
const cache = new Map();
const renderer = new Renderer();
renderer.code = ({ text, lang }) => {
const key = `${lang}:${text}`;
if (cache.has(key)) {
return cache.get(key);
}
const result = expensiveHighlight(text, lang);
// Limit cache size
if (cache.size > 1000) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, result);
return result;
};
function expensiveHighlight(text, lang) {
// Simulated expensive operation
return `<pre><code class="language-${lang}">${text}</code></pre>\n`;
}
// Measure performance
console.time('render');
const tokens = Lexer.lex(largeMarkdown);
const html = Parser.parse(tokens);
console.timeEnd('render');Performance Tips: