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

Библиотека Marked предоставляет гибкую систему расширений, которая позволяет модифицировать процесс разбора Markdown и генерации HTML. Расширения делятся на несколько типов: лексические (lexer extensions) и рендерные (renderer extensions). Каждый тип имеет собственный способ регистрации и область применения.


Лексические расширения

Лексические расширения позволяют вмешиваться в процесс токенизации текста Markdown. Они работают на уровне исходного текста и могут определять новые типы токенов или модифицировать существующие.

Структура лексического расширения:

const myLexerExtension = {
  name: 'customLexer',
  level: 'block', // или 'inline'
  start(src) {
    // Определяет позицию начала потенциального совпадения
    return src.match(/^\[.*\]/)?.index;
  },
  tokenizer(src, tokens) {
    const match = /^\[([^\]]+)\]\(([^)]+)\)/.exec(src);
    if (match) {
      return {
        type: 'customLink',
        raw: match[0],
        text: match[1],
        href: match[2]
      };
    }
  },
  childTokens: [],
};
  • name — уникальный идентификатор расширения.
  • level — уровень обработки: 'block' для блочных элементов, 'inline' для встроенных.
  • start(src) — функция, возвращающая индекс начала потенциального совпадения. Оптимизирует производительность, ограничивая количество проверок.
  • tokenizer(src, tokens) — основная функция, создающая токен. Возвращает объект с полями type, raw и другими пользовательскими.

Регистрация лексического расширения происходит через:

import { marked } from 'marked';

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

После этого все вызовы marked.parse() будут учитывать добавленные токены.


Рендерные расширения

Рендерные расширения позволяют изменять HTML, генерируемый из токенов. Они работают после этапа токенизации и управляют выводом конкретных элементов.

Пример рендерного расширения:

const myRendererExtension = {
  name: 'customRenderer',
  level: 'inline',
  renderer(token) {
    if (token.type === 'customLink') {
      return `<a href="${token.href}" class="custom">${token.text}</a>`;
    }
  }
};
  • renderer(token) получает токен, возвращаемый лексером, и должен возвращать строку HTML.
  • Совместимо с лексическими расширениями: можно сначала добавить новый токен, а затем определить его HTML-представление.

Регистрация идентична лексической:

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

Совместное использование расширений

Можно комбинировать несколько расширений, используя единый вызов marked.use():

marked.use({
  extensions: [myLexerExtension, myRendererExtension]
});

При этом порядок элементов в массиве имеет значение:

  1. Лексические расширения обрабатываются в том порядке, в котором они указаны.
  2. Рендерные расширения применяются после токенизации.

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


Конфликты и приоритеты

Если два расширения пытаются обработать один и тот же кусок текста, Marked использует следующее правило:

  • Лексические расширения обрабатываются последовательно, и первый успешный матч прекращает дальнейшую проверку.
  • Рендерные расширения вызываются только для токенов, созданных на предыдущем этапе.

Чтобы избежать конфликтов, рекомендуется:

  • Уникально называть токены и расширения.
  • Четко ограничивать область применения через level и start().
  • Тщательно тестировать сочетания блоков и inline-элементов.

Пример расширенной регистрации

const emojiLexer = {
  name: 'emoji',
  level: 'inline',
  tokenizer(src) {
    const match = /:([a-z_]+):/.exec(src);
    if (match) {
      return {
        type: 'emoji',
        raw: match[0],
        text: match[1]
      };
    }
  }
};

const emojiRenderer = {
  name: 'emojiRenderer',
  level: 'inline',
  renderer(token) {
    if (token.type === 'emoji') {
      return `<span class="emoji">${token.text}</span>`;
    }
  }
};

marked.use({ extensions: [emojiLexer, emojiRenderer] });

В этом примере текст Hello :smile: будет преобразован в HTML <span class="emoji">smile</span>.


Ключевые моменты при регистрации расширений

  • Каждое расширение должно иметь уникальное имя.
  • Лексические и рендерные расширения могут быть зарегистрированы вместе.
  • level позволяет разграничить область действия (block vs inline).
  • Функции start и tokenizer критичны для производительности и точности.
  • Рендерные функции должны быть устойчивыми к любым токенам, иначе может возникнуть ошибка генерации HTML.

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