unist-util-filter: фильтрация узлов

unist-util-filter — это утилита для работы с деревьями синтаксического представления (AST), совместимыми с unist. Она позволяет создавать новые деревья, фильтруя узлы по заданным критериям, при этом структура дерева сохраняется, а нежелательные ветви удаляются. Это особенно полезно при работе с библиотеками Remark и Rehype, когда требуется выбирать, трансформировать или удалять определённые типы узлов из Markdown или HTML AST.


Установка и подключение

Для использования unist-util-filter необходимо установить пакет через npm:

npm install unist-util-filter

Подключение в коде выглядит следующим образом:

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

Функция filter принимает два основных аргумента:

  1. tree — исходное дерево AST.
  2. predicate — функция или массив функций, определяющих условия фильтрации.

Синтаксис функции filter

const newTree = filter(tree, predicate);
  • tree — объект AST, обычно получаемый через парсер Remark (remark.parse()) или Rehype (rehype.parse()).
  • predicate — функция (node) => boolean, которая возвращает true, если узел должен быть оставлен, и false, если узел должен быть удалён.

Пример использования функции-предиката:

const keepHeadings = node => node.type === 'heading';
const headingsTree = filter(tree, keepHeadings);

В этом примере создаётся новое дерево, включающее только заголовки.


Фильтрация по массиву типов

unist-util-filter поддерживает передачу массива типов узлов. Узлы, соответствующие любому типу из массива, остаются в дереве:

const typesToKeep = ['heading', 'paragraph'];
const filteredTree = filter(tree, typesToKeep);

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


Сохраняется структура дерева

Важный аспект работы unist-util-filter — сохранение иерархии дерева. Если узел не удовлетворяет условию фильтрации, он удаляется, но его потомки проверяются рекурсивно. Таким образом, могут оставаться потомки, даже если родительский узел был исключён:

const keepText = node => node.type === 'text';

const newTree = filter(tree, keepText);

Даже если родительский узел типа paragraph не сохраняется напрямую, все текстовые узлы внутри него будут включены в результат.


Фильтрация с условиями по свойствам узлов

Функция-предикат может использовать любые свойства узла. Например, фильтрация заголовков определённого уровня:

const h2Only = node => node.type === 'heading' && node.depth === 2;
const h2Tree = filter(tree, h2Only);

Аналогично можно фильтровать ссылки по URL или изображения по пути:

const keepExternalLinks = node => node.type === 'link' && /^https?:\/\//.test(node.url);
const externalLinksTree = filter(tree, keepExternalLinks);

Рекурсивная фильтрация с вложенными функциями

Для более сложных случаев можно комбинировать несколько предикатов или строить рекурсивные условия:

const predicate = node => {
  if (node.type === 'heading') return node.depth <= 3;
  if (node.type === 'paragraph') return true;
  return false;
};

const filteredTree = filter(tree, predicate);

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


Производительность и особенности

  • Фильтрация создаёт новое дерево, не модифицируя исходное.
  • Узлы, не соответствующие предикату, удаляются вместе с их потомками, если потомки также не удовлетворяют условиям.
  • Можно безопасно применять фильтрацию к большим AST без риска нарушения структуры документа.
  • Поддерживается интеграция с unist-util-visit и другими утилитами экосистемы unist для комбинированных трансформаций.

Примеры интеграции с Remark и Rehype

Remark: выбор только заголовков и параграфов

import { remark } from 'remark';
import { filter } from 'unist-util-filter';

const md = `
# Заголовок 1
Текст параграфа.

## Заголовок 2
Больше текста.
`;

const tree = remark().parse(md);
const filteredTree = filter(tree, ['heading', 'paragraph']);
console.log(filteredTree);

Rehype: выбор всех ссылок на внешние ресурсы

import { rehype } from 'rehype';
import { filter } from 'unist-util-filter';

const html = `<p>Текст <a href="https://example.com">ссылки</a></p>`;

const tree = rehype().parse(html);
const linksTree = filter(tree, node => node.type === 'element' && node.tagName === 'a' && /^https?:\/\//.test(node.properties.href));

console.log(linksTree);

unist-util-filter является универсальным инструментом для чистки и отбора узлов AST, что позволяет создавать новые представления документа, ориентированные на конкретные задачи: генерацию оглавлений, извлечение ссылок, фильтрацию контента и многое другое. Его гибкость и простота делают его неотъемлемой частью работы с Remark и Rehype.