Кастомизация GFM поведения

Основы работы с GFM

GFM (GitHub Flavored Markdown) расширяет стандартный Markdown, добавляя поддержку таблиц, чекбоксов, автолинков, ударений и других возможностей. В экосистеме Remark GFM реализован через плагин remark-gfm, который преобразует соответствующие синтаксические конструкции в дерево MDAST (Markdown Abstract Syntax Tree).

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';

const processor = unified()
  .use(remarkParse)
  .use(remarkGfm);

После применения плагина, элементы GFM становятся полноценными узлами MDAST, что позволяет выполнять глубокую кастомизацию на этапе обработки дерева.


Модификация узлов MDAST

Каждый GFM-элемент имеет собственный тип узла. Например:

  • taskList – список с чекбоксами
  • table – таблица
  • tableRow – строка таблицы
  • tableCell – ячейка таблицы

Для изменения поведения этих элементов используют обработчики (visitor-функции), которые обходят дерево:

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

processor.use(() => (tree) => {
  visit(tree, 'tableCell', (node) => {
    // Добавление кастомного класса к каждой ячейке таблицы
    node.data = node.data || {};
    node.data.hProperties = { className: 'custom-cell' };
  });
});

Ключевые моменты:

  • node.data.hProperties используется для передачи свойств в Rehype при последующем преобразовании в HTML.
  • Изменения на уровне MDAST позволяют управлять стилями, атрибутами и структурой выходного HTML.

Кастомизация чекбоксов

Элементы task list в GFM представлены узлами listItem с полем checked. Можно менять их визуальное поведение:

visit(tree, 'listItem', (node) => {
  if (node.checked !== null) {
    node.data = node.data || {};
    node.data.hProperties = { className: node.checked ? 'checked-task' : 'unchecked-task' };
  }
});

Такой подход позволяет:

  • добавлять кастомные иконки вместо стандартных чекбоксов,
  • управлять стилями через CSS-классы,
  • интегрировать поведение с JavaScript (например, обработка клика по чекбоксу).

Расширение таблиц

Стандартные таблицы GFM ограничены базовой разметкой. Для более сложных задач можно внедрять дополнительные атрибуты:

visit(tree, 'table', (node) => {
  node.data = node.data || {};
  node.data.hProperties = { border: '1', className: 'styled-table' };
});

visit(tree, 'tableCell', (node, index, parent) => {
  if (index === 0) {
    node.data = node.data || {};
    node.data.hProperties = { style: 'font-weight:bold' };
  }
});

Применение такого подхода позволяет задавать:

  • стили для конкретных колонок,
  • классы для групп ячеек,
  • динамическое изменение структуры таблицы перед рендером.

Настройка автолинков и ударений

GFM автоматически обрабатывает URL и email через remark-gfm. Для контроля поведения используют посетители узлов link:

visit(tree, 'link', (node) => {
  node.data = node.data || {};
  node.data.hProperties = {
    target: '_blank',
    rel: 'noopener noreferrer',
  };
});

Для ударений (~ или ^) можно добавлять собственные преобразования на этапе MDAST или Rehype, создавая узлы с кастомными тегами.


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

После обработки MDAST, для генерации HTML используют Rehype:

import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';

const html = await processor
  .use(remarkRehype)
  .use(rehypeStringify)
  .process(markdownText);

Особенности интеграции:

  • Узлы MDAST с data.hProperties и data.hName автоматически конвертируются в HTML-атрибуты и теги.
  • Можно менять тег HTML для любого узла:
visit(tree, 'table', (node) => {
  node.data = node.data || {};
  node.data.hName = 'div'; // Преобразует таблицу в div с кастомным классом
});
  • Это даёт полный контроль над структурой итогового HTML и его стилизацией.

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

Для комплексных изменений удобно писать отдельный плагин:

function customGfmPlugin() {
  return (tree) => {
    visit(tree, 'table', (node) => {
      node.data = node.data || {};
      node.data.hProperties = { className: 'custom-table' };
    });

    visit(tree, 'listItem', (node) => {
      if (node.checked !== null) {
        node.data = node.data || {};
        node.data.hProperties = { className: 'custom-task' };
      }
    });
  };
}

processor.use(customGfmPlugin);

Такой подход позволяет объединить все кастомизации GFM в одном месте, поддерживать единообразие и облегчает сопровождение кода.


Рекомендации по производительности

  • Минимизировать количество обходов дерева (visit), комбинируя обработку нескольких типов узлов в одной функции.
  • Сохранять данные в node.data вместо прямой модификации узла, чтобы сохранить совместимость с другими плагинами.
  • Для больших документов рассматривать unist-util-visit-parents при необходимости доступа к родительским узлам.

Полезные утилиты

  • unist-util-visit – обход MDAST.
  • unist-util-map – создание модифицированного дерева без изменения оригинала.
  • hastscript / h – генерация кастомного HTML при необходимости.

Эти инструменты позволяют создавать гибкие решения для сложных документов GFM, объединяя возможности Remark и Rehype.