remark plugin that turns markdown into HTML to support rehype
npx @tessl/cli install tessl/npm-remark-rehype@11.1.0remark-rehype is a unified plugin that bridges the markdown ecosystem (remark/mdast) and the HTML ecosystem (rehype/hast). It transforms markdown syntax trees into HTML syntax trees, enabling users to process markdown with remark plugins and then continue processing the resulting HTML with rehype plugins.
npm install remark-rehypeimport remarkRehype from "remark-rehype";For CommonJS:
const remarkRehype = require("remark-rehype");Re-exported utilities from mdast-util-to-hast:
import {
defaultFootnoteBackContent,
defaultFootnoteBackLabel,
defaultHandlers
} from "remark-rehype";import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
// Basic markdown to HTML transformation
const file = await unified()
.use(remarkParse) // Parse markdown
.use(remarkRehype) // Transform to HTML
.use(rehypeStringify) // Serialize HTML
.process("# Hello World"); // Process markdown
console.log(String(file)); // "<h1>Hello World</h1>"remark-rehype operates in two distinct modes:
The plugin relies on mdast-util-to-hast for the core transformation logic and provides unified plugin integration with comprehensive options for customization.
The main plugin function that transforms markdown (mdast) syntax trees into HTML (hast) syntax trees.
/**
* Turn markdown into HTML.
*
* @param destination - Processor for bridge mode, or options for mutate mode
* @param options - Configuration options when processor is provided
* @returns Transform function for the unified pipeline
*/
function remarkRehype(
destination?: Processor | Options,
options?: Options
): TransformMutate | TransformBridge;
// Mutate mode - returns hast tree for further processing
type TransformMutate = (tree: MdastRoot, file: VFile) => HastRoot;
// Bridge mode - runs processor and discards result
type TransformBridge = (tree: MdastRoot, file: VFile) => Promise<undefined>;Usage Examples:
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
// Mutate mode (default)
const processor = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify);
// Mutate mode with options
const processorWithOptions = unified()
.use(remarkParse)
.use(remarkRehype, {
allowDangerousHtml: true,
handlers: { /* custom handlers */ }
})
.use(rehypeStringify);
// Bridge mode
const bridgeProcessor = unified()
.use(remarkParse)
.use(remarkRehype, unified().use(rehypeStringify))
.use(remarkStringify); // Continue with markdown processingComprehensive configuration options for customizing the transformation behavior.
interface Options {
/** Allow raw HTML in markdown to be passed through */
allowDangerousHtml?: boolean;
/** Custom handlers for specific node types */
handlers?: Partial<Record<string, Handler>>;
/** Node types to pass through unchanged */
passThrough?: string[];
/** Handler for unknown node types */
unknownHandler?: Handler;
/** Function to generate footnote back-reference labels */
footnoteBackLabel?: (referenceIndex: number, rereferenceIndex: number) => string;
/** Function to generate footnote back-reference content */
footnoteBackContent?: (referenceIndex: number, rereferenceIndex: number) => ElementContent[];
/** Label to use for the footnotes section (affects screen readers) */
footnoteLabel?: string;
/** Properties for footnote label elements */
footnoteLabelProperties?: Properties;
/** Tag name for footnote label elements */
footnoteLabelTagName?: string;
/** Prefix for DOM clobbering prevention */
clobberPrefix?: string;
}
type Handler = (state: State, node: Node) => Element | ElementContent[] | void;Usage Examples:
// Custom footnote labels for non-English languages
const germanOptions = {
footnoteBackLabel(referenceIndex, rereferenceIndex) {
return 'Zurück zu Referenz ' + (referenceIndex + 1) +
(rereferenceIndex > 1 ? '-' + rereferenceIndex : '');
}
};
// Allow raw HTML processing
const htmlOptions = {
allowDangerousHtml: true
};
// Custom node handlers
const customOptions = {
handlers: {
// Override heading transformation
heading(state, node) {
return {
type: 'element',
tagName: 'h' + node.depth,
properties: { className: ['custom-heading'] },
children: state.all(node)
};
}
}
};Pre-built utilities for GitHub-compatible footnote handling.
/**
* Generate default content for footnote back-references
* @param referenceIndex - Index of the definition's first reference (0-indexed)
* @param rereferenceIndex - Index of calls to the same definition (0-indexed)
* @returns Array of hast element content nodes
*/
function defaultFootnoteBackContent(
referenceIndex: number,
rereferenceIndex: number
): ElementContent[];
/**
* Generate default accessibility labels for footnote back-references
* @param referenceIndex - Index of the definition's first reference (0-indexed)
* @param rereferenceIndex - Index of calls to the same definition (0-indexed)
* @returns Label string for screen readers
*/
function defaultFootnoteBackLabel(
referenceIndex: number,
rereferenceIndex: number
): string;Usage Examples:
import { defaultFootnoteBackContent, defaultFootnoteBackLabel } from "remark-rehype";
// Use default functions as templates for customization
function customFootnoteBackContent(refIndex, rerefIndex) {
const defaultContent = defaultFootnoteBackContent(refIndex, rerefIndex);
// Modify the default content as needed
return defaultContent;
}
// Check what the defaults generate
console.log(defaultFootnoteBackLabel(0, 1)); // "Back to reference 1"
console.log(defaultFootnoteBackLabel(1, 2)); // "Back to reference 2-2"Complete set of default handlers for transforming all standard markdown node types to HTML.
/**
* Default handlers for transforming mdast nodes to hast nodes
* Includes handlers for all standard CommonMark and GFM node types
*/
const defaultHandlers: Partial<Record<string, Handler>>;Available Handlers:
blockquote - Block quotes (> text)break - Hard line breaks ( \n)code - Code blocks (```)delete - Strikethrough text (~~text~~)emphasis - Italic text (*text*)footnoteReference - Footnote references ([^ref])heading - Headings (# text)html - Raw HTMLimage - Images ()imageReference - Image references (![alt][ref])inlineCode - Inline code (`code`)link - Links ([text](url))linkReference - Link references ([text][ref])list - Lists (- item)listItem - List itemsparagraph - Paragraphsroot - Document rootstrong - Bold text (**text**)table - TablestableCell - Table cellstableRow - Table rowstext - Plain textthematicBreak - Horizontal rules (---)Usage Examples:
import { defaultHandlers } from "remark-rehype";
// Extend default handlers with custom ones
const customHandlers = {
...defaultHandlers,
heading(state, node) {
const result = defaultHandlers.heading(state, node);
// Add custom attributes to all headings
if (result && result.properties) {
result.properties.className = ['custom-heading'];
}
return result;
},
customNode(state, node) {
// Handle custom node types
return {
type: 'element',
tagName: 'div',
properties: { className: ['custom-node'] },
children: state.all(node)
};
}
};
const processor = unified()
.use(remarkParse)
.use(remarkRehype, { handlers: customHandlers })
.use(rehypeStringify);import type { Root as MdastRoot, Node as MdastNode } from "mdast";
import type { Root as HastRoot, Element, ElementContent, Properties } from "hast";
import type { VFile } from "vfile";
import type { Processor } from "unified";
import type { State } from "mdast-util-to-hast";
interface Options {
allowDangerousHtml?: boolean;
handlers?: Partial<Record<string, Handler>>;
passThrough?: string[];
unknownHandler?: Handler;
footnoteBackLabel?: (referenceIndex: number, rereferenceIndex: number) => string;
footnoteBackContent?: (referenceIndex: number, rereferenceIndex: number) => ElementContent[];
footnoteLabel?: string;
footnoteLabelProperties?: Properties;
footnoteLabelTagName?: string;
clobberPrefix?: string;
}
type Handler = (state: State, node: MdastNode) => Element | ElementContent[] | void;
type TransformMutate = (tree: MdastRoot, file: VFile) => HastRoot;
type TransformBridge = (tree: MdastRoot, file: VFile) => Promise<undefined>;The plugin handles several edge cases and error conditions:
unknownHandler or falls back to creating <div> elementsallowDangerousHtml: true option to process embedded HTMLclobberPrefix to prevent ID-based security issues with footnotes// Enable raw HTML processing (use with caution)
const processor = unified()
.use(remarkParse)
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw) // Parse raw HTML into proper hast nodes
.use(rehypeStringify);// Customize footnote labels for different languages
const frenchProcessor = unified()
.use(remarkParse)
.use(remarkRehype, {
footnoteBackLabel(refIndex) {
return `Retour à la référence ${refIndex + 1}`;
}
})
.use(rehypeStringify);// Handle custom markdown extensions
const processor = unified()
.use(remarkParse)
.use(remarkRehype, {
handlers: {
// Handle custom admonition blocks
admonition(state, node) {
return {
type: 'element',
tagName: 'div',
properties: {
className: ['admonition', node.type]
},
children: state.all(node)
};
}
}
})
.use(rehypeStringify);