В экосистеме Remark и Rehype ToC (Table of Contents) представляет собой структурированный список заголовков документа, который позволяет пользователю ориентироваться в содержимом. Для генерации и позиционирования ToC используется комбинация парсинга Markdown через Remark и последующей трансформации в HTML через Rehype.
Важной особенностью является то, что ToC формируется на
основе AST (Abstract Syntax Tree) документа. AST предоставляет
возможность детально анализировать заголовки (heading) и их
уровни (depth), что критически важно для правильной
вложенности и структуры ToC.
Remark позволяет получить AST документа с помощью функции
remark().parse(). Структура AST содержит ключевые
свойства:
type — тип узла (например, root,
heading, paragraph).depth — уровень заголовка (1–6).children — дочерние узлы, содержащие текстовые элементы
(text) и другие вложенные структуры.Пример обхода AST для извлечения заголовков:
import { remark } from 'remark';
const markdown = `
# Заголовок 1
## Заголовок 1.1
### Заголовок 1.1.1
`;
const tree = remark().parse(markdown);
function extractHeadings(node, headings = []) {
if (node.type === 'heading') {
const text = node.children
.filter(child => child.type === 'text')
.map(child => child.value)
.join('');
headings.push({ depth: node.depth, text });
}
if (node.children) {
node.children.forEach(child => extractHeadings(child, headings));
}
return headings;
}
const toc = extractHeadings(tree);
console.log(toc);
Результат:
[
{ "depth": 1, "text": "Заголовок 1" },
{ "depth": 2, "text": "Заголовок 1.1" },
{ "depth": 3, "text": "Заголовок 1.1.1" }
]
После генерации структуры ToC необходимо вставить его в конкретное место документа. Это достигается через Remark-плагины, которые модифицируют AST перед передачей Rehype. Типовой подход:
tocPlaceholder).list) с элементами
(listItem) для каждого заголовка.Пример создания узлов списка:
import { u } from 'unist-builder';
function createTocNode(headings) {
return u('list', { ordered: false },
headings.map(heading =>
u('listItem', {}, [
u('paragraph', {}, [
u('text', `${' '.repeat(heading.depth - 1)}${heading.text}`)
])
])
)
);
}
В этом примере отступ создается на основе уровня заголовка, что позволяет визуально отражать иерархию документа.
После вставки ToC в AST Markdown его необходимо преобразовать в HTML. Rehype предоставляет гибкость для управления позиционированием через свойства HTML и CSS-классы:
id для привязки якорей к
заголовкам.Пример привязки якорей к заголовкам:
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
const processedHtml = await remark()
.use(rehypeSlug)
.use(rehypeAutolinkHeadings, { beh * avior: 'wrap' })
.process(markdown);
Здесь каждый заголовок получает уникальный id, что
позволяет ToC ссылаться на соответствующие секции документа.
Фиксированный sidebar ToC может быть закреплен
слева или справа от основного контента с помощью CSS
(position: sticky), что позволяет пользователю всегда
видеть структуру документа при прокрутке.
Встроенный ToC в начале документа Вставка ToC как первого элемента AST делает его частью контента, что удобно для PDF-генерации или статичных HTML-страниц.
Динамическая генерация при рендеринге Если Markdown часто меняется, можно генерировать ToC на лету в браузере, обходя уже скомпилированный HTML через Rehype. Это позволяет минимизировать время сборки и поддерживать актуальность ссылок.
Поддержка глубоких уровней вложенности Для
больших документов важно корректно визуализировать уровни
h4–h6. AST-подход Remark позволяет создавать вложенные
списки (list внутри listItem) для точного
отражения структуры.
Архитектурно процесс выглядит так:
Такой подход обеспечивает точное позиционирование ToC, правильную вложенность, удобство навигации и возможность гибко управлять отображением на любых типах платформ и устройств.