Svelte 5 и SvelteKit: руны, компилятор и когда выбирать Svelte
Как устроен современный Svelte: руны $state, $derived, $effect и $props, сниппеты, общее состояние, SvelteKit с загрузкой данных и формами, производительность без мифов, миграция с Svelte 4 и когда Svelte уместнее React.
Svelte часто описывают одной фразой: «компилятор вместо библиотеки». В этом и есть его главное отличие от React и Vue. Большая часть работы, которую другие фреймворки выполняют в браузере во время работы приложения, у Svelte происходит на этапе сборки: компилятор превращает компоненты в компактный JavaScript, который напрямую обновляет нужные узлы страницы. SvelteKit — фреймворк поверх Svelte для полноценных приложений с маршрутизацией, серверным рендерингом и работой с данными. С выходом Svelte 5 модель реактивности заметно изменилась: появились руны — явные примитивы состояния, производных значений и эффектов. Многие руководства в интернете до сих пор описывают старый синтаксис. Ниже — как устроен современный Svelte, чем он отличается от React, как строятся приложения на SvelteKit и, главное, когда выбор Svelte оправдан для реального продукта, а когда нет. Чем Svelte отличается от React Компиляция. React поставляет в браузер библиотеку и сравнивает виртуальное дерево элементов при каждом обновлении. Svelte на этапе сборки знает, какие части разметки зависят от какого состояния, и генерирует код, который обновляет только их. Синтаксис. Компонент Svelte — файл с разметкой, скриптом и стилями, близкий к обычному HTML. В React интерфейс описывается JSX внутри JavaScript. Стили. Стили в компоненте Svelte по умолчанию изолированы и применяются только к его разметке — без дополнительных библиотек. Реактивность. В Svelte 5 состояние объявляется рунами, и обновление значения само приводит к обновлению интерфейса, без функций-сеттеров и массивов зависимостей. Экосистема. У React кратно больше готовых библиотек, компонентов, специалистов на рынке и примеров решений. Это главный аргумент против Svelte в коммерческой разработке. Компонент и реактивность в Svelte 5 Состояние: $state Руна $state объявляет реактивное состояние. Изменение значения обновляет всё, что от него зависит. script let count = $state(0); /script button onclick={() = count++} Нажатий: {count} /button Обратите внимание на обработчик события: в Svelte 5 это обычный атрибут onclick , а не директива on:click , как в предыдущих версиях. Для объектов и массивов $state создаёт глубоко реактивный прокси: изменение вложенного свойства или добавление элемента в массив тоже обновляет интерфейс. script let todos = $state([]); function add(text) { todos.push({ text, done: false }); } /script Производные значения: $derived Значение, вычисляемое из состояния, объявляется через $derived и пересчитывается только при изменении зависимостей. script let price = $state(1200); let quantity = $state(3); let total = $derived(price * quantity); /script p Итого: {total} ₽ /p Для сложных вычислений есть $derived.by , принимающая функцию. Побочные эффекты: $effect Код, который должен выполняться при изменении состояния и взаимодействовать с внешним миром — логирование, работа с DOM напрямую, подписки, — помещается в $effect . Зависимости определяются автоматически по тому, какое состояние читается внутри. script let title = $state('Черновик'); $effect(() = { document.title = title; }); /script Хорошее правило: если значение можно вычислить из состояния, используйте $derived , а не $effect с присваиванием. Эффекты — для синхронизации с внешними системами. Свойства компонента: $props script let { name, role = 'участник', onselect } = $props(); /script button onclick={() = onselect(name)} {name} — {role} /button Вместо создания собственных событий через диспетчер, как в прежних версиях, компонент принимает функции обратного вызова в свойствах — так же, как это принято в React. Двусторонняя привязка script let email = $state(''); let agreed = $state(false); /script input type="email" bind:value={email} / label input type="checkbox" bind:checked={agreed} / Согласен /label Условия, списки и асинхронные данные {#if user} p Здравствуйте, {user.name} /p {:else} a href="/login" Войти /a {/if} ul {#each todos as todo (todo.id)} li class:done={todo.done} {todo.text} /li {:else} li Задач пока нет /li {/each} /ul {#await loadProfile()} p Загрузка… /p {:then profile} p {profile.company} /p {:catch error} p Не удалось загрузить: {error.message} /p {/await} Сниппеты вместо слотов Для передачи фрагментов разметки в компонент Svelte 5 использует сниппеты — именованные блоки разметки, которые можно передать как свойство и отрисовать через {@render} . !-- Card.svelte -- script let { header, children } = $props(); /script article {@render header()} {@render children()} /article Анимации и переходы Встроенные переходы — одно из преимуществ Svelte: появление и исчезновение элементов анимируется директивами без дополнительных библиотек. script import { fade, fly } from 'svelte/transition'; let open = $state(false); /script {#if open} div in:fly={{ y: 20 }} out:fade Панель /div {/if} Общее состояние Руны работают не только внутри компонентов, но и в файлах с расширением .svelte.js или .svelte.ts . Это позволяет выносить общее состояние в модули без отдельной библиотеки управления состоянием. // cart.svelte.js export const cart = $state({ items: [] }); export function addToCart(product) { cart.items.push(product); } Важная деталь: экспортировать нужно объект и менять его свойства, а не переприсваивать экспортированную переменную — иначе импортирующие модули не увидят изменения. Хранилища из svelte/store по-прежнему поддерживаются и полезны для совместимости с существующим кодом и потоковых данных. SvelteKit: приложения целиком Маршрутизация на файлах Структура каталога src/routes определяет адреса приложения. Страница — файл +page.svelte , параметры маршрута — имена каталогов в квадратных скобках. src/routes/ ├── +layout.svelte общий макет ├── +page.svelte / ├── cases/ │ ├── +page.svelte /cases │ └── [slug]/ │ ├── +page.svelte /cases/любой-адрес │ └── +page.server.js загрузка данных на сервере └── api/ └── leads/ └── +server.js API-обработчик Загрузка данных Функция load в файле +page.server.js выполняется на сервере и передаёт данные странице. Здесь можно обращаться к базе данных и секретам, не опасаясь, что они попадут в браузер. // src/routes/cases/[slug]/+page.server.js import { error } from '@sveltejs/kit'; import { getCase } from '$lib/server/cases'; export async function load({ params }) { const item = await getCase(params.slug); if (!item) error(404, 'Кейс не найден'); return { item }; } !-- +page.svelte -- script let { data } = $props(); /script h1 {data.item.title} /h1 Формы и серверные действия Действия формы обрабатывают отправку на сервере и работают даже без JavaScript в браузере, а при наличии JavaScript — без перезагрузки страницы. // +page.server.js import { fail } from '@sveltejs/kit'; export const actions = { default: async ({ request }) = { const form = await request.formData(); const email = form.get('email'); if (!email) return fail(400, { missing: true }); await saveLead({ email }); return { success: true }; } }; Режимы рендеринга Для каждого маршрута можно выбрать серверный рендеринг, предварительную генерацию статического HTML при сборке или работу только в браузере. Это важно для SEO: публичные страницы получают готовый HTML, а личный кабинет может работать как одностраничное приложение. Почему это критично для поисковых систем, мы разбирали в статье об индексации сайтов на JavaScript . Адаптеры развёртывания SvelteKit собирает приложение под целевую платформу через адаптеры: Node.js-сервер, статический хостинг, различные облачные платформы. Для российских проектов с требованиями к размещению данных обычно используют Node.js-адаптер на собственном сервере или у облачного провайдера в России, либо статическую сборку. Производительность: без мифов Отсутствие виртуального DOM и компактный скомпилированный код дают Svelte хорошие характеристики на старте: меньше JavaScript нужно загрузить и выполнить до появления интерактивности. Особенно это заметно на небольших и средних приложениях и на слабых мобильных устройствах. При этом конкретный выигрыш зависит от приложения, и универсальных процентов не существует. На больших приложениях размер кода определяется в первую очередь самим приложением и библиотеками, а не фреймворком. Главные источники медленной работы — тяжёлые зависимости, неоптимизированные изображения, лишние запросы и отсутствие разделения кода — одинаково опасны в любом фреймворке. Общие приёмы ускорения мы собрали в статье о performance optimization . Переход со Svelte 4 на Svelte 5 Svelte 5 поддерживает компоненты в старом синтаксисе, поэтому миграция может быть постепенной. Официальный инструмент миграции автоматически переписывает большую часть кода на руны. Основные замены: let с реактивностью → $state , $: для вычислений → $derived , $: для эффектов → $effect , export let → $props , on:click → onclick , диспетчер событий → функции в свойствах, слоты → сниппеты. Сторонние библиотеки компонентов нужно проверить на совместимость. Когда выбирать Svelte Svelte подходит, если важны скорость загрузки и компактность — контентные сайты, лендинги, виджеты, встраиваемые компоненты; команда небольшая и ценит простой синтаксис и меньший объём шаблонного кода; проект не зависит от большого числа специализированных библиотек интерфейса; команда уже знает Svelte или готова инвестировать в него. React или Vue предпочтительнее, если планируется рост команды и важна скорость найма; продукт опирается на готовые сложные компоненты — таблицы данных, редакторы, диаграммы, дизайн-системы; есть мобильное приложение на React Native и хочется переиспользовать знания и часть логики; продукт будет долго поддерживаться разными командами и подрядчиками. Рынок специалистов и экосистема для многих коммерческих проектов перевешивают технические преимущества. Общий подход к выбору технологий — в статье о выборе стека для стартапа . Фронтенд на React, Next.js и других фреймворках мы разрабатываем в рамках разработки веб-проектов и помогаем выбрать технологию под задачу продукта. Частые вопросы Svelte — это библиотека или фреймворк? Svelte — компилятор компонентов интерфейса. SvelteKit — фреймворк приложений поверх него с маршрутизацией, загрузкой данных, серверным рендерингом и сборкой под разные платформы. Сравнивать корректно Svelte с React, а SvelteKit — с Next.js. Нужен ли SvelteKit для каждого проекта на Svelte? Для приложений и сайтов с несколькими страницами — практически всегда да, он решает маршрутизацию, рендеринг и сборку. Сам Svelte без SvelteKit уместен для встраиваемых виджетов и отдельных компонентов в существующих проектах. Можно ли использовать TypeScript? Да, TypeScript поддерживается в компонентах и в SvelteKit из коробки, включая типизацию свойств и данных, возвращаемых функциями загрузки. Подходит ли Svelte для SEO? Да, при использовании SvelteKit с серверным рендерингом или предварительной генерацией страниц. Сам по себе фреймворк не влияет на SEO — влияет то, получает ли поисковый робот готовый HTML. Стоит ли переписывать приложение с React на Svelte? Как правило, нет, если текущее приложение работает и развивается. Переписывание — дорогой проект, и выигрыш в размере кода редко его окупает. Svelte разумнее рассматривать для новых продуктов и отдельных частей, где его преимущества важны. Актуальны ли руководства по Svelte 3 и 4? Частично. Общие идеи — компиляция, изолированные стили, переходы, SvelteKit — остались, но синтаксис реактивности, свойств, событий и слотов в Svelte 5 изменился. Для нового кода ориентируйтесь на документацию Svelte 5.