Комбинирование токенайзеров и рендереров

В библиотеке Marked процесс преобразования Markdown в HTML строится на двух ключевых компонентах: токенайзере (lexer) и рендерере (renderer). Токенайзер разбивает исходный Markdown-текст на логические единицы — токены, каждый из которых имеет тип и содержимое. Рендерер отвечает за преобразование этих токенов в итоговый HTML-код.

Токенизация

Токенайзер выполняет синтаксический разбор Markdown и создает массив объектов с описанием элементов. Основные свойства токена:

  • type — тип элемента (например, heading, paragraph, list).
  • raw — исходный фрагмент Markdown.
  • text — текстовое содержимое.
  • depth — глубина заголовка (для heading).
  • items — массив элементов для списков (list).

Пример создания токенов с помощью стандартного лексера:

import { marked } from 'marked';

const markdown = `
# Заголовок 1
- Пункт 1
- Пункт 2
`;

const lexer = new marked.Lexer();
const tokens = lexer.lex(markdown);

console.log(tokens);

Результат — массив объектов, где каждый объект представляет собой отдельный элемент Markdown.

Рендеринг

Рендерер получает токены и преобразует их в HTML. В Marked предусмотрен стандартный рендерер, но его можно переопределить для кастомного HTML.

Пример использования стандартного рендерера:

const renderer = new marked.Renderer();

const html = marked.parser(tokens, { renderer });
console.log(html);

Создание собственного рендерера

Для управления выводом можно создать свой класс, наследующийся от marked.Renderer или объект с методами, соответствующими типам токенов.

Пример кастомного рендерера:

const customRenderer = {
  heading(text, level) {
    return `<h${level} class="custom-heading">${text}</h${level}>`;
  },
  list(body, ordered) {
    const tag = ordered ? 'ol' : 'ul';
    return `<${tag} class="custom-list">${body}</${tag}>`;
  },
  listitem(text) {
    return `<li class="custom-item">${text}</li>`;
  }
};

const htmlCustom = marked.parser(tokens, { renderer: customRenderer });
console.log(htmlCustom);

Комбинирование токенайзеров и рендереров

Marked позволяет не только использовать стандартные компоненты, но и комбинировать их для тонкой настройки обработки Markdown. Варианты комбинаций:

  1. Кастомная токенизация с использованием стандартного рендерера

Можно изменить поведение лексера для специфичных Markdown-правил, но выводить стандартный HTML:

class CustomLexer extends marked.Lexer {
  paragraph(src) {
    // Добавление префикса ко всем параграфам
    const token = super.paragraph(src);
    token.text = `Префикс: ${token.text}`;
    return token;
  }
}

const customLexer = new CustomLexer();
const customTokens = customLexer.lex(markdown);

const htmlFromCustomLexer = marked.parser(customTokens);
  1. Стандартная токенизация с кастомным рендерером

Используется стандартный анализ Markdown, но HTML формируется по своим правилам:

const htmlCustomRendererOnly = marked.parser(tokens, { renderer: customRenderer });
  1. Полная кастомизация

Можно одновременно создать свой токенайзер и рендерер, что дает полный контроль над процессом:

class FullCustomLexer extends marked.Lexer {
  heading(src) {
    // Превращаем все заголовки в заголовки 2 уровня
    const token = super.heading(src);
    token.depth = 2;
    return token;
  }
}

const fullLexer = new FullCustomLexer();
const fullTokens = fullLexer.lex(markdown);

const fullCustomRenderer = {
  heading(text, level) {
    return `<h${level} style="color: red;">${text}</h${level}>`;
  },
  paragraph(text) {
    return `<p style="font-style: italic;">${text}</p>`;
  }
};

const fullHtml = marked.parser(fullTokens, { renderer: fullCustomRenderer });

Использование цепочек обработки

Для более сложных проектов можно создавать цепочки токенайзеров и рендереров, где каждый этап выполняет отдельное преобразование:

  1. Первый токенайзер — базовый разбор Markdown.
  2. Промежуточный обработчик — добавление кастомной логики (например, подстановка переменных или аннотаций).
  3. Рендерер — окончательное формирование HTML.

Такой подход позволяет внедрять промежуточные шаги без изменения исходного Markdown или базового рендерера.

Особенности и рекомендации

  • Безопасность HTML: при кастомном рендеринге следует учитывать возможность внедрения вредоносного HTML. Marked поддерживает опцию sanitize.
  • Производительность: комбинирование токенайзеров увеличивает сложность. Оптимально переопределять только необходимые методы.
  • Совместимость: кастомные токенайзеры могут влиять на работу стандартных функций вроде marked.parse и marked.parser. Важно проверять результат на разных видах Markdown.

Примеры практического применения

  • Генерация документации с кастомным стилем для заголовков и списков.
  • Создание обучающих материалов с подсветкой ключевых слов через токенизацию.
  • Построение динамического контента с вставкой пользовательских данных в Markdown перед рендерингом.

Комбинирование токенайзеров и рендереров позволяет строить гибкие, настраиваемые пайплайны обработки Markdown, полностью контролируя как разбор, так и визуальное представление контента.