tessl install tessl/npm-marked@17.0.0A markdown parser built for speed
Configuration options for customizing marked's parsing and rendering behavior.
interface MarkedOptions {
/**
* Enable async parsing - returns Promise from parse functions
* Required when using async walkTokens or async hooks
* Default: false
*/
async?: boolean;
/**
* Enable GFM line breaks (requires gfm: true)
* Converts single \n in paragraphs to <br>
* Without this, only two spaces + \n creates <br>
* Default: false
*/
breaks?: boolean;
/**
* Enable GitHub Flavored Markdown features:
* - Tables (pipe-delimited)
* - Strikethrough (~~text~~)
* - Autolinks (raw URLs become clickable)
* - Task lists (- [ ] and - [x])
* Default: true
*/
gfm?: boolean;
/**
* Conform to obscure parts of markdown.pl behavior
* Don't fix original markdown bugs or poor behavior
* Generally should stay false unless specific compatibility needed
* Default: false
*/
pedantic?: boolean;
/**
* Show HTML error message when rendering fails instead of throwing
* Useful for user-generated content where errors shouldn't break page
* Default: false
*/
silent?: boolean;
/**
* Custom hooks for processing pipeline
* See Hooks documentation for details
* Default: null
*/
hooks?: Hooks | null;
/**
* Custom renderer for generating output from tokens
* Override to customize HTML output or create non-HTML formats
* Default: new Renderer()
*/
renderer?: Renderer | null;
/**
* Custom tokenizer for recognizing markdown syntax
* Override methods to customize how markdown is recognized
* Default: new Tokenizer()
*/
tokenizer?: Tokenizer | null;
/**
* Custom extensions (populated by use())
* Generally not set directly - use the use() method instead
* Contains tokenizer and renderer extensions
* Default: null
*/
extensions?: {
renderers: { [name: string]: RendererExtensionFunction };
childTokens: { [name: string]: string[] };
inline?: TokenizerExtensionFunction[];
block?: TokenizerExtensionFunction[];
startInline?: TokenizerStartFunction[];
startBlock?: TokenizerStartFunction[];
} | null;
/**
* Function called for each token, useful for preprocessing
* Called after tokenization but before rendering
* Can modify tokens in place
* Default: null
*/
walkTokens?: ((token: Token) => void | Promise<void> | (void | Promise<void>)[]) | null;
}import { marked } from "marked";
// Enable GFM line breaks
marked.setOptions({
breaks: true,
gfm: true
});
const html = marked.parse("Line 1\nLine 2");
// Output: <p>Line 1<br>Line 2</p>
// Without breaks: true, output would be: <p>Line 1\nLine 2</p>Common Combinations:
{ gfm: true, breaks: true }{ gfm: false, pedantic: false }{ pedantic: true, gfm: false }import { marked } from "marked";
// Enable async mode
marked.setOptions({ async: true });
// Now marked returns a Promise
const html = await marked.parse("# Async Markdown");
// Or use explicit async option
const html2 = await marked.parse("# Title", { async: true });When to Use Async:
walkTokens callbacksAsync Performance Note: Async mode adds overhead even for sync operations. Only enable when needed.
import { marked } from "marked";
// Default behavior (throw errors)
try {
const html = marked.parse(malformedMarkdown);
} catch (err) {
console.error('Parse error:', err);
}
// Silent mode (return error as HTML)
marked.setOptions({ silent: true });
const html = marked.parse(malformedMarkdown);
// Returns: <p>An error occurred:</p><pre>Error message</pre>Silent Mode Use Cases:
Warning: Silent mode may hide legitimate bugs. Use sparingly.
import { marked } from "marked";
// Conform to original markdown.pl behavior
marked.setOptions({
pedantic: true,
gfm: false
});
const html = marked.parse("# Title");
// Example differences:
// - Stricter emphasis rules
// - Different list handling
// - No tables, autolinks, or GFM featuresPedantic Mode Notes:
gfm: trueimport { marked, Renderer } from "marked";
const renderer = new Renderer();
// Customize code block rendering
renderer.code = ({ text, lang, escaped }) => {
const language = lang || 'text';
return `<pre><code class="language-${language}">${text}</code></pre>\n`;
};
// Customize heading rendering
renderer.heading = ({ tokens, depth }) => {
const text = this.parser.parseInline(tokens);
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
return `<h${depth} id="${id}">${text}</h${depth}>\n`;
};
marked.setOptions({ renderer });Renderer Customization Use Cases:
import { marked } from "marked";
// Set multiple options at once
marked.setOptions({
gfm: true,
breaks: true,
silent: false,
async: false
});
// Options can be overridden per parse call
const html = marked.parse(markdown, {
breaks: false // Override global setting
});Option Precedence (highest to lowest):
use())setOptions())Extensions provide a way to customize marked without modifying global options. Extensions can add custom tokenizers, renderers, and hooks.
interface MarkedExtension {
/**
* Enable async mode for this extension
* Set to true if extension uses async operations
*/
async?: boolean;
/**
* Enable GFM line breaks for this extension
*/
breaks?: boolean;
/**
* Enable GFM features for this extension
*/
gfm?: boolean;
/**
* Enable pedantic mode for this extension
*/
pedantic?: boolean;
/**
* Enable silent mode for this extension
*/
silent?: boolean;
/**
* Array of custom tokenizer and renderer extensions
* Each extension adds new token types or overrides existing ones
*/
extensions?: (TokenizerExtension | RendererExtension | (TokenizerExtension & RendererExtension))[] | null;
/**
* Partial hooks object to extend default hooks
* Multiple extensions can define hooks - they chain together
*/
hooks?: {
preprocess?: (markdown: string) => string | Promise<string>;
postprocess?: (html: string) => string | Promise<string>;
processAllTokens?: (tokens: Token[] | TokensList) => Token[] | TokensList | Promise<Token[] | TokensList>;
emStrongMask?: (src: string) => string;
provideLexer?: () => Function;
provideParser?: () => Function;
} | null;
/**
* Partial renderer object to override default rendering methods
* Return false from method to fall back to default renderer
*/
renderer?: {
[key: string]: (token: any) => string | false;
} | null;
/**
* Partial tokenizer object to override default tokenization methods
* Return false/undefined from method to fall back to default tokenizer
*/
tokenizer?: {
[key: string]: (src: string, ...args: any[]) => any;
} | null;
/**
* Function to walk all tokens
* Called for each token in the tree
* Can modify tokens in place
*/
walkTokens?: ((token: Token) => void | Promise<void>) | null;
}import { marked } from "marked";
// Using extension to customize behavior
marked.use({
gfm: true,
breaks: true,
renderer: {
heading({ tokens, depth }) {
const text = this.parser.parseInline(tokens);
return `<h${depth} class="custom-heading">${text}</h${depth}>\n`;
},
link({ href, title, tokens }) {
const text = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : '';
// Add target="_blank" to external links
const external = href.startsWith('http') ? ' target="_blank" rel="noopener"' : '';
return `<a href="${href}"${titleAttr}${external}>${text}</a>`;
}
},
walkTokens(token) {
if (token.type === 'link') {
// Validate links
if (!isValidUrl(token.href)) {
console.warn('Invalid URL:', token.href);
}
}
}
});
function isValidUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
}Extension Benefits:
import { marked } from "marked";
// Get default options (original defaults, not current)
const defaults = marked.getDefaults();
console.log(defaults);
// Output:
// {
// async: false,
// breaks: false,
// gfm: true,
// pedantic: false,
// silent: false,
// hooks: null,
// renderer: null,
// tokenizer: null,
// extensions: null,
// walkTokens: null
// }
// Access current defaults (including modifications)
console.log(marked.defaults);
// Shows current default configuration
// Reset to original defaults
marked.setOptions(marked.getDefaults());Configuration Inspection:
import { marked } from "marked";
// Check current configuration
function inspectConfig() {
const config = marked.defaults;
console.log('GFM enabled:', config.gfm);
console.log('Breaks enabled:', config.breaks);
console.log('Async mode:', config.async);
console.log('Silent mode:', config.silent);
console.log('Custom renderer:', config.renderer !== null);
console.log('Custom hooks:', config.hooks !== null);
}
inspectConfig();When using multiple option sources, they are applied in order with later options overriding earlier ones:
getDefaults())setOptions())use())parse())import { marked } from "marked";
// 1. Original defaults
// gfm: true, breaks: false
// 2. Set global default
marked.setOptions({ breaks: false, silent: false });
// 3. Use extension
marked.use({ breaks: true, async: false }); // breaks: true now overrides global
// 4. Parse-time option
const html = marked.parse('text', { breaks: false }); // breaks: false overrides extensionPriority Example:
import { marked } from "marked";
// Global: breaks=false
marked.setOptions({ breaks: false });
// Extension: breaks=true (overrides global)
marked.use({ breaks: true });
// Parse-time: breaks=false (overrides extension)
const html1 = marked.parse('Line 1\nLine 2', { breaks: false });
// Output: <p>Line 1\nLine 2</p> (no <br>)
// Without parse-time override, extension setting used
const html2 = marked.parse('Line 1\nLine 2');
// Output: <p>Line 1<br>Line 2</p> (has <br>)Create isolated instances with different configurations:
import { Marked } from "marked";
// Instance for GitHub-flavored markdown
const gfmMarked = new Marked({
gfm: true,
breaks: true
});
// Instance for strict CommonMark
const strictMarked = new Marked({
gfm: false,
pedantic: false
});
// Instance for pedantic markdown.pl compatibility
const pedanticMarked = new Marked({
pedantic: true,
gfm: false
});
// Each instance has independent options
const html1 = gfmMarked.parse('Line 1\nLine 2');
// Output: <p>Line 1<br>Line 2</p>
const html2 = strictMarked.parse('Line 1\nLine 2');
// Output: <p>Line 1\nLine 2</p>
const html3 = pedanticMarked.parse('Line 1\nLine 2');
// Output: <p>Line 1\nLine 2</p> (with pedantic quirks)Instance Isolation:
import { Marked, marked } from "marked";
// Global marked configuration
marked.setOptions({ breaks: true });
// Create instance (doesn't inherit global config)
const instance = new Marked({ breaks: false });
// Global and instance are independent
const html1 = marked.parse('Line\nBreak'); // Has <br>
const html2 = instance.parse('Line\nBreak'); // No <br>import { marked } from "marked";
// Valid options
marked.setOptions({
gfm: true,
breaks: true,
silent: false
});
// Invalid/unknown options are ignored (no error)
marked.setOptions({
unknownOption: true, // Ignored
gfm: 'yes' // Type mismatch, may cause issues
});
// Type-safe with TypeScript
import type { MarkedOptions } from "marked";
const options: MarkedOptions = {
gfm: true,
breaks: true,
// unknownOption: true // TypeScript error
};
marked.setOptions(options);import { marked } from "marked";
import DOMPurify from "dompurify";
// Safe parsing of untrusted content
marked.setOptions({
silent: true, // Don't throw on errors
gfm: true, // Support GFM features
breaks: true // Easier for users
});
function safeRender(markdown) {
const html = marked.parse(markdown);
return DOMPurify.sanitize(html);
}import { marked } from "marked";
import hljs from "highlight.js";
marked.use({
gfm: true,
breaks: false, // Strict paragraphs
renderer: {
code({ text, lang }) {
if (lang && hljs.getLanguage(lang)) {
const highlighted = hljs.highlight(text, { language: lang }).value;
return `<pre><code class="hljs language-${lang}">${highlighted}</code></pre>\n`;
}
return false;
},
heading({ tokens, depth }) {
const text = this.parser.parseInline(tokens);
const id = text.toLowerCase().replace(/[^\w]+/g, '-');
return `<h${depth} id="${id}"><a href="#${id}">#</a> ${text}</h${depth}>\n`;
}
}
});import { marked } from "marked";
marked.use({
gfm: true,
breaks: false,
renderer: {
link({ href, title, tokens }) {
const text = this.parser.parseInline(tokens);
const titleAttr = title ? ` title="${title}"` : '';
// Open external links in new tab
const target = href.startsWith('http') ? ' target="_blank" rel="noopener noreferrer"' : '';
return `<a href="${href}"${titleAttr}${target}>${text}</a>`;
},
image({ href, title, text }) {
const titleAttr = title ? ` title="${title}"` : '';
return `<figure><img src="${href}" alt="${text}"${titleAttr} loading="lazy"><figcaption>${text}</figcaption></figure>`;
}
}
});import { marked } from "marked";
// GitHub-like rendering
marked.use({
gfm: true,
breaks: false,
renderer: {
code({ text, lang }) {
const language = lang || '';
return `<div class="code-block"><div class="code-header">${language}</div><pre><code class="language-${language}">${text}</code></pre></div>\n`;
},
table(token) {
// Add responsive wrapper
const defaultHtml = false; // Get default rendering
return `<div class="table-wrapper">${this.constructor.prototype.table.call(this, token)}</div>\n`;
}
}
});import { marked } from "marked";
// For maximum performance
marked.setOptions({
async: false, // Sync is faster
gfm: true, // GFM is optimized
breaks: false, // Slightly faster
silent: false, // Don't catch errors
walkTokens: null // No token walking overhead
});
// For large documents
const largeMarkdown = '...'; // 10MB+
console.time('parse');
const html = marked.parse(largeMarkdown);
console.timeEnd('parse');
// Consider caching for repeated parses
const cache = new Map();
function cachedParse(markdown) {
if (cache.has(markdown)) {
return cache.get(markdown);
}
const html = marked.parse(markdown);
if (cache.size < 100) { // Limit cache size
cache.set(markdown, html);
}
return html;
}import { marked } from "marked";
// Conflicting options
marked.setOptions({
gfm: false,
breaks: true // breaks requires gfm, will not work
});
// Redundant async
marked.setOptions({
async: true // Not needed if no async operations
});
// Overly permissive
marked.setOptions({
silent: true, // May hide bugs
pedantic: true // Usually unnecessary
});Best Practices:
async when neededsilent sparingly (mainly for user content)gfm: true unless strict CommonMark requiredpedantic unless legacy compatibility neededimport { marked } from "marked";
// Log current configuration
function debugConfig() {
const config = marked.defaults;
console.log('=== Marked Configuration ===');
console.log('GFM:', config.gfm);
console.log('Breaks:', config.breaks);
console.log('Async:', config.async);
console.log('Silent:', config.silent);
console.log('Pedantic:', config.pedantic);
console.log('Custom Renderer:', !!config.renderer);
console.log('Custom Tokenizer:', !!config.tokenizer);
console.log('Custom Hooks:', !!config.hooks);
console.log('Extensions:', !!config.extensions);
console.log('WalkTokens:', !!config.walkTokens);
console.log('============================');
}
debugConfig();
// Test configuration with sample
function testConfig(markdown) {
console.log('Input:', markdown);
console.log('Output:', marked.parse(markdown));
console.log('---');
}
testConfig('Line 1\nLine 2');
testConfig('~~strikethrough~~');
testConfig('https://example.com');