Метаданные блоков кода

Блоки кода в Markdown часто сопровождаются метаданными, которые позволяют управлять подсветкой синтаксиса, выполнением кода или интеграцией с различными инструментами. Remark и Rehype предоставляют гибкие механизмы для работы с этими метаданными через AST (Abstract Syntax Tree).

Структура блока кода

В Markdown блок кода оформляется тройными обратными кавычками (```), за которыми может следовать язык программирования и дополнительные данные:

```javascript linenos highlight="2,3"
console.log('Hello World');

В этом примере:
- `javascript` — язык для подсветки.
- `linenos` — пользовательский флаг, например, для отображения номеров строк.
- `highlight="2,3"` — указание строк для подсветки.

В AST Remark этот блок кодов представлен узлом типа `code`:

```json
{
  "type": "code",
  "lang": "javascript",
  "meta": "linenos highlight=\"2,3\"",
  "value": "console.log('Hello World');"
}
  • type всегда "code".
  • lang — язык программирования.
  • meta — строка метаданных, которую можно анализировать для кастомной логики.
  • value — текст кода внутри блока.

Разбор метаданных

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

Пример функции для разбора метаданных:

function parseMeta(meta) {
  const result = {};
  if (!meta) return result;

  const regex = /(\w+)(?:="([^"]+)")?/g;
  let match;

  while ((match = regex.exec(meta)) !== null) {
    const key = match[1];
    const value = match[2] || true;
    result[key] = value;
  }

  return result;
}

Использование:

const metaString = 'linenos highlight="2,3"';
const parsed = parseMeta(metaString);
// parsed = { linenos: true, highlight: "2,3" }

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

Remark предоставляет возможность работать с AST через плагины. Для блоков кода это обычно делается с помощью remark-parse и собственного плагина для обработки code узлов.

Пример плагина для выделения строк:

const visit = require('unist-util-visit');

function remarkHighlightLines() {
  return (tree) => {
    visit(tree, 'code', (node) => {
      const meta = parseMeta(node.meta);
      if (meta.highlight) {
        node.data = node.data || {};
        node.data.highlightLines = meta.highlight.split(',').map(Number);
      }
      if (meta.linenos) {
        node.data = node.data || {};
        node.data.showLineNumbers = true;
      }
    });
  };
}
  • visit используется для обхода всех узлов типа code.
  • node.data хранит структурированные данные для последующей передачи в Rehype или рендерер.

Передача данных в Rehype

После обработки Remark-плагином AST может быть преобразован в HTML через remark-rehype. Метаданные, сохранённые в node.data, можно использовать для добавления атрибутов или классов к элементам <pre> и <code>:

const rehype = require('rehype');
const remarkRehype = require('remark-rehype');
const html = require('rehype-stringify');

function rehypeAddAttributes() {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'code' && node.data) {
        if (node.data.highlightLines) {
          node.properties = node.properties || {};
          node.properties['data-highlight'] = node.data.highlightLines.join(',');
        }
        if (node.data.showLineNumbers) {
          node.properties = node.properties || {};
          node.properties['className'] = (node.properties.className || []).concat('line-numbers');
        }
      }
    });
  };
}

Результирующий HTML может выглядеть так:

<pre class="line-numbers"><code class="language-javascript" data-highlight="2,3">
console.log('Hello World');
</code></pre>

Практические применения метаданных

  1. Подсветка строк Метаданные позволяют задавать конкретные строки для акцента, что удобно для демонстраций и учебных материалов.

  2. Нумерация строк Класс line-numbers может быть обработан сторонними библиотеками, такими как PrismJS, для отображения нумерации.

  3. Фильтры кода Можно скрывать определённые блоки, помеченные специальными метками, например skip или demo.

  4. Динамические параметры Любые дополнительные параметры (например, темы подсветки, автоисполнение) могут передаваться через метаданные и использоваться при рендеринге.

Рекомендации по организации

  • Всегда сохранять метаданные в node.data после разбора. Это гарантирует, что Rehype сможет безопасно использовать их для генерации HTML.
  • Использовать консистентный формат ключ=значение для упрощения парсинга.
  • Разделять логику разбора и рендеринга: Remark отвечает за структурирование данных, Rehype — за их визуальное представление.

Метаданные блоков кода в Remark/Rehype обеспечивают мощный механизм для управления отображением, интерактивностью и дополнительной семантикой кода, оставаясь гибкими и расширяемыми для любых учебных и производственных задач.