Расширение Markdown синтаксиса

Remark и Rehype — это две тесно связанные библиотеки для работы с Markdown и HTML в экосистеме JavaScript. Remark занимается разбором и трансформацией Markdown в абстрактное синтаксическое дерево (AST), а Rehype работает с HTML и позволяет модифицировать его через AST, аналогично работе с Remark.

Markdown-текст сначала превращается в дерево через Remark, которое затем можно анализировать, изменять или расширять с помощью плагинов. После этого дерево можно конвертировать обратно в Markdown или преобразовать в HTML через Rehype.


AST (Abstract Syntax Tree) в Remark

AST — это структура данных, представляющая Markdown-документ в виде дерева узлов. Каждый узел имеет тип (type) и набор свойств (data, children и другие), что позволяет точно описать структуру документа.

Пример основных типов узлов:

  • root — корень дерева документа.
  • paragraph — абзац.
  • heading — заголовок, с уровнем (depth).
  • text — текст внутри абзаца или заголовка.
  • link — гиперссылка, со свойствами url и title.
  • list — маркированный или нумерованный список.
  • listItem — элемент списка.

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


Расширение Markdown синтаксиса с помощью Remark

Remark поддерживает подключение плагинов, которые обрабатывают AST. С их помощью можно:

  1. Добавлять новые типы блоков и элементов.
  2. Поддерживать кастомные синтаксисы, такие как callout-блоки, заметки или admonitions.
  3. Преобразовывать Markdown в специфичный HTML с пользовательскими атрибутами.

Структура плагина Remark:

function remarkCustomPlugin() {
  return (tree) => {
    visit(tree, 'paragraph', (node) => {
      // пример трансформации текста
      node.children.forEach((child) => {
        if (child.type === 'text') {
          child.value = child.value.replace(/\[important\](.*?)\[\/important\]/g, '**$1**');
        }
      });
    });
  };
}

Объяснение:

  • visit — функция для обхода AST.
  • Плагин ищет все узлы типа paragraph и модифицирует текстовые узлы внутри.
  • Регулярное выражение позволяет реализовать новый синтаксис [important]...[/important].

Конвертация Markdown в HTML через Rehype

После обработки AST Remark можно передавать дерево в Rehype для генерации HTML.

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

const markdown = `
# Заголовок

[important]Важный текст[/important]
`;

unified()
  .use(remarkParse)
  .use(remarkCustomPlugin)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdown)
  .then((file) => {
    console.log(String(file));
  });

Пояснение:

  • remarkParse превращает Markdown в AST.
  • remarkCustomPlugin модифицирует дерево, добавляя поддержку кастомного синтаксиса.
  • remarkRehype конвертирует AST Markdown в AST HTML.
  • rehypeStringify генерирует финальный HTML.

Создание сложных расширений синтаксиса

Для создания более сложных расширений можно использовать:

  1. Inline и Block синтаксис:

    • Inline: подчеркивание, ссылки, эмоджи, цитаты внутри текста.
    • Block: callout-блоки, таблицы, код-блоки с кастомными языками.
  2. Метаданные узлов: AST узлы могут содержать объект data, в котором можно хранить дополнительные свойства для последующего HTML.

node.data = {
  hName: 'div',
  hProperties: { className: ['callout', 'important'] },
};
  1. Обход и модификация дерева:

    • Использование unist-util-visit для обхода всех узлов определённого типа.
    • Создание собственных узлов с типами, не предусмотренными стандартом Markdown.

Взаимодействие Remark и Rehype

Remark и Rehype работают как связка: Remark для обработки Markdown, Rehype для генерации HTML.

Преимущества такой связки:

  • Поддержка расширенного синтаксиса Markdown без изменения оригинальных файлов.
  • Возможность внедрять пользовательские блоки и стили.
  • Универсальность: дерево можно модифицировать или анализировать программно.

Пример кастомного блока:

{
  type: 'customCallout',
  title: 'Важно',
  children: [{ type: 'text', value: 'Это критическая информация.' }]
}

При передаче в Rehype можно указать, что этот узел преобразуется в HTML <div class="callout"> с вложенным содержимым.


Плагины сообщества и экосистема

Remark и Rehype обладают широкой экосистемой:

  • remark-gfm — поддержка GitHub Flavored Markdown (таблицы, чекбоксы, зачеркивания).
  • remark-math и rehype-katex — рендеринг математических формул.
  • remark-frontmatter — работа с YAML-фронтматтером для метаданных документа.

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


Рекомендации по построению расширений

  • Всегда использовать AST для модификации, а не строковые замены.
  • Создавать узлы с понятными type и data, чтобы Rehype мог корректно генерировать HTML.
  • Разделять inline и block-трансформации для удобства поддержки.
  • Подключать существующие плагины при возможности вместо реализации с нуля.

Эти подходы позволяют создавать мощные и гибкие системы работы с Markdown, превращая обычный текст в интерактивный и стилизованный HTML-контент.