Типобезопасные утилиты

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


Основные типы AST

В Remark используется MDAST (Markdown Abstract Syntax Tree), а в Rehype — HAST (HTML Abstract Syntax Tree). Каждое дерево состоит из узлов (nodes), имеющих определённый тип:

  • Root — корневой узел всего документа.
  • Paragraph — параграф текста.
  • Heading — заголовок, с указанием уровня (depth).
  • Text — текстовый узел.
  • Link — ссылочный узел с url и title.
  • Code — блок кода с языком и содержимым.

Типобезопасность достигается за счет точного описания структуры узлов с помощью TypeScript. Например:

import type { Root, Paragraph, Text } from 'mdast';

const node: Paragraph = {
  type: 'paragraph',
  children: [{ type: 'text', value: 'Пример текста' } as Text],
};

Это предотвращает передачу некорректных свойств узлов и позволяет IDE выдавать автодополнение.


Утилиты для проверки и трансформации узлов

Remark и Rehype предоставляют типобезопасные утилиты для работы с AST:

  1. unist-util-visit Позволяет безопасно обходить дерево, фильтруя узлы по типу:
import { visit } from 'unist-util-visit';
import type { Root, Heading } from 'mdast';

visit(root, 'heading', (node: Heading) => {
  console.log(node.depth, node.children);
});

visit обеспечивает, что колбэк вызывается только с узлами нужного типа.

  1. unist-util-select Позволяет выполнять селекцию узлов по CSS-подобному синтаксису:
import { selectAll } from 'unist-util-select';
import type { Root, Paragraph } from 'mdast';

const paragraphs = selectAll('paragraph', root) as Paragraph[];

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

  1. unist-util-map Создает новое дерево, оставляя исходное без изменений, с сохранением типизации:
import { map } from 'unist-util-map';
import type { Root, Text } from 'mdast';

const newRoot = map(root, (node) => {
  if (node.type === 'text') {
    return { ...node, value: node.value.toUpperCase() } as Text;
  }
  return node;
}) as Root;

Использование map гарантирует, что структура дерева сохраняется и типы узлов корректны.


Создание типобезопасных плагинов

Remark и Rehype поддерживают написание плагинов, которые используют AST для модификации документа. Типобезопасный плагин должен:

  1. Определять тип входного узла (например, Root).
  2. Четко указывать тип возвращаемого узла.
  3. Использовать утилиты visit, map и selectAll для безопасной трансформации.

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

import type { Plugin } from 'unified';
import type { Root, Heading, Text } from 'mdast';
import { visit } from 'unist-util-visit';

const prefixHeadingsPlugin: Plugin<[], Root> = () => {
  return (tree: Root) => {
    visit(tree, 'heading', (node: Heading) => {
      if (node.depth === 2) {
        node.children.unshift({ type: 'text', value: '? ' } as Text);
      }
    });
  };
};

export default prefixHeadingsPlugin;

Типизация гарантирует, что:

  • Узлы действительно являются заголовками.
  • Добавляемые элементы соответствуют структуре MDAST.
  • Плагин совместим с другими обработчиками.

Типобезопасные конвертации между Remark и Rehype

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

import { remark } from 'remark';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import type { Root as MDASTRoot } from 'mdast';
import type { Root as HASTRoot } from 'hast';

const mdast: MDASTRoot = { type: 'root', children: [] };

const processor = remark()
  .use(remarkRehype)
  .use(rehypeStringify);

const html = processor.processSync(mdast).toString(); // HAST → HTML

Явное указание типов предотвращает ошибки на этапе компиляции, а IDE подсказывает допустимые структуры узлов.


Утилиты для создания безопасных AST узлов

Для удобства можно использовать вспомогательные функции для создания узлов:

import type { Paragraph, Text } from 'mdast';

function createParagraph(content: string): Paragraph {
  return {
    type: 'paragraph',
    children: [{ type: 'text', value: content } as Text],
  };
}

const para = createParagraph('Пример абзаца');

Такой подход обеспечивает:

  • Централизованное управление структурой узлов.
  • Автодополнение и проверку типов.
  • Упрощение тестирования и модульного кода.

Встроенные типы в Remark и Rehype

Обе библиотеки активно поддерживают TypeScript. Основные пакеты поставляются с типами, которые:

  • Определяют поля каждого узла.
  • Позволяют безопасно обращаться к свойствам children, value, url, depth.
  • Поддерживают кастомные узлы через расширение интерфейсов.

Например, добавление собственного узла Alert в MDAST:

import type { Literal, Parent } from 'unist';

interface AlertNode extends Parent {
  type: 'alert';
  children: Literal[];
  level: 'info' | 'warning' | 'error';
}

Теперь любые операции с AlertNode будут проверяться TypeScript, предотвращая неправильное использование.


Практика типобезопасной обработки

  1. Никогда не обращаться к полям узла без проверки типа.
  2. Использовать утилиты visit и map для обхода и изменения дерева.
  3. Применять интерфейсы TypeScript для кастомных узлов.
  4. Выделять функции для создания стандартных узлов.
  5. Приводить результаты селекции к ожидаемым типам.

Эти правила обеспечивают стабильность кода при сложных трансформациях документов.


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