2024-10-04

Как сделать блог на Svelte + MDX?

Предисловие

Существует такой удивительный формат под названием MDX. Он представляет из себя смесь JSX и Markdown, отсюда и MDX.

Он удобен тем, что на нем легко писать форматированный текст, но при этом не надо отказываться от возможности писать сложные интерактивные компоненты, поскольку их можно вставить как обычный React JSX прямо в тело MDX файла.

### Пример статьи

<CustomTabs>
  <CustomTab
    value={1}
  >
    Да, можно **прямо тут** форматировать!
  </CustomTab>
  <CustomTab
    value={2}
  >
    Или оставлять [ссылки](https://kopyl.dev)
    привычным способом
  </CustomTab>
</CustomTabs>

...

Пример статьи

Да, можно прямо тут форматировать!

Есть целый ряд фреймворков, которые позволяют писать на нем, а для особо ушлых, конечно, есть и варианты без фреймворка. MDX, в конце концов, просто компилируется в обычный React JSX, так что прикрутив этот компилятор MDX к обычному React проекту, можно начать писать в нем MDX.

А как все-таки сделан этот блог?

Ведь эта страница, как и остальные на этом сайте, написана на Svelte!

К счастью для меня и остальных фанатов MDX, есть подобная альтернатива, интегрирующая Markdown и Svelte: MDsveX.

Хоть название и не лучшее, возможности не уступают оригиналу. Самое главное, что у SVX (я буду так его называть), есть возможность передавать в компилятор remark и rehype плагины.

Создание проекта

1. Инициализация проекта

npm create svelte@latest my-blog
cd my-blog

Инициализируем проект на SvelteKit и переходим в каталог с ним.

2. Установка зависимостей

npm i -D mdsvex @mavrin/remark-typograf

Ставим наши зависимости, про вторую чуть позже.

3. Настройка конфигов

// svelte.config.js
import adapter from "@sveltejs/adapter-auto";
import {vitePreprocess} from "@sveltejs/vite-plugin-svelte";
import {mdsvex} from "mdsvex";
import remarkTypograf from "@mavrin/remark-typograf";

const config = {
  extensions: [
    ".svelte",
    ".svx"
  ],
  preprocess: [
    vitePreprocess(),
    mdsvex({
      remarkPlugins: [remarkTypograf] 
    }) 
  ],
  kit: {
    adapter: adapter()
  }
};

export default config;

Отличается от стандартного конфига лишь тем, что мы добавили mdsvex в preprocess и закинули .svx в extensions.

4. Создание страницы

Создадим каталог src/routes/blog, в нем будут все наши статьи. Чтобы создать страницу, создадим в этом каталоге каталог с произвольным именем this-blog и в нем файл +page.svx. В нем можно писать как будто это обычный .svelte файл, но весь Markdown будет на выходе форматирован.

Помимо этого SVX поддерживает Frontmatter — в начале файла напишем блок выделенный минусами.

---
title: "Как сделан этот блог?"
date: "2024-10-02"
---

Теперь эти поля доступны как переменные как в документе, так и для экспорта внутри объекта metadata.

---
title: "Как сделан этот блог?"
date: "2024-10-02"
---

<h1>{title}</h1>

5. Создание списка статей

Теперь создадим файл +page.server.ts в каталоге src/routes/blog. Он будет предоставлять странице список всех статей.

// src/routes/blog/+page.server.ts
import {metadata as thisBlogMetadata} from "./this-blog/+page.svx"

const posts = [
  {
    href: "/blog/this-blog",
    ...thisBlogMetadata
  }
]

export const load = async () => {
  return {
    posts
  }
}

А в файл +page.svelte используем переданный список.

// src/routes/blog/+page.svelte
<script>
  export let data;
</script>

<h2>
  Статьи
</h2>

<div class="flex flex-col gap-14">
  {#each data.posts as post}
    <a
      href={post.href}
    >
      <div class="text-sm">{post.date}</div>
      <h4 class="text-2xl md:text-3xl">{post.title}</h4>
    </a>
  {/each}
</div>

Готово!

Наслаждаемся результатами

В нашем блоге настроен типограф, что позволяет нам не заботиться о переносах строк в неположенных местах, он заполняет текст неразрывными пробелами где надо. Помимо этого, он заменяет " на », выносит кавычки за пределы ссылки, заменяет ... на , превращает -- в .

Это только наиболее часто встречающиеся исправления, помимо них еще огромное количество исправлений и замен.