Расширения инлайн-уровня

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

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


Структура расширения инлайн-уровня

Расширение инлайн-уровня в Marked представляет собой объект с несколькими обязательными и опциональными свойствами:

{
  name: 'название_расширения',
  level: 'inline',
  start?: Function,
  tokenizer: Function,
  renderer?: Function
}
  • name – уникальное имя расширения, необходимое для регистрации.
  • level – указывает уровень расширения, для инлайн-уровня значение всегда 'inline'.
  • start (опционально) – функция, которая возвращает индекс первого возможного вхождения паттерна в тексте. Используется для оптимизации обработки.
  • tokenizer – обязательная функция, которая ищет соответствие паттерну в строке и возвращает токен, описывающий найденный элемент.
  • renderer (опционально) – функция, которая определяет HTML-представление токена. Если отсутствует, используется стандартный вывод Markdown.

Создание простого инлайн-расширения

Для демонстрации создадим расширение, которое обрабатывает текст вида ++выделение++ и преобразует его в <mark>:

const highlightExtension = {
  name: 'highlight',
  level: 'inline',
  start(src) {
    return src.indexOf('++');
  },
  tokenizer(src) {
    const rule = /^\+\+([^\+]+)\+\+/;
    const match = rule.exec(src);
    if (match) {
      return {
        type: 'highlight',
        raw: match[0],
        text: match[1]
      };
    }
  },
  renderer(token) {
    return `<mark>${token.text}</mark>`;
  }
};
  • start помогает библиотеке быстро найти потенциальное место для токена, избегая лишних проверок всего текста.
  • tokenizer проверяет соответствие регулярному выражению и возвращает объект с типом токена, исходной строкой и содержимым.
  • renderer преобразует токен в HTML-разметку <mark>.

Для активации расширения необходимо зарегистрировать его при парсинге:

import { marked } from 'marked';

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

const result = marked('Это ++важный++ текст.');
console.log(result);
// Вывод: Это <mark>важный</mark> текст.

Работа с более сложными паттернами

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

const emojiExtension = {
  name: 'emoji',
  level: 'inline',
  tokenizer(src) {
    const rule = /^:([a-z_]+):/;
    const match = rule.exec(src);
    if (match) {
      return {
        type: 'emoji',
        raw: match[0],
        name: match[1]
      };
    }
  },
  renderer(token) {
    const emojiMap = {
      smile: '?',
      heart: '❤️',
      fire: '?'
    };
    return emojiMap[token.name] || token.raw;
  }
};
  • Регулярное выражение определяет синтаксис эмодзи :имя:.
  • renderer использует словарь для подстановки символа эмодзи. Если эмодзи не найдено, возвращается исходная строка.

Приоритет расширений и порядок обработки

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

Для корректной работы следует:

  1. Реализовать функцию start для ускорения поиска.
  2. Проверять, что регулярное выражение не конфликтует с другими стандартными Markdown-элементами.
  3. Возвращать токены с корректным raw для точного подсчёта оставшегося текста.

Оптимизация и производительность

Инлайн-уровень обрабатывает текст посимвольно, поэтому даже небольшие оптимизации могут существенно ускорить парсинг. Основные рекомендации:

  • Использовать start для ограничения области поиска.
  • Минимизировать сложность регулярных выражений.
  • Возвращать undefined для токенов, если соответствия нет, чтобы библиотека переходила к следующему расширению.

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

Marked позволяет регистрировать несколько инлайн-расширений одновременно:

marked.use({ extensions: [highlightExtension, emojiExtension] });

const result = marked('++Внимание++ :smile:');
console.log(result);
// Вывод: <mark>Внимание</mark> ?

Каждое расширение обрабатывает текст независимо, при этом библиотека соблюдает порядок регистрации, что обеспечивает предсказуемое поведение.


Доступ к токенам и модификация парсинга

Помимо HTML, токены расширений могут использоваться для:

  • генерации собственного AST,
  • модификации содержимого перед рендерингом,
  • динамического добавления атрибутов, классов или событий.

Пример добавления CSS-класса через расширение:

const boldExtension = {
  name: 'customBold',
  level: 'inline',
  tokenizer(src) {
    const match = /^\*\*([^\*]+)\*\*/.exec(src);
    if (match) {
      return { type: 'customBold', raw: match[0], text: match[1] };
    }
  },
  renderer(token) {
    return `<strong class="highlighted">${token.text}</strong>`;
  }
};

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


Расширения инлайн-уровня в Marked дают полный контроль над обработкой текстовых элементов Markdown. Правильное использование tokenizer, renderer и start обеспечивает высокую производительность, точное распознавание сложных паттернов и гибкость кастомизации.