Генерация якорных ссылок

Основы работы с заголовками и якорями

Remark и Rehype — это мощные инструменты для парсинга и трансформации Markdown и HTML в экосистеме JavaScript. Одной из ключевых задач при обработке контента является генерация якорных ссылок для заголовков. Якорь позволяет ссылаться на конкретный раздел документа и автоматически формирует URL-фрагмент, который может быть использован для навигации.

В Markdown заголовки представлены синтаксисом #, ##, ### и так далее. Remark преобразует Markdown в AST (Abstract Syntax Tree), где каждый заголовок представлен узлом типа heading. Для генерации якорей необходимо пройтись по дереву и добавить каждому заголовку атрибут id.

Использование плагинов Remark

Для генерации якорей часто используют плагин remark-slug. Его задача — автоматически формировать уникальные идентификаторы для заголовков:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkSlug from 'remark-slug';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

const markdown = `
# Введение
## Основные понятия
### Подробности реализации
`;

const processor = unified()
  .use(remarkParse)
  .use(remarkSlug)
  .use(remarkRehype)
  .use(rehypeStringify);

processor.process(markdown).then(file => {
  console.log(String(file));
});

После применения remark-slug заголовки будут содержать атрибуты id, сгенерированные на основе текста заголовка:

<h1 id="введение">Введение</h1>
<h2 id="основные-понятия">Основные понятия</h2>
<h3 id="подробности-реализации">Подробности реализации</h3>

Настройка формата якорей

По умолчанию remark-slug преобразует текст заголовка в строку в стиле kebab-case, удаляя специальные символы. Для более гибкого контроля можно использовать remark-autolink-headings в сочетании с remark-slug, чтобы добавлять ссылки непосредственно к заголовкам:

import remarkAutolinkHeadings from 'remark-autolink-headings';

const processor = unified()
  .use(remarkParse)
  .use(remarkSlug)
  .use(remarkAutolinkHeadings, {
    beh * avior: 'prepend', // вставка ссылки перед заголовком
    linkProperties: { className: 'anchor-link' },
    content: {
      type: 'text',
      value: '#'
    }
  })
  .use(remarkRehype)
  .use(rehypeStringify);

В результате заголовки будут иметь кликабельный якорь:

<h2 id="основные-понятия">
  <a href="#основные-понятия" class="anchor-link">#</a>
  Основные понятия
</h2>

Использование Rehype для дальнейшей обработки

После генерации id с помощью Remark, Rehype предоставляет возможность дополнительно модифицировать HTML. Например, можно добавлять иконки к якорям, оборачивать заголовки в контейнеры или генерировать карту содержимого (TOC). Работа ведется через обход AST типа hast:

import { visit } from 'unist-util-visit';

function rehypeAddAnchorIcons() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName)) {
        const id = node.properties.id;
        if (id) {
          node.children.unshift({
            type: 'element',
            tagName: 'span',
            properties: { className: ['anchor-icon'] },
            children: [{ type: 'text', value: '?' }]
          });
        }
      }
    });
  };
}

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

<h3 id="подробности-реализации">
  <span class="anchor-icon">?</span>
  Подробности реализации
</h3>

Создание пользовательских форматов якорей

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

function remarkCustomSlug() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      const textNode = node.children.find(n => n.type === 'text');
      if (textNode) {
        // Простейший translit: заменить пробелы на дефисы и привести к нижнему регистру
        node.data = {
          hProperties: {
            id: textNode.value.toLowerCase().replace(/\s+/g, '-')
          }
        };
      }
    });
  };
}

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

Поддержка многоязычного контента

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

import slugify from 'slugify';

function remarkI18nSlug() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      const textNode = node.children.find(n => n.type === 'text');
      if (textNode) {
        node.data = {
          hProperties: {
            id: slugify(textNode.value, { lower: true, strict: true })
          }
        };
      }
    });
  };
}

Это гарантирует, что идентификаторы будут корректны для URL, независимо от языка текста.

Интеграция с динамическими сайтами

Генерация якорей особенно полезна в статических сайтах и документации, где Markdown автоматически конвертируется в HTML. Использование Remark + Rehype позволяет создавать динамические оглавления, реализовать плавную прокрутку по якорям и управлять стилями ссылок через CSS. Например, можно автоматически строить список всех заголовков:

import { visit } from 'unist-util-visit';

function buildTOC(tree) {
  const toc = [];
  visit(tree, 'heading', node => {
    const textNode = node.children.find(n => n.type === 'text');
    if (textNode && node.data && node.data.hProperties.id) {
      toc.push({ title: textNode.value, id: node.data.hProperties.id });
    }
  });
  return toc;
}

Результат можно использовать для генерации бокового меню или интерактивного индекса страницы.


Гибкость Remark и Rehype позволяет полностью контролировать процесс генерации якорных ссылок: от автоматической конвертации заголовков до кастомной обработки для сложных сценариев с многоязычным контентом и динамическими интерфейсами. Это делает библиотеку незаменимым инструментом для современных веб-проектов с Markdown и HTML.