Всем привет!
Долго я возился с маркдауном в своих проектах и, честно говоря, знатно подгорел. Первая проблема — это вечный выбор библиотеки.
С одной стороны, есть «конструкторы» типа 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 в своих проектах?
