High-performance Markdown parser with full CommonMark support, syntax extensions, and configurable rule systems.
—
Remarkable uses a sophisticated rule-based system for parsing markdown. The system consists of three main parsers (core, block, inline) each with their own set of configurable rules managed by Ruler instances.
The Ruler class manages rule chains and execution order for parsing operations.
/**
* Rule manager for organizing and controlling parsing rules
*/
class Ruler {
/** Add rule to the end of chain */
push(name: string, fn: RuleFunction, options?: RuleOptions): void;
/** Add rule before specified rule */
before(beforeName: string, ruleName: string, fn: RuleFunction, options?: RuleOptions): void;
/** Add rule after specified rule */
after(afterName: string, ruleName: string, fn: RuleFunction, options?: RuleOptions): void;
/** Replace rule function */
at(name: string, fn: RuleFunction, options?: RuleOptions): void;
/** Enable rules by name */
enable(names: string | string[], ignoreInvalid?: boolean): string[];
/** Disable rules by name */
disable(names: string | string[], ignoreInvalid?: boolean): string[];
/** Get active rules for chain */
getRules(chainName: string): RuleFunction[];
}
interface RuleOptions {
/** Alternative rule names this rule can replace */
alt?: string[];
}
type RuleFunction = (state: StateCore | StateBlock | StateInline) => boolean | void;Usage Examples:
import { Remarkable } from "remarkable";
const md = new Remarkable();
// Add custom rule to block parser
md.block.ruler.push('custom_block', function(state, start, end, silent) {
// Custom block parsing logic
return false;
});
// Insert rule before 'paragraph'
md.block.ruler.before('paragraph', 'custom_before', customRule);
// Enable/disable rules
md.core.ruler.enable(['abbr', 'footnote_tail']);
md.inline.ruler.disable(['autolink']);Core processing rules that handle document-level operations.
interface CoreRules {
/** Process block-level elements */
block: RuleFunction;
/** Process inline elements within blocks */
inline: RuleFunction;
/** Resolve link references */
references: RuleFunction;
/** Apply typographic replacements */
replacements: RuleFunction;
/** Apply smart quotes */
smartquotes: RuleFunction;
/** Process abbreviations */
abbr: RuleFunction;
/** Finalize abbreviation processing */
abbr2: RuleFunction;
/** Process footnote tails */
footnote_tail: RuleFunction;
}Default Core Rule Chain:
block - Parse block elementsinline - Parse inline elementsreferences - Resolve referencesreplacements - Typography replacementssmartquotes - Smart quotesabbr2 - Finalize abbreviationsfootnote_tail - Process footnotesRules for parsing block-level markdown elements.
interface BlockRules {
/** Code blocks (indented) */
code: RuleFunction;
/** Fenced code blocks */
fences: RuleFunction;
/** Blockquotes */
blockquote: RuleFunction;
/** Horizontal rules */
hr: RuleFunction;
/** Lists (ordered and unordered) */
list: RuleFunction;
/** Footnote definitions */
footnote: RuleFunction;
/** ATX headings (# ## ###) */
heading: RuleFunction;
/** Setext headings (underlined) */
lheading: RuleFunction;
/** HTML blocks */
htmlblock: RuleFunction;
/** Tables */
table: RuleFunction;
/** Definition lists */
deflist: RuleFunction;
/** Paragraphs */
paragraph: RuleFunction;
}Default Block Rule Chain:
code - Indented code blocksfences - Fenced code blocksblockquote - Blockquoteshr - Horizontal ruleslist - Listsfootnote - Footnote definitionsheading - ATX headingslheading - Setext headingshtmlblock - HTML blockstable - Tablesdeflist - Definition listsparagraph - ParagraphsRules for parsing inline markdown elements.
interface InlineRules {
/** Plain text */
text: RuleFunction;
/** Line breaks */
newline: RuleFunction;
/** Escape sequences */
escape: RuleFunction;
/** Code spans */
backticks: RuleFunction;
/** Strikethrough */
del: RuleFunction;
/** Insert/underline */
ins: RuleFunction;
/** Mark/highlight */
mark: RuleFunction;
/** Bold and italic */
emphasis: RuleFunction;
/** Subscript */
sub: RuleFunction;
/** Superscript */
sup: RuleFunction;
/** Links and images */
links: RuleFunction;
/** Inline footnotes */
footnote_inline: RuleFunction;
/** Footnote references */
footnote_ref: RuleFunction;
/** Autolinks <url> */
autolink: RuleFunction;
/** HTML tags */
htmltag: RuleFunction;
/** HTML entities */
entity: RuleFunction;
}Default Inline Rule Chain:
text - Plain textnewline - Line breaksescape - Escape sequencesbackticks - Code spansdel - Strikethroughins - Insert/underlinemark - Mark/highlightemphasis - Bold/italicsub - Subscriptsup - Superscriptlinks - Links and imagesfootnote_inline - Inline footnotesfootnote_ref - Footnote referencesautolink - Autolinkshtmltag - HTML tagsentity - HTML entitiesCreating custom parsing rules for extending Remarkable functionality.
/**
* State objects passed to rule functions
*/
interface StateCore {
src: string;
env: object;
options: RemarkableOptions;
tokens: Token[];
inlineMode: boolean;
inline: ParserInline;
block: ParserBlock;
renderer: Renderer;
}
interface StateBlock {
src: string;
md: Remarkable;
env: object;
tokens: Token[];
bMarks: number[];
eMarks: number[];
tShift: number[];
sCount: number[];
blkIndent: number;
line: number;
lineMax: number;
tight: boolean;
parentType: string;
ddIndent: number;
getLines(begin: number, end: number, indent: number, keepLastLF: boolean): string;
isEmpty(line: number): boolean;
skipEmptyLines(from: number): number;
skipSpaces(pos: number): number;
skipChars(pos: number, code: number): number;
skipCharsBack(pos: number, code: number, min: number): number;
push(token: Token): void;
}
interface StateInline {
src: string;
env: object;
md: Remarkable;
tokens: Token[];
pos: number;
posMax: number;
level: number;
pending: string;
pendingLevel: number;
cache: object;
delimiters: object[];
push(token: Token): void;
pushPending(): Token;
scanDelims(start: number, canSplitWord: boolean): object;
}Usage Examples:
// Custom block rule for special containers
function customContainer(state, start, end, silent) {
const marker = ':::';
let pos = state.bMarks[start] + state.tShift[start];
let max = state.eMarks[start];
// Check for marker
if (pos + marker.length > max) return false;
if (state.src.slice(pos, pos + marker.length) !== marker) return false;
if (silent) return true;
// Find closing marker
let nextLine = start + 1;
let found = false;
while (nextLine < end) {
pos = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (state.src.slice(pos, pos + marker.length) === marker) {
found = true;
break;
}
nextLine++;
}
if (!found) return false;
// Create tokens
const token_o = state.push({ type: 'container_open', level: state.level++ });
const token_c = state.push({ type: 'container_close', level: --state.level });
token_o.map = [start, nextLine];
token_c.map = [nextLine, nextLine + 1];
state.line = nextLine + 1;
return true;
}
// Add the rule
md.block.ruler.before('paragraph', 'container', customContainer);Configuring which rules are active through presets and manual configuration.
interface PresetConfig {
options?: RemarkableOptions;
components?: {
core?: { rules: string[] };
block?: { rules: string[] };
inline?: { rules: string[] };
};
}Usage Examples:
// Enable specific rules only
md.configure({
components: {
core: { rules: ['block', 'inline'] },
block: { rules: ['paragraph', 'heading', 'list'] },
inline: { rules: ['text', 'emphasis', 'links'] }
}
});
// Enable/disable individual rules
md.block.ruler.enable(['table', 'footnote']);
md.inline.ruler.disable(['autolink', 'htmltag']);Install with Tessl CLI
npx tessl i tessl/npm-remarkable