AST манипуляции

MDX (Markdown for JSX) объединяет синтаксис Markdown и возможности JSX, позволяя включать компоненты React прямо в документы Markdown. Для понимания и модификации MDX-документов на глубоком уровне используется AST (Abstract Syntax Tree) — абстрактное синтаксическое дерево, которое представляет структуру документа.

AST в MDX строится на основе двух уровней:

  1. MDAST (Markdown AST) — описывает структуру Markdown: заголовки, параграфы, списки, цитаты, кодовые блоки.
  2. HAST (HTML AST) — описывает HTML-представление, необходимое для рендеринга JSX-компонентов.

MDX парсер сначала строит MDAST, затем преобразует его в HAST, где JSX-компоненты интегрируются в дерево как специальные узлы. Этот процесс позволяет выполнять сложные трансформации и анализ документов на уровне синтаксиса.


Структура узлов MDX AST

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

  • root — корневой узел документа, содержит массив children.
  • paragraph — параграф текста, children содержит текстовые или встроенные узлы.
  • heading — заголовок, поле depth задаёт уровень (1-6), children — текст.
  • text — текстовый узел с полем value.
  • code — блок кода с полями lang и value.
  • inlineCode — встроенный код.
  • jsx — узел с JSX-кодом, содержимое хранится в value.
  • link — ссылка с атрибутом url и children.

Пример AST узла параграфа:

{
  "type": "paragraph",
  "children": [
    {
      "type": "text",
      "value": "Пример текста в параграфе"
    }
  ]
}

Инструменты для работы с AST

MDX интегрируется с экосистемой unified, что позволяет использовать цепочку плагинов:

  • remark-plugins — работают на уровне Markdown (MDAST). Используются для анализа, добавления или модификации Markdown-узлов.
  • rehype-plugins — работают на уровне HTML (HAST). Позволяют модифицировать результат рендеринга, включая JSX-компоненты.
  • unist-util-visit — утилита для обхода AST, применяемая для поиска и трансформации узлов по типу или условию.
  • unist-builder — упрощает создание новых узлов для вставки в дерево.

Пример обхода и изменения всех заголовков уровня 2:

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

visit(ast, 'heading', (node) => {
  if (node.depth === 2) {
    node.children.push({ type: 'text', value: ' (обновлено)' });
  }
});

Преобразование и вставка JSX

Вставка компонентов React в MDX выполняется через узлы типа jsx. Для добавления компонента в документ:

import { u } from 'unist-builder';

const jsxNode = u('jsx', { value: '<MyComponent prop="value" />' });
ast.children.push(jsxNode);

MDX автоматически обрабатывает JSX при рендеринге, если узел правильно встроен в AST. При этом важно учитывать, что JSX узлы находятся на этапе HAST, поэтому для сложных трансформаций может потребоваться использование mdxjs/mdx с включением опции rehypePlugins.


Трансформации AST

Трансформации делятся на несколько типов:

  1. Аналитические — подсчет узлов, извлечение текста, построение оглавления.
  2. Редактирующие — изменение текста, замена компонентов, вставка новых узлов.
  3. Фильтрующие — удаление узлов по критериям (например, все комментарии или неиспользуемые компоненты).

Пример фильтрации всех комментариев из документа:

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

visit(ast, 'html', (node, index, parent) => {
  if (node.value.startsWith('<!--')) {
    parent.children.splice(index, 1);
  }
});

Практические приёмы

  • Глубокая вставка: при вставке узлов внутрь параграфов или списков использовать вложенные children для корректного синтаксиса.
  • Сохранение исходного форматирования: при изменении AST важно не нарушать порядок узлов, чтобы избежать ошибок рендеринга.
  • Проверка типов: перед манипуляцией проверять node.type, чтобы случайно не изменить неподходящий узел.
  • Комбинирование remark и rehype: сначала выполнять операции на уровне Markdown (текст), затем на HTML/JSX, если нужны визуальные изменения.

Отладка AST

  • Использовать console.log(JSON.stringify(ast, null, 2)) для визуализации дерева.
  • Инструменты вроде AST Explorer позволяют подключать remark-mdx и визуально видеть результат парсинга.
  • Для больших документов полезно писать функции обхода с выводом только нужных типов узлов, чтобы не перегружать вывод.

Оптимизация производительности

  • Обрабатывать AST по мере необходимости, избегая полного обхода всех узлов, если достаточно целевых изменений.
  • Использовать фильтры в unist-util-visit или unist-util-visit-parents для селективной модификации.
  • Для часто изменяемых больших документов рассматривать промежуточное кеширование AST.

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