---
title: "Как сделать блог на Svelte + MDX?"
date: "2024-10-04"
---

<script>
import ArticleTitle from "$lib/components/ArticleTitle.svelte"
import CustomTabs from "./CustomTabs.svelte"
</script>

<ArticleTitle metadata={metadata} />

## Предисловие

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

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

<div class='grid md:grid-cols-2 gap-4'>
  <div>

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

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

...
```

  </div>
  <div class='my-2 p-4'>

    <h3 class='text-2xl'>Пример статьи</h3>

    <CustomTabs>
      <div slot="1">

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

      </div>
      <div slot="2">

        Или оставлять [ссылки](https://kopyl.dev) привычным способом

      </div>
    </CustomTabs>

  </div>
</div>

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

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

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

К счастью для меня и остальных фанатов MDX, есть подобная альтернатива, интегрирующая Markdown и Svelte: [MDsveX](https://github.com/pngwn/MDsveX).

Хоть название и не лучшее, возможности не уступают оригиналу.
Самое главное, что у SVX (я буду так его называть), есть возможность передавать
в компилятор [remark](https://github.com/remarkjs/remark) и [rehype](https://github.com/rehypejs/rehype) плагины.

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

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

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

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

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

```bash
npm i -D mdsvex @mavrin/remark-typograf
```

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

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

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

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

export default config;
```

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

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

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

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

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

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

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

<h1>{title}</h1>
```

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

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

```typescript
// 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` используем переданный список.

```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>
```

Готово!

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

В нашем блоге настроен [типограф](https://github.com/typograf/typograf), что позволяет нам не заботиться о переносах строк в неположенных местах,
он заполняет текст неразрывными пробелами где надо.
Помимо этого, он заменяет `"` на `»`, выносит кавычки за пределы ссылки, заменяет `...` на `…`, превращает `--` в `—`.

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