unist-builder: построение деревьев

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

Основная идея

Библиотека предоставляет функцию u(type, props?, children?), где:

  • type — строка, обозначающая тип узла (paragraph, heading, text, element, root и др.);
  • props — объект с дополнительными свойствами узла (например, depth для заголовков, url для ссылок, alt для изображений);
  • children — массив дочерних узлов или одиночный узел.

Простая структура узла в unist выглядит так:

{
  type: 'имя_узла',
  data: { /* дополнительные свойства */ },
  children: [ /* дочерние узлы */ ]
}

unist-builder автоматизирует создание этой структуры.

Установка

npm install unist-builder

Или через Yarn:

yarn add unist-builder

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

Создание текстового узла
import u from 'unist-builder';

const textNode = u('text', 'Привет, мир!');
console.log(textNode);

Результат:

{
  "type": "text",
  "value": "Привет, мир!"
}
Создание параграфа с текстом
const paragraph = u('paragraph', [u('text', 'Это параграф с текстом.')]);

Структура AST:

{
  "type": "paragraph",
  "children": [
    {
      "type": "text",
      "value": "Это параграф с текстом."
    }
  ]
}
Создание заголовков

Для заголовков можно передавать объект с пропсами:

const heading = u('heading', { depth: 2 }, [u('text', 'Подзаголовок')]);

Результат:

{
  "type": "heading",
  "depth": 2,
  "children": [
    {
      "type": "text",
      "value": "Подзаголовок"
    }
  ]
}

Деревья с несколькими уровнями

unist-builder идеально подходит для построения сложных иерархий:

const root = u('root', [
  u('heading', { depth: 1 }, [u('text', 'Главная тема')]),
  u('paragraph', [u('text', 'Вступительный абзац.')]),
  u('list', { ordered: true }, [
    u('listItem', [u('paragraph', [u('text', 'Первый элемент')])]),
    u('listItem', [u('paragraph', [u('text', 'Второй элемент')])])
  ])
]);

Такой корневой узел root может быть сразу передан в Remark или Rehype для дальнейшей обработки или рендеринга.

Интеграция с Remark

После построения дерева его можно обработать плагинами Remark:

import { remark } from 'remark';
import remarkStringify from 'remark-stringify';

remark()
  .use(remarkStringify)
  .process(root)
  .then(file => {
    console.log(String(file));
  });

Результат генерации Markdown:

# Главная тема

Вступительный абзац.

1. Первый элемент
2. Второй элемент

Особенности работы с Rehype

unist-builder совместим и с HTML-деревьями через Rehype. Основное отличие — типы узлов:

  • element вместо paragraph или heading;
  • свойства tagName, properties и children.

Пример HTML-дерева:

const htmlRoot = u('root', [
  u('element', { tagName: 'h1' }, [u('text', 'Заголовок')]),
  u('element', { tagName: 'p' }, [u('text', 'Абзац текста')])
]);

После обработки через Rehype с плагином rehype-stringify получится:

<h1>Заголовок</h1>
<p>Абзац текста</p>

Работа с атрибутами узлов

Для HTML-элементов properties позволяет задавать любые атрибуты:

const link = u('element', {
  tagName: 'a',
  properties: { href: 'https://example.com', target: '_blank' }
}, [u('text', 'Ссылка')]);

AST:

{
  "type": "element",
  "tagName": "a",
  "properties": { "href": "https://example.com", "target": "_blank" },
  "children": [
    { "type": "text", "value": "Ссылка" }
  ]
}

Советы по построению деревьев

  • Минимизировать глубину вложенности, чтобы облегчить обработку AST плагинами.
  • Использовать u для единообразного создания узлов, вместо ручного создания объектов.
  • Явно указывать тип узла — это повышает совместимость с Remark/Rehype и сторонними плагинами.
  • Сочетать с утилитами: unist-util-visit и unist-util-map для обхода и трансформации деревьев.

Заключение по практике

unist-builder позволяет легко формировать и модифицировать Markdown и HTML-деревья, что упрощает работу с экосистемой Remark/Rehype. Использование единообразной функции u делает код чистым, предсказуемым и полностью совместимым с плагинами. Такой подход особенно полезен для генерации динамического контента, автоматической сборки документации и кастомной обработки AST.