Парсинг HTML в HAST (Hypertext Abstract Syntax Tree) является ключевым этапом при работе с библиотеками Rehype и Remark, особенно когда необходимо преобразовать HTML-контент в дерево для последующей обработки или анализа. HAST — это абстрактное представление HTML-документа в виде дерева, где каждый узел соответствует определённому элементу HTML, атрибуту или текстовому содержимому. В отличие от DOM, HAST ориентирован на работу в среде Node.js и удобен для трансформаций, плагинов и генерации контента.
HAST строится на основе следующих типов узлов:
Root — корневой узел документа. Содержит массив
children, включающий все элементы HTML-документа.
Element — узел, представляющий HTML-тег. Основные свойства:
tagName — имя тега (div, p,
a и т.д.).properties — объект с атрибутами тега.children — массив дочерних узлов.Text — узел текста. Содержит свойство
value с текстовым содержимым.
Comment — узел комментария HTML. Содержит
свойство value.
Doctype — узел определения типа документа.
Пример структуры узла Element в HAST:
{
type: 'element',
tagName: 'p',
properties: { className: ['intro'] },
children: [
{ type: 'text', value: 'Пример текста внутри параграфа.' }
]
}
Rehype предоставляет модуль rehype-parse, который
позволяет преобразовать строку HTML в HAST. Пример базового
парсинга:
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
const html = '<div class="container"><p>Текст абзаца</p></div>';
const tree = unified()
.use(rehypeParse, { fragment: true })
.parse(html);
console.log(tree);
Параметр fragment: true указывает парсеру, что
переданная строка может быть фрагментом HTML, а не полноценным
документом с <!DOCTYPE> и html
тегами.
HAST-дерево можно обходить и изменять с помощью таких инструментов,
как unist-util-visit или напрямую через рекурсивные
функции. Например, изменение текста всех параграфов:
import { visit } from 'unist-util-visit';
visit(tree, 'element', (node) => {
if (node.tagName === 'p') {
node.children.push({ type: 'text', value: ' Дополненный текст.' });
}
});
Ключевые моменты при работе с HAST:
element могут иметь вложенные дочерние
элементы, что позволяет строить сложные иерархии.properties всегда объект, где ключи
соответствуют атрибутам HTML.value.Для генерации HTML из HAST используется
rehype-stringify:
import rehypeStringify from 'rehype-stringify';
const htmlOutput = unified()
.use(rehypeStringify)
.stringify(tree);
console.log(htmlOutput);
// <div class="container"><p>Текст абзаца Дополненный текст.</p></div>
Таким образом, процесс выглядит как «парсинг → трансформация → генерация», что позволяет гибко обрабатывать контент на уровне синтаксического дерева.
Remark используется для работы с Markdown, но с помощью
remark-rehype можно преобразовывать Markdown в HAST:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const markdown = '# Заголовок\n\nПараграф текста.';
const htmlFromMarkdown = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.processSync(markdown)
.toString();
console.log(htmlFromMarkdown);
// <h1>Заголовок</h1><p>Параграф текста.</p>
Этот подход позволяет объединять возможности Markdown и HTML, создавая единое дерево HAST для дальнейшей обработки, добавления плагинов или оптимизации HTML.
type, tagName, properties,
children) для предотвращения ошибок при
трансформациях.unist-util-visit или
рекурсивные функции для упрощения манипуляций.rehype-sanitize, если исходный HTML поступает
из ненадёжных источников, чтобы избежать XSS-уязвимостей.HAST предоставляет мощный и гибкий инструмент для работы с HTML на уровне синтаксического дерева, что делает его основой современных систем генерации и трансформации контента в экосистеме JavaScript.