Кастомный парсер

MDX — это расширение Markdown, которое позволяет интегрировать JSX-компоненты напрямую в текстовые документы. При работе с нестандартными требованиями к разметке часто возникает необходимость создания кастомного парсера, который обрабатывает специфические конструкции и преобразует их в нужные JSX-компоненты.

MDX основан на экосистеме remark и rehype, что делает его крайне гибким для трансформации AST (Abstract Syntax Tree). Основная идея кастомного парсера — внедрить свои правила в процесс разбора Markdown/MDX и при необходимости модифицировать структуру AST перед рендерингом.


Структура AST в MDX

В MDX каждый документ преобразуется в дерево узлов, где каждый узел представляет элемент Markdown или JSX. Основные типы узлов:

  • root — корневой узел документа.
  • paragraph — абзац текста.
  • heading — заголовок, с уровнем depth.
  • text — текстовый контент.
  • inlineCode — инлайн-код.
  • code — блок кода с указанием языка.
  • jsx — узел, содержащий JSX-разметку.

Каждый узел имеет свойства type, value (для текстовых узлов), а также children, если узел может содержать вложенные элементы. Понимание структуры AST является ключевым при написании кастомного парсера, так как трансформации выполняются именно на этом уровне.


Подключение кастомного плагина

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

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

function remarkCustomParser() {
  return (tree) => {
    // Обход всех узлов AST
    visit(tree, 'text', (node) => {
      if (node.value.includes('::highlight::')) {
        node.type = 'jsx';
        node.value = `${node.value.replace('::highlight::', '')}`;
      }
    });
  };
}

В этом примере все текстовые узлы, содержащие метку ::highlight::, преобразуются в JSX-компонент .


Работа с visit и обходом дерева

Для обхода AST используется функция visit из пакета unist-util-visit. Она позволяет рекурсивно проходить по всем узлам и выполнять операции только над нужными типами:

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

visit(tree, 'heading', (node) => {
  if (node.depth === 2) {
    node.children.push({
      type: 'text',
      value: ' ?'
    });
  }
});

В этом примере к каждому заголовку второго уровня добавляется специальный символ. Такой подход позволяет гибко модифицировать AST перед генерацией финального MDX.


Создание собственных синтаксических конструкций

Кастомный парсер позволяет вводить свои синтаксические конструкции. Например, можно определить блок :::note для заметок:

function remarkCustomNote() {
  return (tree) => {
    visit(tree, 'paragraph', (node, index, parent) => {
      const match = /^:::note\s*(.*)/.exec(node.children[0]?.value);
      if (match) {
        parent.children[index] = {
          type: 'jsx',
          value: `${match[1]}`,
        };
      }
    });
  };
}

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


Комбинация с rehype для HTML-трансформаций

После обработки Markdown-плагинами AST передается в rehype для работы с HTML. Можно создавать rehype-плагины, чтобы:

  • Оборачивать элементы в кастомные компоненты.
  • Изменять атрибуты тегов.
  • Добавлять стили или классы к элементам.

Пример добавления класса ко всем ссылкам:

function rehypeAddLinkClass() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'a') {
        node.properties = { ...node.properties, className: 'custom-link' };
      }
    });
  };
}

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

  • Минимизировать глобальные изменения — лучше ограничиваться конкретными типами узлов.
  • Сохранять совместимость с существующим MDX-рендерером.
  • Разделять обработку Markdown и JSX — использовать remark для синтаксиса, rehype для HTML.
  • Тестировать на разных документах — особенно если есть вложенные компоненты и блоки кода.

Интеграция в проект

Кастомные плагины подключаются в MDX через mdxOptions:

import { MDXProvider } from '@mdx-js/react';
import remarkCustomParser from './remarkCustomParser';


  

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