Структура токенов

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

Токенизация в Marked выполняется с помощью функции lexer, которая принимает строку Markdown и возвращает массив токенов:

import { marked } from 'marked';

const markdownText = `
# Заголовок уровня 1
Это абзац текста с **жирным** выделением.
- Пункт списка 1
- Пункт списка 2
`;

const tokens = marked.lexer(markdownText);
console.log(tokens);

Результат будет массивом объектов с ключевыми свойствами, описывающими тип токена, его содержимое и дополнительные параметры.

Типы токенов

Каждый токен имеет свой type, определяющий его роль. Основные типы:

  • heading — заголовки (#, ##, ### и т.д.)
  • paragraph — абзацы текста
  • list — списки (упорядоченные и неупорядоченные)
  • list_item — элемент списка
  • code — блоки кода
  • blockquote — цитаты
  • hr — горизонтальные линии
  • html — встроенный HTML
  • table — таблицы
  • link / image — ссылки и изображения
  • strong / em — жирный и курсивный текст внутри параграфов

Каждый токен имеет дополнительные свойства. Например, для заголовка:

{
  "type": "heading",
  "depth": 2,
  "text": "Подзаголовок второго уровня"
}

depth показывает уровень заголовка (от 1 до 6), а text — содержимое заголовка без Markdown-разметки.

Вложенные токены и рекурсивная структура

Некоторые токены содержат вложенные токены, отражающие структуру документа. Например, элемент списка (list_item) может содержать абзацы, код, ссылки и другие элементы. Это позволяет библиотеке строить дерево документа, которое потом удобно рендерить в HTML:

{
  "type": "list_item",
  "tokens": [
    { "type": "paragraph", "text": "Первый пункт списка" }
  ]
}

Таким образом, каждый элемент списка может включать несколько токенов, а каждый параграф внутри списка — свои собственные вложенные элементы.

Сравнение inline и block токенов

Marked разделяет block-токены и inline-токены:

  • Block-токены описывают структуру документа на уровне блоков: заголовки, параграфы, списки, цитаты.
  • Inline-токены описывают форматирование внутри текста: ссылки, изображения, жирный и курсивный текст, код в строке.

Lexer сначала формирует block-токены, затем при необходимости анализирует их содержимое с помощью inline-лексера, чтобы получить вложенные inline-токены. Например, для параграфа с текстом Это **важно** будет создан блок:

{
  "type": "paragraph",
  "tokens": [
    { "type": "text", "text": "Это " },
    { "type": "strong", "text": "важно" }
  ]
}

Специальные свойства токенов

Некоторые токены имеют дополнительные свойства, которые управляют их поведением при рендеринге:

  • list:

    • orderedtrue, если список нумерованный.
    • start — стартовое число для нумерованного списка.
    • loosetrue, если элементы списка разделены пустыми строками.
  • code:

    • lang — язык программирования для подсветки.
    • text — содержимое кода.
  • link:

    • href — URL ссылки.
    • title — текст всплывающей подсказки.
    • text — видимый текст ссылки.
  • image:

    • href — URL изображения.
    • title — текст подсказки.
    • text — альтернативный текст.

Настройка токенизации

Marked позволяет изменять поведение токенизации через options:

  • gfm — включение расширений GitHub Flavored Markdown.
  • breaks — переводы строк в параграфах становятся <br>.
  • smartLists — улучшенная обработка списков.
  • smartypants — замена типографских символов ("/, --).

Пример:

const tokens = marked.lexer(markdownText, {
  gfm: true,
  breaks: true
});

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

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

tokens.forEach(token => {
  if (token.type === 'heading' && token.depth === 1) {
    token.text = token.text.toUpperCase();
  }
});

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

Практические особенности

  • Производительность: токенизация выполняется быстрее, чем сразу рендеринг HTML, особенно для больших документов.
  • Модифицируемость: можно добавлять новые типы токенов и расширять inline-парсер.
  • Точность: правильная токенизация позволяет корректно обрабатывать вложенные списки, цитаты и таблицы, что критично для сложных Markdown-документов.

Токены в Marked создают мощный каркас для последующей генерации HTML, анализа структуры документа и внедрения кастомной логики рендеринга, делая библиотеку гибкой и эффективной для любых задач работы с Markdown.