Интеграция с highlight.js

Основные понятия

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

highlight.js — библиотека для подсветки синтаксиса кода. Она автоматически определяет язык блока кода и применяет стили, преобразуя текст кода в HTML с CSS-классами для последующего форматирования.

Интеграция Remark/Rehype с highlight.js позволяет преобразовывать Markdown с кодом в HTML, где каждый блок кода будет подсвечен, сохраняя структуру документа.


Настройка проекта

Для начала необходимо установить зависимости:

npm install remark remark-html rehype rehype-highlight highlight.js
  • remark — ядро для работы с Markdown.
  • remark-html — плагин для преобразования Markdown в HTML.
  • rehype — ядро для работы с HTML AST.
  • rehype-highlight — плагин для интеграции highlight.js в Rehype.
  • highlight.js — сама библиотека подсветки.

Преобразование Markdown с подсветкой

Основной поток работы состоит из нескольких шагов:

  1. Парсинг Markdown в AST с помощью Remark.
  2. Преобразование AST Markdown в HTML AST через Remark → Rehype.
  3. Применение highlight.js к блокам кода через rehype-highlight.
  4. Генерация финального HTML.

Пример кода:

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeHighlight from 'rehype-highlight';
import fs from 'fs';

const markdown = fs.readFileSync('example.md', 'utf-8');

const html = await unified()
  .use(remarkParse)            // Парсинг Markdown в AST
  .use(remarkRehype)           // Преобразование в HTML AST
  .use(rehypeHighlight)        // Подсветка кода
  .use(rehypeStringify)        // Преобразование AST в HTML
  .process(markdown);

console.log(String(html));

Особенности работы rehype-highlight:

  • Автоматическое определение языка по первой строке блока (```javascript).
  • Возможность передавать массив поддерживаемых языков, чтобы уменьшить размер финальной сборки.
  • Поддержка кастомных CSS-классов для стилизации.

Настройка языков для оптимизации

В проектах с большим количеством языков иногда требуется подключать только нужные языки для сокращения размера бандла:

import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import python from 'highlight.js/lib/languages/python';

hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('python', python);

Затем передаем этот кастомный объект в rehype-highlight:

.use(rehypeHighlight, { languages: hljs.listLanguages().reduce((acc, lang) => {
  acc[lang] = hljs.getLanguage(lang);
  return acc;
}, {}) })

Использование стилей highlight.js

Для корректного отображения подсвеченного кода необходимо подключить CSS темы. Пример:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.8.0/styles/github.css">

Highlight.js поддерживает большое количество встроенных тем: github, atom-one-dark, monokai, vs, dracula и другие.


Расширенные возможности

  1. Кастомизация подсветки отдельных блоков:

Rehype позволяет изменять HTML AST перед генерацией финального HTML. Например, можно добавлять свои классы или атрибуты к блокам <pre>:

.use(() => (tree) => {
  visit(tree, 'element', (node) => {
    if (node.tagName === 'pre') {
      node.properties.className = (node.properties.className || []).concat('custom-pre');
    }
  });
});
  1. Применение highlight.js для инлайнового кода:

По умолчанию rehype-highlight работает с блочными элементами <pre><code>. Для инлайнового кода можно использовать rehype-sanitize и обрабатывать теги <code> отдельно.

  1. Асинхронная обработка файлов:

Если требуется обрабатывать несколько Markdown-файлов одновременно, unified().process() возвращает Promise, что удобно для интеграции с современными сборщиками и серверными приложениями.


Типичные ошибки и нюансы

  • Несоответствие версий remark/rehype и плагинов может приводить к ошибкам типа TypeError: node.type is undefined.
  • Отсутствие CSS-темы делает подсветку бессмысленной — код останется без стилей.
  • Передача неподдерживаемого языка в rehype-highlight вызывает отсутствие подсветки для этого блока.
  • Для серверной генерации HTML (например, в Node.js) необходимо использовать полифиллы для fetch и других API, если темы загружаются динамически через CDN.

Итоговый поток обработки Markdown с подсветкой

  1. Remark парсит Markdown → AST
  2. Remark-Rehype преобразует Markdown AST → HTML AST
  3. Rehype-Highlight применяет подсветку к <pre><code> блокам
  4. Rehype-Stringify превращает AST в готовый HTML
  5. Подключение CSS темы обеспечивает визуальное оформление подсветки

Этот подход обеспечивает гибкость: можно добавлять новые плагины, фильтровать или модифицировать AST, использовать кастомные стили и интегрировать процесс в любые веб-приложения на Node.js или в браузере.