or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/marked@17.0.x

docs

index.md
tile.json

tessl/npm-marked

tessl install tessl/npm-marked@17.0.0

A markdown parser built for speed

parser-renderer.mddocs/reference/

Parser and Renderer

The Parser converts tokens to output using a Renderer. The default Renderer generates HTML, but custom renderers can produce other formats.

Class: Parser

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

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;
}

Class: TextRenderer

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;
}

Usage

Basic Parsing

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:

  1. Lexer creates token tree
  2. Parser renders tokens to HTML

Why Separate?

  • Allows token inspection/modification
  • Enables non-HTML output
  • Supports multi-pass processing

Custom Renderer

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 instance
  • this.parser.parse(tokens): Render block tokens
  • this.parser.parseInline(tokens): Render inline tokens
  • this.options: Current marked options

Syntax Highlighting in Code Blocks

import { 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, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

Code Renderer Notes:

  • Check if escaped is true before escaping
  • Validate language exists before highlighting
  • Handle highlighting errors gracefully
  • Preserve code as-is if language unknown

Custom Image Rendering

import { 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:

  • Use tokens for formatted alt text
  • Use text for plain alt text
  • Add accessibility attributes
  • Consider lazy loading for performance

Text Extraction

import { 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:

  • Search indexing
  • Content preview
  • Word counting
  • Plain text emails
  • Accessibility

Context-Aware Rendering

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:

  • Use closures to maintain state
  • Reset state appropriately
  • Consider multi-document scenarios

Conditional Rendering

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function escapeAttr(text) {
  return text.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

Accessing Parser from Renderer

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 tokens
  • this.parser.parseInline(tokens): Parse inline tokens
  • this.parser.parseInline(tokens, renderer): Parse inline with custom renderer

Non-HTML Output

import { 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:

  • Plain text
  • Terminal (ANSI) output
  • LaTeX
  • ReStructuredText
  • Custom XML/JSON

Markdown-to-Markdown Renderer

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() });

Instance vs Static Methods

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:

  • Multiple documents with same configuration
  • Performance (reuse instance)
  • State management across documents
  • Custom renderer reuse

Error Handling

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:

  • Validate token properties
  • Provide fallbacks for missing data
  • Use silent mode for user-generated content
  • Log errors for debugging
  • Don't throw in production renderers

Performance

  • The parser makes a single pass through tokens
  • Renderer methods are called once per token
  • Inline token parsing is recursive
  • Custom renderers should be efficient (called frequently)
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:

  • Cache expensive operations
  • Minimize regex in renderers
  • Avoid synchronous I/O
  • Reuse parser instances
  • Profile with real documents