В библиотеке Marked процесс обработки Markdown текста делится на два ключевых этапа: токенизация и рендеринг. Токенайзер преобразует исходный текст в структурированный набор токенов, каждый из которых описывает отдельный элемент Markdown (заголовок, список, ссылка, код и т.д.). Эти токены затем передаются рендереру, который преобразует их в HTML или другой целевой формат.
Применение токенайзеров позволяет более гибко контролировать парсинг текста, добавлять кастомные правила или модифицировать стандартное поведение Markdown.
В Marked можно создавать собственные токенайзеры, наследуя стандартное поведение или полностью переопределяя его. Основная структура кастомного токенайзера:
const { Lexer, Parser } = require('marked');
class CustomTokenizer {
constructor() {}
// Пример обработки заголовков
heading(src) {
const match = /^#{1,6}\s*(.+)/.exec(src);
if (match) {
return {
type: 'heading',
raw: match[0],
depth: match[0].trim().split(' ')[0].length,
text: match[1]
};
}
return false;
}
// Пример обработки жирного текста
strong(src) {
const match = /^\*\*(.+?)\*\*/.exec(src);
if (match) {
return {
type: 'strong',
raw: match[0],
text: match[1]
};
}
return false;
}
}
Ключевые моменты:
false,
если правило не сработало.raw — исходная строка, которая была распознана.text, depth,
href, title и т.д.) описывают содержание
токена и его свойства.Рендерер принимает токены и преобразует их в HTML. Можно использовать
стандартный Renderer или создавать кастомный класс:
const { Renderer } = require('marked');
class CustomRenderer extends Renderer {
heading(token) {
return `<h${token.depth} class="custom-heading">${token.text}</h${token.depth}>`;
}
strong(token) {
return `<strong class="highlight">${token.text}</strong>`;
}
}
Особенности интеграции:
heading, paragraph, list,
link и т.д.).После создания кастомных токенайзера и рендерера их интеграция
выполняется через экземпляр marked:
const { marked } = require('marked');
const lexer = new Lexer({ tokenizer: new CustomTokenizer() });
const renderer = new CustomRenderer();
const markdown = `
# Заголовок уровня 1
Это **жирный** текст.
`;
const tokens = lexer.lex(markdown);
const html = marked.parser(tokens, { renderer });
console.log(html);
Пошаговая логика:
Этот подход позволяет точно контролировать как разбор текста, так и итоговую разметку.
Можно добавлять нестандартные правила для специфичных Markdown-синтаксисов:
class ExtendedTokenizer extends CustomTokenizer {
highlight(src) {
const match = /==(.+?)==/.exec(src);
if (match) {
return {
type: 'highlight',
raw: match[0],
text: match[1]
};
}
return false;
}
}
class ExtendedRenderer extends CustomRenderer {
highlight(token) {
return `<mark>${token.text}</mark>`;
}
}
Особенности:
Можно комбинировать стандартные правила Marked и свои кастомные токенайзеры:
const { Lexer, marked } = require('marked');
const lexer = new Lexer({ tokenizer: new ExtendedTokenizer() });
const renderer = new ExtendedRenderer();
const html = marked.parse(markdown, { lexer, renderer });
Примечания:
raw и
text, а при необходимости — дополнительные поля
(depth, href).Эта архитектура позволяет создавать расширяемые и гибкие системы рендеринга Markdown, полностью управляемые на уровне токенов и рендеринга, что особенно полезно для кастомных редакторов и сложных приложений.