Breaking changes и их обработка

Библиотеки Remark и Rehype активно развиваются, и с каждым крупным обновлением появляются breaking changes — изменения, которые нарушают обратную совместимость. Их неправильная обработка может привести к поломке существующих парсеров, плагинов и конвейеров обработки Markdown или HTML.


Понимание breaking changes

Breaking changes можно разделить на несколько категорий:

  1. Изменение API функций

    • Порядок аргументов может меняться.
    • Возвращаемые значения могут быть другими типами (например, раньше string, теперь Node).
    • Асинхронные функции могут стать синхронными или наоборот.
  2. Изменение структуры AST

    • Remark использует формат MDAST, Rehype — HAST.
    • Узлы могут получать новые обязательные свойства или изменять старые.
    • Появление новых типов узлов может ломать плагины, которые не предусмотрели их обработку.
  3. Обновление зависимостей

    • Breaking changes могут приходить вместе с обновлением зависимостей, например unified, micromark или rehype-parse.
    • Изменения на уровне парсера часто проявляются как новые правила или недопустимые символы в старых конфигах.

Методы обработки breaking changes

1. Чтение changelog и миграционных гайдлайнов

  • Каждое крупное обновление Remark и Rehype сопровождается детализированным changelog.
  • Там описаны измененные функции, удаленные методы и рекомендуемые замены.
  • Пример: при переходе с Rehype 11 на 12 rehype-highlight изменил способ передачи опций.

2. Использование TypeScript и типизации

  • Типизация помогает обнаружить несовпадения в API на этапе компиляции.
  • Новые версии MDAST/HAST часто сопровождаются обновленными типами @types/unist и @types/mdast.
  • Пример: если новый узел добавлен в AST, TypeScript предупредит о несовпадении при обработке старым кодом.

3. Изоляция кода через плагины

  • Разделение обработки на независимые плагины снижает риски поломки всей цепочки.
  • Плагины могут быть протестированы на разных версиях Remark/Rehype.
  • Использование функций processor.use(plugin) позволяет подключать плагины с опцией backward-compatible.

4. Написание тестов на AST

  • Сравнение ожидаемой структуры узлов с фактической после парсинга.
  • Тесты фиксируют изменения в MDAST/HAST, которые могут быть незаметны на уровне визуального HTML/Markdown.
  • Рекомендуется покрывать кейсы с краевыми символами, например эмодзи, нестандартные HTML-теги, вложенные списки.

5. Логирование и мониторинг

  • В продакшне полезно логировать структуры AST перед и после обработки.
  • Это позволяет быстро выявлять изменения поведения после обновления библиотеки.

Практические примеры

Изменение структуры узлов в Remark:

Раньше узлы code имели поля:

{
  type: 'code',
  lang: 'js',
  value: 'console.log("Hello")'
}

В новой версии поле meta стало обязательным:

{
  type: 'code',
  lang: 'js',
  value: 'console.log("Hello")',
  meta: null
}

Без добавления meta TypeScript выдаст ошибку, а плагин форматирования сломается.

Переход на Rehype 12:

  • Ранее: rehype-stringify() принимал объект с опцией closeSelfClosing.
  • Сейчас опция заменена на tightSelfClosing.
  • Старый код:
processor.use(rehypeStringify, { closeSelfClosing: true });
  • Новый код:
processor.use(rehypeStringify, { tightSelfClosing: true });

Без изменений HTML-вывод может быть некорректным.


Стратегии минимизации риска

  1. Фиксировать версии библиотек в package.json

    • Использование семантических версий (^ и ~) иногда приводит к неожиданным breaking changes.
    • Предпочтительно фиксировать точную версию, например "remark": "14.0.2".
  2. Пошаговое обновление

    • Сначала обновление minor-патчей, потом major.
    • Проверка всех плагинов и кастомных обработчиков на совместимость.
  3. Использование адаптеров и обёрток

    • Можно написать обёртку для старого API, которая переводит новые узлы AST в старый формат.
    • Это временное решение, пока весь код не адаптирован.
  4. Документирование изменений в проекте

    • Хранение заметок о том, какие breaking changes уже обработаны.
    • Особенно важно для командных проектов с несколькими модулями, использующими Remark/Rehype.

Инструменты для облегчения работы с breaking changes

  • unist-util-visit и unist-util-visit-parents Позволяют безопасно обходить AST без привязки к конкретной структуре узлов.

  • remark-lint и rehype-lint Помогают выявлять устаревшие конструкции Markdown/HTML.

  • remark-rehype Позволяет корректно конвертировать между MDAST и HAST, минимизируя проблемы с несовместимостью узлов.

  • Тестовые фикстуры Наборы Markdown и HTML с разнообразными структурами, которые проверяются после обновления библиотек.


Заключение по подходам

Обработка breaking changes в Remark и Rehype требует системного подхода, включающего: изучение changelog, использование типизации, написание тестов, логирование AST, и поэтапное обновление зависимостей. Такой подход снижает риск поломки обработки Markdown/HTML и обеспечивает стабильность разработки при миграции на новые версии библиотек.