Расширения уровня блоков

Библиотека Marked предоставляет мощный инструмент для парсинга Markdown, позволяя гибко обрабатывать текст и получать HTML. Одним из ключевых механизмов расширения функционала являются расширения уровня блоков (block-level extensions). Они позволяют определять собственные структуры, которые будут распознаваться как отдельные блоки Markdown и корректно преобразовываться в HTML.


Основные принципы работы

Расширения уровня блоков работают на этапе лексического анализа, когда Markdown-текст разбивается на токены. Каждый блок — это отдельный токен, который содержит информацию о типе, содержимом и возможных атрибутах. Расширение может:

  • распознавать новые типы блоков;
  • изменять существующие блоки;
  • добавлять кастомную обработку содержимого перед генерацией HTML.

В Marked блоковое расширение определяется объектом с обязательным полем name и методом level. Дополнительно можно указывать start, tokenizer и renderer.


Структура блокового расширения

Обязательные и ключевые свойства:

const customBlock = {
  name: 'customBlock',        // уникальное имя расширения
  level: 'block',             // уровень, должен быть 'block'
  start(src) {                // необязательный метод для оптимизации поиска
    return src.match(/^:::/)?.index;
  },
  tokenizer(src, tokens) {    // функция, которая распознаёт токен
    const rule = /^:::\s*(\w+)\n([\s\S]+?)\n:::/;
    const match = rule.exec(src);
    if (match) {
      return {
        type: 'customBlock',
        raw: match[0],         // полный текст блока
        blockType: match[1],   // тип блока, например alert, note
        text: match[2],        // содержимое блока
        tokens: this.lexer.inlineTokens(match[2]) // токены для inline-парсинга
      };
    }
  },
  renderer(token) {            // функция генерации HTML из токена
    return `<div class="custom-${token.blockType}">${this.parser.parseInline(token.tokens)}</div>`;
  }
};

Методы и их особенности

  1. start(src)

Метод возвращает индекс первого символа, с которого может начинаться интересующий блок. Это позволяет оптимизировать процесс сканирования текста. Возврат undefined или -1 означает, что блок не найден.

  1. tokenizer(src, tokens)

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

Особенности:

  • raw — обязательно должен содержать полный исходный текст блока.
  • tokens — используется для inline-парсинга содержимого блока (ссылки, выделения, эмфазы и т. д.).
  1. renderer(token)

Отвечает за преобразование токена в HTML. Внутри можно использовать this.parser.parseInline(token.tokens) для обработки inline-содержимого.


Регистрация расширения

После создания блокового расширения его нужно подключить к Marked:

import { marked } from 'marked';

marked.use({ extensions: [customBlock] });

const markdown = `
:::alert
Это важное сообщение.
:::
`;

console.log(marked.parse(markdown));

В результате получим HTML:

<div class="custom-alert">Это важное сообщение.</div>

Вложенные блоки

Расширения уровня блоков могут содержать другие блоки Markdown. Для этого токенизация содержимого выполняется через this.lexer.blockTokens().

Пример:

const nestedBlock = {
  name: 'nestedBlock',
  level: 'block',
  tokenizer(src) {
    const match = /^:::\s*note\n([\s\S]+?)\n:::/m.exec(src);
    if (match) {
      return {
        type: 'nestedBlock',
        raw: match[0],
        text: match[1],
        tokens: this.lexer.blockTokens(match[1])
      };
    }
  },
  renderer(token) {
    return `<section class="note">${this.parser.parse(token.tokens)}</section>`;
  }
};

Использование this.lexer.blockTokens позволяет корректно обрабатывать заголовки, списки и другие блоки внутри кастомного блока.


Советы по проектированию блоковых расширений

  • Всегда возвращать raw для корректного позиционирования при разборе Markdown.
  • При вложенных блоках использовать blockTokens для вложенной токенизации.
  • Для inline-обработки текста внутри блока применять inlineTokens и parseInline.
  • Начинать поиск блока с оптимизированного start(src) для ускорения парсинга больших документов.
  • Присваивать уникальные имена (name) всем расширениям, чтобы избежать конфликтов.

Примеры практических расширений

  • Alert-блоки: :::alert … ::: с разными типами (success, warning, error).
  • Цитаты с автором: :::quote Автор … ::: для создания структурированных цитат.
  • Табличные блоки с метаданными: :::table data-json=… ::: для генерации интерактивных таблиц.

Каждое из этих расширений строится по одной и той же логике: распознавание блока → токенизация содержимого → генерация HTML с возможной стилизацией через CSS.


Взаимодействие с другими расширениями

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

  • Порядок подключения расширений влияет на обработку текста.
  • Если два расширения совпадают по началу строки, приоритет отдаётся первому в массиве.
  • Можно комбинировать блоковые и inline-расширения для полной кастомизации Markdown.

Заключение по структуре

Расширения уровня блоков предоставляют высокий уровень контроля над Markdown. Они позволяют:

  • внедрять новые типы контента;
  • создавать сложные вложенные структуры;
  • гибко генерировать HTML с кастомными классами и атрибутами.

Правильное использование токенизации и методов tokenizer и renderer обеспечивает совместимость с существующими Markdown-блоками и поддерживает обработку больших документов без потери производительности.