Создание пользовательского рендерера

Библиотека Marked в JavaScript предоставляет мощный механизм для парсинга Markdown и преобразования его в HTML. Один из ключевых аспектов расширения возможностей Marked — это создание пользовательских рендереров, позволяющих полностью контролировать процесс генерации HTML для различных типов элементов Markdown.

Основы рендерера

В Marked рендерер — это объект с методами, каждый из которых отвечает за конкретный элемент Markdown. Стандартный рендерер содержит методы для следующих блоков и встроенных элементов:

  • code(code, language, isEscaped) — блок кода
  • blockquote(quote) — цитата
  • html(html) — HTML-блок
  • heading(text, level, raw, slugger) — заголовок
  • hr() — горизонтальная линия
  • list(body, ordered, start) — список
  • listitem(text) — элемент списка
  • checkbox(checked) — чекбокс внутри списка
  • paragraph(text) — абзац
  • table(header, body) — таблица
  • tablerow(content) — строка таблицы
  • tablecell(content, flags) — ячейка таблицы

Каждый метод принимает аргументы, содержащие текстовую или структурированную информацию, и возвращает строку HTML. Переопределяя эти методы, можно менять HTML-структуру, добавлять классы, атрибуты или стили.

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

import { marked } from 'marked';

const customRenderer = {
  heading(text, level) {
    return `<h${level} class="custom-heading">${text}</h${level}>`;
  },
  paragraph(text) {
    return `<p class="custom-paragraph">${text}</p>`;
  },
  code(code, language) {
    const langClass = language ? ` class="language-${language}"` : '';
    return `<pre><code${langClass}>${code}</code></pre>`;
  }
};

marked.use({ renderer: customRenderer });

const markdown = `
# Заголовок 1
Параграф с **жирным текстом**.

\`\`\`javascript
console.log('Пример кода');
\`\`\`
`;

console.log(marked(markdown));

В этом примере заголовки, абзацы и блоки кода получают свои кастомные CSS-классы, что упрощает стилизацию и интеграцию с дизайном.

Использование функций для условного рендеринга

Рендерер может быть динамическим, позволяя изменять поведение в зависимости от содержимого. Например, можно выделять определённые ключевые слова или добавлять иконки:

const dynamicRenderer = {
  heading(text, level) {
    if (text.includes('Важно')) {
      return `<h${level} style="color:red">${text}</h${level}>`;
    }
    return `<h${level}>${text}</h${level}>`;
  }
};

Такой подход позволяет интегрировать логические правила прямо на уровне HTML-генерации, без постобработки.

Расширение рендерера через наследование

Для сложных случаев можно создать объект-наследник стандартного рендерера. Это полезно, когда нужно сохранить базовое поведение для большинства элементов, но изменить несколько ключевых.

import { Renderer } from 'marked';

class ExtendedRenderer extends Renderer {
  listitem(text) {
    return `<li class="custom-list-item">${text}</li>`;
  }
}

marked.use({ renderer: new ExtendedRenderer() });

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

Интеграция с внешними библиотеками

Пользовательский рендерер легко интегрировать с библиотеками подсветки синтаксиса, генераторами таблиц или инструментами для работы с метаданными. Например, интеграция с highlight.js:

import hljs from 'highlight.js';

const rendererWithHighlight = {
  code(code, language) {
    const validLang = hljs.getLanguage(language) ? language : 'plaintext';
    const highlighted = hljs.highlight(code, { language: validLang }).value;
    return `<pre><code class="hljs ${validLang}">${highlighted}</code></pre>`;
  }
};

Здесь метод code использует highlight.js для подсветки синтаксиса, при этом сохраняется возможность добавлять кастомные стили через CSS.

Методы для обработки встроенных элементов

Кроме блоков, рендерер позволяет переопределять встроенные элементы Markdown:

  • strong(text) — жирный текст
  • em(text) — курсив
  • codespan(code) — встроенный код
  • link(href, title, text) — ссылка
  • image(href, title, text) — изображение

Например, можно обернуть ссылки в элемент с иконкой внешней ссылки:

const linkRenderer = {
  link(href, title, text) {
    const icon = href.startsWith('http') ? ' ?' : '';
    return `<a href="${href}" title="${title || ''}">${text}${icon}</a>`;
  }
};

Рендерер для таблиц

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

const tableRenderer = {
  table(header, body) {
    return `<table class="custom-table">
              <thead>${header}</thead>
              <tbody>${body}</tbody>
            </table>`;
  },
  tablerow(content) {
    return `<tr class="custom-row">${content}</tr>`;
  },
  tablecell(content, flags) {
    const tag = flags.header ? 'th' : 'td';
    return `<${tag} class="custom-cell">${content}</${tag}>`;
  }
};

Такой подход даёт полный контроль над внешним видом таблиц без дополнительных CSS-селекторов по структуре HTML.

Использование нескольких рендереров

Marked позволяет комбинировать несколько рендереров, используя опции use. Это удобно для модульной архитектуры, когда один рендерер отвечает за стилизацию текста, а другой — за визуализацию блоков кода или таблиц.

marked.use({
  renderer: customRenderer,
  mangle: false,
  headerIds: true
});

Опции mangle и headerIds позволяют управлять генерацией идентификаторов заголовков и экранированием специальных символов, что важно для интеграции с якорями и SEO.


Создание пользовательского рендерера в Marked предоставляет полный контроль над HTML-выходом, позволяет добавлять стили, логические правила и интегрироваться с внешними библиотеками. Это ключевой инструмент для построения гибких Markdown-парсеров в современных веб-приложениях.