javascript

Запилил кросс-фреймворк Markdown/MDX парсер, чтобы не мучаться с контентом

  • пятница, 30 января 2026 г. в 00:00:08
https://habr.com/ru/articles/990754/

Всем привет!

Долго я возился с маркдауном в своих проектах и, честно говоря, знатно подгорел. Первая проблема — это вечный выбор библиотеки.

С одной стороны, есть «конструкторы» типа unified, remark и rehype. Штуки мощные, но настраивать весь этот AST-конвейер и систему плагинов — это какой-то оверхед и лишняя сложность, имхо.

С другой стороны, есть @next/mdx, который вроде и ок, но слишком завязан на страницах и вообще не умеет работать на клиенте.

Раньше я обычно выбирал что-то вроде markdown-to-jsx или react-markdown.

DX у них приятнее, работают и на клиенте, и на сервере, весят мало.

Но вот беда: они «из коробки» не переваривают HTML или MDX, и ты снова вязнешь в настройке плагинов. А если добавить туда i18n (типа i18next или next-intl), начинается настоящий ад. Куча if/else в коде, чтобы отрендерить нужный язык, и бандл раздувается до небес. Плюс вечные косяки с front-matter. Ну и до недавнего времени всё это было только для React.

В общем, решил я написать свое решение для intlayer. Чтобы просто работало.

> К слову, за основу я взял форк markdown-to-jsx v7.7.14 (от quantizor), который базируется на simple-markdown v0.2.2 (от Khan Academy).

Когда пилил этот парсер, ставил перед собой такие цели:

- Максимально легкий вес

- Кросс-фреймворковость (React, Vue, Svelte, Angular, Solid, Preact)

- Простая настройка: никаких бесконечных цепочек плагинов

- Поддержка SSR и клиентского рендеринга

- Настройка на уровне провайдера: можно легко прокинуть свои компоненты из дизайн-системы

- Компонентный подход: полный контроль над рендерингом каждой части приложения

- Типобезопасность (front-matter возвращается как типизированный объект, типизация пропсов компонентов)

- Заточен под i18n (оптимизированная загрузка контента для разных языков)

- Валидация front-matter через схемы zod

Демо:

Можно юзать как обычную утилиту:

import { renderMarkdown } from "react-intlayer"; // Для других фреймворков аналогично: vue-intlayer, svelte-intlayer и т.д.

// Простая функция рендера (возвращает JSX/ноды, а не просто строку)

renderMarkdown("### Мой заголовок", {

components: { h3: (props) => <h3 className="text-xl" {...props} /> },

});

Или через компоненты и хуки:

import {
  MarkdownRenderer,
  MarkdownProvider,
  useMarkdownRenderer,
} from "react-intlayer";

// В виде компонента
<MarkdownRenderer components={{ h3: MyCustomH3 }}>
  ### Мой заголовок
</MarkdownRenderer>;

<MarkdownProvider components={{ h3: MyCustomH3 }}>{children}</MarkdownProvider>;

// В виде хука с контекстом провайдера
const render = useMarkdownRenderer();
return <div>{render("# Привет")}</div>;

Но настоящая магия начинается при использовании с декларацией контента Intlayer (полное разделение логики и данных):

// ./myMarkdownContent.content.ts
import { md } from "intlayer";

export default {
  key: "my-content",
  content: md("## Это мой мультиязычный MD"),

  // Загрузка из файловой системы
  //   content: md(readFileSync("./myMarkdown.md", "utf8")),

  // Загрузка по API
  //   content: md(fetch("https://api.example.com/content").then((res) => res.text())),
};

В самом компоненте это выглядит как обычная переменная — никакого ручного парсинга:

const { myContent } = useIntlayer("my-content");

return (
  <div>
    {myContent} {/* Рендерится автоматически с глобальным конфигом */}
    {/* или */}
    {/* Можно переопределить компоненты на лету */}
    {myContent.use({
      h2: (props) => <h2 className="text-blue-500" {...props} />,
    })}
  </div>
);

В чем, собственно, инновация?

- Реально универсальный: одна и та же логика для React, Vue, Svelte и прочих.

- Легкий MDX-подобный компилятор: отлично работает на Edge и сервере.

- Нулевое время загрузки: контент подгружается на этапе билда (будь то fs или fetch).

- Удобная организация: можно легко переиспользовать куски маркдауна между разными доками.

- Типобезопасный парсинг front-matter (как это делал покойный contentLayer).

Для чего это подходит?

- Блоги / Документация / Политика конфиденциальности / Условия использования

- Динамические данные с бэкенда

- Вынос контента страниц в Headless CMS

- Прямая загрузка .md файлов

Сделал это от безысходности и усталости от текущих решений. Знакомо? Расскажите, как вы сейчас готовите Markdown в своих проектах?

Дока: https://intlayer.org/ru/doc/concept/content/markdown

Гитхаб: https://github.com/intlayer/intlayer