Remark и Rehype используют систему плагинов для обработки Markdown и HTML. В основе этой системы лежат токенизаторы, которые разбивают текст на синтаксические единицы — токены. Создание кастомного токенизатора позволяет расширять синтаксис Markdown, добавлять новые конструкции и точечно управлять обработкой текста.
Токенизатор — это функция, которая получает текущую позицию в исходном тексте и возвращает объект с распознанным узлом (node) и количеством обработанных символов. Стандартный интерфейс токенизатора в Remark выглядит следующим образом:
function myTokenizer(eat, value, silent) {
// eat — функция для "поглощения" текста
// value — текущий фрагмент текста
// silent — режим проверки без создания узла
}
Ключевые моменты работы токенизатора:
eat(text) — создает узел с текстом,
который токенизатор распознал, и продвигает курсор.silent — если true,
токенизатор должен только проверять возможность распознавания, не
создавая узел.Node
или undefined, если совпадение не найдено.Рассмотрим токенизатор, который обрабатывает кастомный синтаксис
==выделение== для подсветки текста:
function highlightTokenizer(eat, value, silent) {
const match = /^==(.+?)==/.exec(value);
if (!match) return;
if (silent) return true;
return eat(match[0])({
type: 'highlight',
value: match[1]
});
}
highlightTokenizer.locator = function(value, fromIndex) {
return value.indexOf('==', fromIndex);
}
Здесь:
==.locator ускоряет поиск позиции начала токена, что
улучшает производительность при больших файлах.highlight, который можно
затем обрабатывать через плагин или рендерить в HTML с помощью
Rehype.После создания токенизатора его нужно интегрировать в парсер:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
const processor = unified()
.use(remarkParse)
.use(() => (tree, file) => {
// Пример плагина для добавления токенизатора
const Parser = this.Parser;
const tokenizers = Parser.prototype.inlineTokenizers;
const methods = Parser.prototype.inlineMethods;
tokenizers.highlight = highlightTokenizer;
methods.splice(methods.indexOf('text'), 0, 'highlight');
});
Ключевые моменты:
inlineTokenizers — объект, где ключи —
имена токенизаторов, значения — функции.inlineMethods — массив методов,
определяющий порядок обработки токенов. Новые токенизаторы должны быть
вставлены перед или после существующих методов в зависимости от
приоритета.Remark позволяет использовать несколько кастомных токенизаторов
одновременно. Для этого создаются отдельные функции и регистрируются в
inlineTokenizers, а порядок их применения задается в
inlineMethods. Важно избегать пересечения паттернов, иначе
один токенизатор может поглотить текст, предназначенный другому.
tokenizers.emoji = emojiTokenizer;
methods.splice(methods.indexOf('text'), 0, 'emoji');
Rehype работает аналогично, но оперирует HTML-деревом. Кастомные
токенизаторы здесь чаще реализуются как трансформеры, которые обходят
дерево узлов (hast) и модифицируют их. Пример токенизатора
для Rehype:
function rehypeHighlight() {
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'mark') {
node.tagName = 'span';
node.properties = { className: ['highlight'] };
}
});
};
}
visit из пакета unist-util-visit
используется для обхода дерева.element) и тегу
(tagName).locator для
производительности — поиск позиции начала токена значительно
ускоряет обработку больших файлов.silent режим — он
позволяет проверить распознавание без модификации дерева.inlineMethods —
приоритет токенизаторов влияет на то, какой паттерн будет обработан
первым.Кастомные токенизаторы предоставляют мощный инструмент для расширения возможностей Markdown и HTML-парсинга, позволяя создавать новые синтаксические конструкции и управлять процессом преобразования текста на уровне узлов.