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, что позволяет выполнять глубокую кастомизацию на этапе обработки дерева.
Каждый GFM-элемент имеет собственный тип узла. Например:
Для изменения поведения этих элементов используют обработчики (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.Элементы 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' };
}
});
Такой подход позволяет:
Стандартные таблицы 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, создавая узлы с
кастомными тегами.
После обработки MDAST, для генерации HTML используют Rehype:
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
const html = await processor
.use(remarkRehype)
.use(rehypeStringify)
.process(markdownText);
Особенности интеграции:
data.hProperties и data.hName
автоматически конвертируются в HTML-атрибуты и теги.visit(tree, 'table', (node) => {
node.data = node.data || {};
node.data.hName = 'div'; // Преобразует таблицу в div с кастомным классом
});
Для комплексных изменений удобно писать отдельный плагин:
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 при необходимости доступа к
родительским узлам.Эти инструменты позволяют создавать гибкие решения для сложных документов GFM, объединяя возможности Remark и Rehype.