HAST (HTML Abstract Syntax Tree) — это абстрактное синтаксическое дерево для HTML-документов, которое используется в экосистеме JavaScript, включая библиотеки Rehype и Remark, для структурированной работы с HTML-контентом. HAST позволяет представить HTML в виде дерева узлов, где каждый узел соответствует элементу, атрибуту или текстовому фрагменту, облегчая анализ, трансформацию и генерацию HTML.
Узлы HAST имеют тип Node, который подразделяется на
несколько ключевых категорий:
Root Представляет корень документа. Содержит
массив дочерних узлов в свойстве children. Пример
структуры:
{
type: 'root',
children: [/* массив узлов */]
}Element Основной узел для HTML-элементов. Содержит следующие свойства:
type: 'element'tagName: имя HTML-тега ('div',
'p', 'span')properties: объект с атрибутами элементаchildren: массив дочерних узлов{
type: 'element',
tagName: 'a',
properties: { href: 'https://example.com' },
children: [
{ type: 'text', value: 'Example' }
]
}Text Узел для текстового содержимого. Содержит
свойство value с текстовой строкой.
{
type: 'text',
value: 'Hello, world!'
}Comment Узел для HTML-комментариев.
{
type: 'comment',
value: 'This is a comment'
}Doctype Узел для объявления
<!DOCTYPE>.
{
type: 'doctype',
name: 'html'
}HAST использует унифицированный способ описания атрибутов и свойств:
Важно учитывать, что атрибуты могут иметь типы, отличные от строки:
например, для className используется массив строк, для
булевых атрибутов — true/false.
{
type: 'element',
tagName: 'input',
properties: { type: 'checkbox', checked: true },
children: []
}
Для работы с HAST часто используют библиотеку Rehype, которая предоставляет парсер и трансформеры:
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
const html = '<p>Hello <strong>world</strong></p>';
const tree = unified()
.use(rehypeParse, { fragment: true })
.parse(html);
console.log(tree);
В результате HTML
<p>Hello <strong>world</strong></p>
превращается в дерево:
{
type: 'root',
children: [
{
type: 'element',
tagName: 'p',
properties: {},
children: [
{ type: 'text', value: 'Hello ' },
{
type: 'element',
tagName: 'strong',
properties: {},
children: [{ type: 'text', value: 'world' }]
}
]
}
]
}
HAST удобно модифицировать программно. Для этого применяются плагины Rehype, которые обходят дерево и изменяют узлы:
unist-util-visit:import { visit } from 'unist-util-visit';
visit(tree, 'element', (node) => {
if (node.tagName === 'strong') {
node.tagName = 'b';
}
});
В этом примере все <strong> заменяются на
<b>.
tree.children.push({
type: 'element',
tagName: 'p',
properties: {},
children: [{ type: 'text', value: 'Новый параграф' }]
});
tree.children = tree.children.filter(node => node.type !== 'comment');
После трансформаций дерево можно превратить обратно в HTML с помощью
rehype-stringify:
import rehypeStringify from 'rehype-stringify';
const htmlOutput = unified()
.use(rehypeStringify)
.stringify(tree);
console.log(htmlOutput);
Результат содержит корректный HTML с учетом всех изменений.
HAST тесно интегрируется с Markdown через Remark.
Markdown сначала преобразуется в MDAST (Markdown AST), затем через
remark-rehype конвертируется в HAST, что позволяет
выполнять HTML-трансформации на основе исходного Markdown:
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const markdown = '# Заголовок\n\nТекст параграфа';
const html = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.processSync(markdown)
.toString();
console.log(html);
Markdown преобразуется в HAST с элементами <h1> и
<p>.
HAST является фундаментальным инструментом для современных инструментов обработки контента в JavaScript, обеспечивая удобный и предсказуемый способ работы с HTML как с программной структурой.