Перейти к содержимому
GitHub

Переход на Svelte 5

Версия 5 включает в себя обновлённый синтаксис и систему реактивности. Хотя на первый взгляд это может показаться непривычным, вы вскоре заметите множество схожестей. В этом руководстве подробно рассматриваются изменения и объясняется, как выполнить обновление. Также мы предоставляем информацию о том, почему были внесены эти изменения.

Вам не обязательно сразу переходить на новый синтаксис — Svelte 5 по-прежнему поддерживает старый синтаксис Svelte 4, и вы можете комбинировать компоненты, используя новый синтаксис с компонентами старого синтаксиса и наоборот. Мы ожидаем, что многие пользователи смогут обновиться, изменив всего несколько строк кода. Кроме того, существует скрипт миграции, который автоматически поможет вам со многими из этих шагов.

Изменения в синтаксисе реактивности

В основе Svelte 5 лежит новый API рун. Руны представляют собой инструкции компилятора, которые сообщают Svelte о реактивности. Синтаксически руны выглядят как функции, начинающиеся с символа доллара.

let -> $state

В Svelte 4 объявление переменной с помощью let на верхнем уровне компонента автоматически становилось реактивным. В Svelte 5 этот процесс стал более явным: переменная становится реактивной только при создании с использованием руны $state. Давайте преобразуем счётчик в режим рун, обернув его в $state:

<script>
-  let count = 0;
+  let count = $state(0);
</script>

Ничего больше не меняется. count по-прежнему является самим числом, и вы читаете и записываете его напрямую, без обёртки, такой как .value или getCount().

$: -> $derived/$effect

В Svelte 4 оператор $: на верхнем уровне компонента использовался для объявления производного состояния, то есть состояния, которое полностью определяется через вычисление других состояний. В Svelte 5 это достигается с помощью руны $derived:

<script>
-  let count = 0;
-  $: double = count * 2;
+  let count = $state(0);
+  const double = $derived(count * 2);
</script>

Как и в случае со $state, ничего больше не меняется. double по-прежнему является самим числом, и вы читаете его напрямую, без обёртки, такой как .value или getDouble().

Оператор $: также можно было использовать для создания побочных эффектов. В Svelte 5 это достигается с помощью руны $effect:

<script>
-  let count = 0;
-  $: {
+  let count = $state(0);
+  $effect(() => {
    if (count > 5) {
      alert('Count is too high!');
    }
-  }
+  });
</script>

ОБратите внимание, что логика выполнения $effect отличается от логики выполнения $:.

export let -> $props

В Svelte 4 свойства компонента объявлялись с помощью export let. Каждое свойство представляло собой отдельное объявление. В Svelte 5 все свойства объявляются через руну $props с использованием деструктуризации:

<script>
-  export let optional = 'unset';
-  export let required;
+  let { optional = 'unset', required } = $props();
</script>

Существуют несколько случаев, когда объявление свойств становится менее очевидным, чем использование нескольких объявлений export let:

  • вы хотите переименовать свойство, например, потому что имя является зарезервированным идентификатором (например, class)
  • вы не знаете заранее, какие другие свойства ожидать
  • вы хотите передать каждое свойство другому компоненту

Все эти случаи требуют специального синтаксиса в Svelte 4:

  • переименование: export { klass as class }
  • другие свойства: $restProps
  • все свойства: $props

В Svelte 5 руна $props упрощает этот процесс без необходимости в дополнительном синтаксисе, специфичном для Svelte:

  • переименование: используйте переименование свойств let { class: klass } = $props();
  • другие свойства: используйте распаковку let { foo, bar, ...rest } = $props();
  • все свойства: не выполняйте деструктуризацию let props = $props();
<script>
-  let klass = '';
-  export { klass as class};
+  let { class: klass, ...rest } = $props();
</script>
-<button class={klass} {...$$restProps}>нажмите меня</button>
+<button class={klass} {...rest}>нажмите меня</button>

Изменения в событиях

Обработчики событий получили обновление в Svelte 5. В то время как в Svelte 4 мы использовали директиву on: для привязки обработчика событий к элементу, в Svelte 5 они стали свойствами, как и любые другие (другими словами — уберите двоеточие):

<script>
  let count = $state(0);
</script>

-<button on:click={() => count++}>
+<button onclick={() => count++}>
  нажатий: {count}
</button>

Поскольку это просто свойства, вы можете использовать обычный сокращённый синтаксис…

<script>
  let count = $state(0);

  function onclick() {
    count++;
  }
</script>

<button {onclick}>
  нажатий: {count}
</button>

…хотя при использовании функции обработчика событий с именем обычно лучше использовать более описательное название.

События компонентов

В Svelte 4 компоненты могли генерировать события, создавая диспетчер с помощью createEventDispatcher.

Эта функция устарела в Svelte 5. Вместо этого компоненты должны принимать колбэк-пропсы, что означает, что вы передаете функции в качестве свойств этим компонентам:

// App.svelte
<script>
  import Pump from './Pump.svelte';

  let size = $state(15);
  let burst = $state(false);

  function reset() {
    size = 15;
    burst = false;
  }
</script>

<Pump
  on:inflate={(power) => {
    size += power.detail;
    if (size > 75) burst = true;
  }}
  on:deflate={(power) => {
    if (size > 0) size -= power.detail;
  }}
/>

{#if burst}
  <button onclick={reset}>новый шарик</button>
  <span class="boom">💥</span>
{:else}
  <span class="balloon" style="scale: {0.01 * size}">
    🎈
  </span>
{/if}
// Pump.svelte
<script>
-  import { createEventDispatcher } from 'svelte';
-  const dispatch = createEventDispatcher();
+  let { inflate, deflate } = $props();
  let power = $state(5);
</script>

-<button onclick={() => dispatch('inflate', power)}>
+<button onclick={() => inflate(power)}>
  надувать
</button>
-<button onclick={() => dispatch('deflate', power)}>
+<button onclick={() => deflate(power)}>
  сдувать
</button>
<button onclick={() => power--}>-</button>
Мощность насоса: {power}
<button onclick={() => power++}>+</button>

Всплывающие события

Вместо того чтобы использовать <button on:click> для «перенаправления» события от элемента к компоненту, компонент должен принимать колбэк-пропс onclick:

<script>
+  let { onclick } = $props();
</script>

-<button on:click>
+<button {onclick}>
  нажми меня
</button>

Обратите внимание, что это также означает, что вы можете «распространять» обработчики событий на элемент вместе с другими пропсами, вместо того чтобы утомительно перенаправлять каждое событие отдельно:

<script>
  let props = $props();
</script>

-<button {...$$props} on:click on:keydown on:all_the_other_stuff>
+<button {...props}>
  нажми меня
</button>

Модификаторы событий

В Svelte 4 вы можете добавлять модификаторы событий к обработчикам:

<button on:click|once|preventDefault={handler}>...</button>

Модификаторы специфичны для on: и, таким образом, не работают с современными обработчиками событий. Добавление таких вещей, как event.preventDefault(), непосредственно внутри самого обработчика предпочтительнее, поскольку вся логика находится в одном месте, а не разделена между обработчиком и модификаторами.

Поскольку обработчики событий — это просто функции, вы можете создавать свои собственные обёртки по мере необходимости:

<script>
  function once(fn) {
    return function (event) {
      if (fn) fn.call(this, event);
      fn = null;
    };
  }

  function preventDefault(fn) {
    return function (event) {
      event.preventDefault();
      fn.call(this, event);
    };
  }
</script>

<button onclick={once(preventDefault(handler))}>...</button>

Существуют три модификатора — capture, passive и nonpassive — которые не могут быть выражены в виде обёрток, поскольку их необходимо применять в момент привязки обработчика события, а не во время его выполнения.

Для capture мы добавляем модификатор к имени события:

<button onclickcapture={...}>...</button>

Изменение опции passive для обработчика события не следует воспринимать легкомысленно. Если у вас действительно есть необходимость в этом — хотя, скорее всего, её нет! — вам потребуется использовать действие, чтобы самостоятельно применить обработчик события.

Несколько обработчиков событий

В Svelte 4 такой вариант возможен:

<button on:click={one} on:click={two}>...</button>

Дублирование атрибутов/свойств на элементах — включая обработчики событий — не допускается. Вместо этого сделайте следующее:

<button
  onclick={(e) => {
    one(e);
    two(e);
  }}
>
  ...
</button>

При распаковке («spreading») свойств локальные обработчики событий должны располагаться после распаковки, иначе они могут быть перезаписаны:

<button
  {...props}
  onclick={(e) => {
    doStuff(e);
    props.onclick?.(e);
  }}
>
  ...
</button>

Сниппеты вместо слотов

В Svelte 4 контент можно передавать компонентам с помощью слотов. В Svelte 5 слоты заменяются сниппетами, которые являются более мощными и гибкими, и поэтому слоты устарели в Svelte 5.

Тем не менее, слоты продолжают работать, и вы можете передавать сниппеты в компонент, который использует слоты:

// Child.svelte
<slot />
<hr />
<slot name="foo" message="hello" />
// Parent.svelte
<script>
  import Child from './Child.svelte';
</script>

<Child>
  Контент по умолчанию

  {#snippet foo({ message })}
    сообщение из дочернего элемента: {message}
  {/snippet}
</Child>

(Обратное неверно — вы не можете передавать контент слотов в компонент, который использует теги {@render ...}.)

При использовании пользовательских элементов вы по-прежнему должны использовать <slot />, как и раньше. В будущих версиях, когда Svelte уберет свою внутреннюю версию слотов, эти слоты останутся без изменений, т. е. будут выводить обычный DOM-тег вместо преобразования.

Контент по умолчанию

В Svelte 4 самым простым способом передать элемент интерфейса дочернему компоненту было использование <slot />. В Svelte 5 это делается с помощью свойства children, которое затем отображается с помощью {@render children()}:

<script>
+  let { children } = $props();
</script>

-<slot />
+{@render children?.()}

Множественные заполнители контента

Если вам нужно было несколько заполнителей интерфейса, вы должны были использовать именованные слоты. В Svelte 5 вместо этого используются пропсы — называйте их как угодно и отображайте с помощью {@render ...}:

<script>
+  let { header, main, footer } = $props();
</script>

<header>
-  <slot name="header" />
+  {@render header()}
</header>

<main>
-  <slot name="main" />
+  {@render main()}
</main>

<footer>
-  <slot name="footer" />
+  {@render footer()}
</footer>

Передача данных обратно

В Svelte 4 вы передавали данные в <slot />, а затем извлекали их с помощью let: в родительском компоненте. В Svelte 5 эту ответственность берут на себя сниппеты:

// App.svelte
<script>
  import List from './List.svelte';
</script>

<List items={['one', 'two', 'three']} let:item>
+  {#snippet item(text)}
    <span>{text}</span>
+  {/snippet}
-   <span slot="empty">Элементов пока нет</span>
+  {#snippet empty()}
+   <span>Элементов пока нет</span>
+  {/snippet}
</List>
// List.svelte
<script>
  let { items, item, empty } = $props();
</script>

{#if items.length}
  <ul>
    {#each items as entry}
      <li>
-        <slot item={entry} />
+        {@render item(entry)}
      </li>
    {/each}
  </ul>
{:else}
-   <slot name="empty" />
+   {@render empty?.()}
{/if}

Скрипт миграции

Теперь у вас должно быть достаточно ясное представление о различиях между старым и новым синтаксисом, а также о том, как они взаимосвязаны. Вероятно, вы также заметили, что многие из этих миграций являются техническими и повторяющимися, и выполнять их вручную не очень удобно.

Мы пришли к такому же выводу, поэтому разработали скрипт миграции, который автоматически выполнит большую часть работы. Вы можете обновить свой проект, выполнив команду npx sv migrate svelte-5. Этот процесс включает в себя следующие шаги:

  • обновление основных зависимостей в вашем package.json
  • миграция на руны (let -> $state и т. д.)
  • преобразование атрибутов событий для DOM-элементов (on:click -> onclick)
  • изменение создания слотов на теги рендеринга (<slot /> -> {@render children()})
  • преобразование использования слотов в сниппеты (<div slot="x">...</div> -> {#snippet x()}<div>...</div>{/snippet})
  • преобразование создания компонентов (new Component(...) -> mount(Component, ...))

Кроме того, вы можете мигрировать отдельный компонент в VS Code с помощью команды Migrate Component to Svelte 5 Syntax или воспользоваться кнопкой Migrate в нашей песочнице.

Не все миграции могут быть выполнены автоматически, и некоторые из них потребуют ручной доработки. В следующих разделах мы подробно рассмотрим эти моменты.

run

Вы можете заметить, что скрипт миграции преобразует некоторые ваши операторы $: в функцию run, которая импортируется из svelte/legacy. Это происходит, если скрипт миграции не смог надежно преобразовать оператор в $derived и пришел к выводу, что это побочный эффект. В некоторых случаях это может быть ошибкой, и лучше изменить это на использование $derived. В других случаях это может быть верно, но поскольку операторы $: также выполнялись на сервере, а $effect — нет, безопасно преобразовать это таким образом нельзя. Вместо этого используется run как временное решение. run имитирует большинство характеристик $:, так как он выполняется на сервере один раз и работает как $effect.pre на клиенте ($effect.pre выполняется до применения изменений к DOM; скорее всего, вы захотите использовать $effect вместо этого).

<script>
-  import { run } from 'svelte/legacy';
-  run(() => {
+  $effect(() => {
    // код с побочными эффектами
  })
</script>

Модификаторы событий

Модификаторы событий не применимы к атрибутам событий (например, вы не можете использовать onclick|preventDefault={...}). Поэтому при миграции директив событий в атрибуты событий нам нужна функция-замена для этих модификаторов. Эти функции импортируются из svelte/legacy и их следует заменить, например, на использование event.preventDefault().

<script>
-  import { preventDefault } from 'svelte/legacy';
</script>

<button
- onclick={preventDefault((event) => {
+ onclick={((event) => {
+   event.preventDefault();
    // ...
  })}
>
  нажми меня
</button>

Вещи, которые не автоматизируются

Скрипт миграции не преобразует createEventDispatcher, и вам нужно будет вручную внести изменения в этих частях кода. Это решение связано с высоким риском, так как может привести к сбоям у пользователей компонента, что скрипт миграции не может предсказать.

Также скрипт миграции не обрабатывает beforeUpdate/afterUpdate, поскольку невозможно точно определить намерения кода. В качестве общего правила вы можете использовать комбинацию $effect.pre (которая выполняется одновременно с beforeUpdate) и tick (импортируется из svelte, позволяет дождаться применения изменений к DOM перед выполнением дальнейших действий).

Компоненты больше не являются классами

В Svelte 3 и 4 компоненты представляют собой классы. В Svelte 5 они становятся функциями и должны быть инстанцированы иначе. Если вам нужно вручную инстанцировать компоненты, используйте mount или hydrate (импортируется из svelte). Если вы видите эту ошибку при использовании SvelteKit, сначала попробуйте обновиться до последней версии SvelteKit, которая добавляет поддержку Svelte 5. Если вы используете Svelte без SvelteKit, вам, вероятно, нужно будет отредактировать файл main.js (или аналогичный):

+import { mount } from 'svelte';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app") });
+const app = mount(App, { target: document.getElementById("app") });

export default app;

mount и hydrate имеют абсолютно одинаковый API. Разница заключается в том, что hydrate будет захватывать HTML, сгенерированный сервером Svelte, внутри своей цели и выполнять его гидратацию. Оба метода возвращают объект с экспортами компонента и потенциальными доступами к свойствам (если скомпилировано с accessors: true). Они не включают методы $on, $set и $destroy, которые вы могли знать из API классовых компонентов. Вот их замены:

Для $on вместо прослушивания событий передавайте их через свойство events в аргументе опций.

+import { mount } from 'svelte';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app") });
-app.$on('event', callback);
+const app = mount(App, { target: document.getElementById("app"), events: { event: callback } });
+import { mount } from 'svelte';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
-app.$set({ foo: 'baz' });
+const props = $state({ foo: 'bar' });
+const app = mount(App, { target: document.getElementById("app"), props });
+props.foo = 'baz';

Вместо $destroy используйте unmount:

+import { mount, unmount } from 'svelte';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
-app.$destroy();
+const app = mount(App, { target: document.getElementById("app") });
+unmount(app);

В качестве временного решения вы также можете использовать createClassComponent или asClassComponent (импортированные из svelte/legacy), чтобы сохранить тот же API, который был известен в Svelte 4, после инстанцирования:

+import { createClassComponent } from 'svelte/legacy';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app") });
+const app = createClassComponent({ component: App, target: document.getElementById("app") });

export default app;

Если этот компонент не находится под вашим контролем, вы можете использовать опцию компилятора compatibility.componentApi для автоматического применения обратной совместимости, что означает, что код, использующий new Component(...), будет продолжать работать без изменений (обратите внимание, что это добавляет небольшие накладные расходы для каждого компонента). Это также добавит методы $set и $on для всех экземпляров компонентов, которые вы получаете через bind:this.

// svelte.config.js
export default {
  compilerOptions: {
    compatibility: {
      componentApi: 4,
    },
  },
};

Обратите внимание, что mount и hydrate не являются синхронными, поэтому такие вещи, как onMount, не будут вызваны к моменту возврата функции, и ожидаемый блок промисов ещё не будет отрендерен (поскольку #await ждет микрозадачу для ожидания потенциально немедленно разрешённого промиса). Если вам нужна такая гарантия, вызовите flushSync (с помощью импорта из 'svelte') после вызова mount/hydrate.

Изменения в API сервера

Аналогично, компоненты больше не имеют метода render, когда они компилируются для серверного рендеринга. Вместо этого передайте функцию в render из svelte/server:

+import { render } from 'svelte/server';
import App from './App.svelte';

-const { html, head } = App.render({ props: { message: 'hello' }});
+const { html, head } = render(App, { props: { message: 'hello' }});

В Svelte 4 рендеринг компонента в строку также возвращал CSS всех компонентов. В Svelte 5 это больше не происходит по умолчанию, поскольку в большинстве случаев вы используете цепочку инструментов, которая обрабатывает это другими способами (например, SvelteKit). Если вам нужно, чтобы CSS возвращался из render, вы можете установить опцию компилятора css в значение 'injected', и это добавит элементы <style> в <head>.

Изменения в типизации компонентов

Переход от классов к функциям также отражен в типах: SvelteComponent, базовый класс из Svelte 4, устарел в пользу нового типа Component, который определяет форму функции компонента Svelte. Чтобы вручную определить форму компонента в файле d.ts:

import type { Component } from 'svelte';
export declare const MyComponent: Component<{
  foo: string;
}>;

Чтобы объявить, что компонент определённого типа является обязательным:

import { ComponentA, ComponentB } from 'component-library';
-import type { SvelteComponent } from 'svelte';
+import type { Component } from 'svelte';

-let C: typeof SvelteComponent<{ foo: string }> = $state(
+let C: Component<{ foo: string }> = $state(
  Math.random() ? ComponentA : ComponentB
);

Два утилитарных типа ComponentEvents и ComponentType также устарели. ComponentEvents стал ненужным, поскольку события теперь определяются как свойства колбэков, а ComponentType устарел, потому что новый тип Component уже является типом компонента (т. е. ComponentType<SvelteComponent<{ prop: string }>> эквивалентен Component<{ prop: string }>).

Изменения в bind:this

Поскольку компоненты больше не являются классами, использование bind:this больше не возвращает экземпляр класса с методами $set, $on и $destroy. Он возвращает только экспортируемые экземпляры (export function/const) и, если вы используете опцию accessors, пару геттер/сеттер для каждого свойства.

<svelte:component> больше не нужен

В Svelte 4 компоненты являются статическими — если вы рендерите <Thing>, и значение Thing изменяется, ничего не происходит. Чтобы сделать его динамическим, вам приходилось использовать <svelte:component>.

В Svelte 5 это больше не так:

<script>
  import A from './A.svelte';
  import B from './B.svelte';

  let Thing = $state();
</script>

<select bind:value={Thing}>
  <option value={A}>A</option>
  <option value={B}>B</option>
</select>

<!-- эти две строки эквивалентны -->
<Thing />
<svelte:component this={Thing} />

При миграции имейте в виду, что имя вашего компонента должно быть с заглавной буквы (Thing), чтобы отличать его от элементов, если не используется нотация с точками.

Нотация с точками указывает на компонент

В Svelte 4 <foo.bar> создавал элемент с именем тега "foo.bar". В Svelte 5 foo.bar рассматривается как компонент. Это особенно полезно внутри блоков each:

{#each items as item}
  <item.component {...item.props} />
{/each}

Обработка пробелов изменилась

Ранее Svelte использовал очень сложный алгоритм для определения, следует ли сохранять пробелы или нет. Svelte 5 упрощает это, что делает его более понятным для разработчиков. Правила следующие:

  • Пробелы между узлами сжимаются до одного пробела
  • Пробелы в начале и в конце тега полностью удаляются
  • Применяются определённые исключения, такие как сохранение пробелов внутри тегов pre

Как и прежде, вы можете отключить обрезку пробелов, установив опцию preserveWhitespace в настройках компилятора или на уровне отдельного компонента в <svelte:options>.

Требуется современный браузер

Svelte 5 требует современный браузер (другими словами, не Internet Explorer) по нескольким причинам:

  • он использует Proxies
  • элементы с привязками clientWidth/clientHeight/offsetWidth/offsetHeight используют ResizeObserver вместо запутанного хакa с <iframe>
  • <input type="range" bind:value={...} /> использует только слушатель события input, а не слушает события change в качестве резервного варианта

Опция компилятора legacy, которая генерировала более громоздкий, но совместимый с IE код, больше не существует.

Изменения в опциях компилятора

  • Значения false/true (уже устаревшие ранее) и "none" были удалены как допустимые значения для опции css
  • Опция legacy была переработана
  • Опция hydratable была удалена. Компоненты Svelte теперь всегда являются гидратируемыми
  • Опция enableSourcemap была удалена. Карты источников теперь всегда генерируются, инструменты могут выбирать игнорировать их
  • Опция tag была удалена. Вместо этого используйте <svelte:options customElement="tag-name" /> внутри компонента
  • Опции loopGuardTimeout, format, sveltePath, errorMode и varsReport были удалены

Свойство children зарезервировано

Содержимое внутри тегов компонента становится сниппетом с именем children. Вы не можете иметь отдельное свойство с таким именем.

Критические изменения в режиме рун

Некоторые изменения, ломающие обратную совместимость, применяются только тогда, когда ваш компонент находится в режиме рун.

Привязки к экспортам компонента не допускаются

Экспорты из компонентов в режиме рун не могут быть привязаны напрямую. Например, если в компоненте A есть export const foo = ..., то использование <A bind:foo /> вызовет ошибку. Вместо этого используйте bind:this<A bind:this={a} /> — и обращайтесь к экспорту как a.foo. Это изменение упрощает понимание, так как оно обеспечивает чёткое разделение между свойствами и экспортами.

Привязки должны быть явно определены с помощью $bindable()

В синтаксисе Svelte 4 каждое свойство (объявленное с помощью export let) является привязываемым, что означает, что вы можете использовать bind: для него. В режиме рун свойства по умолчанию не являются привязываемыми: вам нужно обозначить привязываемые свойства с помощью руны $bindable.

Если привязываемое свойство имеет значение по умолчанию (например, let { foo = $bindable('bar') } = $props();), вам нужно передать ненулевое значение этому свойству, если вы привязываетесь к нему. Это предотвращает неоднозначное поведение — родитель и потомок должны иметь одинаковое значение — и приводит к лучшей производительности (в Svelte 4 значение по умолчанию отражалось обратно в родительский компонент, что приводило к ненужным дополнительным циклам рендеринга).

Опция accessors игнорируется

Установка опции accessors в значение true делает свойства доступными напрямую через экземпляр компонента.

<svelte:options accessors={true} />
<script>
  // доступно через componentInstance.name
  export let name;
</script>

В режиме рун свойства никогда не доступны через экземпляр компонента. Если нужно сделать их доступными, используйте экспорт компонентов.

<script>
  let { name } = $props();
  // доступно через componentInstance.getName()
  export const getName = () => name;
  </script>

Другой вариант — если у вас есть контроль над местом их создания, можно применять руны в файлах .js/.ts, изменив их расширение на .svelte, например .svelte.js или .svelte.ts, а затем использовать $state:

+import { mount } from 'svelte';
import App from './App.svelte'

-const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
-app.foo = 'baz'
+const props = $state({ foo: 'bar' });
+const app = mount(App, { target: document.getElementById("app"), props });
+props.foo = 'baz';

Опция immutable игнорируется

Установка опции immutable не имеет эффекта в режиме рун. Эта концепция заменена тем, как работают $state и его вариации.

Классы больше не являются «авто-реактивными»

В Svelte 4 выполнение следующего кода вызывало реактивность:

<script>
  let foo = new Foo();
</script>

<button on:click={() => (foo.value = 1)}>{foo.value}</button
>

Это связано с тем, что компилятор Svelte рассматривал присвоение foo.value как инструкцию для обновления всего, что ссылается на foo. В Svelte 5 реактивность определяется во время выполнения, а не на этапе компиляции, поэтому вы должны определить value как реактивное поле $state в классе Foo. Оборачивание new Foo() в $state(...) не даст эффекта — только обычные объекты и массивы становятся глубоко реактивными.

События touch и wheel являются пассивными

При использовании атрибутов событий onwheel, onmousewheel, ontouchstart и ontouchmove обработчики являются пассивными, чтобы соответствовать настройкам браузера по умолчанию. Это значительно улучшает отзывчивость, позволяя браузеру немедленно прокручивать документ, а не дожидаться, вызовет ли обработчик события event.preventDefault().

В очень редких случаях, когда вам нужно предотвратить эти значения по умолчанию для событий, вы должны использовать on вместо этого (например, внутри действия).

Синтаксис атрибутов/свойств стал более строгим

В Svelte 4 сложные значения атрибутов не обязательно заключать в кавычки:

<Component prop=this{is}valid />

Это может привести к ошибкам. В режиме рун, если вы хотите объединить значения, вы должны заключить их в кавычки:

<Component prop="this{is}valid" />

Обратите внимание, что Svelte 5 также будет предупреждать, если у вас есть одно выражение, заключённое в кавычки, например answer="{42}" — в Svelte 6 это приведет к преобразованию значения в строку, а не к передаче его как числа.

Структура HTML стала более строгой

В Svelte 4 вам разрешалось писать HTML-код, который браузер исправлял при серверной рендеринге. Например, вы могли написать это…

<table>
  <tr>
    <td>привет</td>
  </tr>
</table>

… и браузер автоматически вставлял элемент <tbody>:

<table>
  <tbody>
    <tr>
      <td>hi</td>
    </tr>
  </tbody>
</table>

Svelte 5 более строг в отношении структуры HTML и будет выдавать ошибку компиляции в случаях, когда браузер исправлял бы DOM.

Другие критические изменения

Более строгая проверка присваивания @const

Присваивания к деструктурированным частям объявления @const больше не допускаются. Ошибкой было допускать это.

:is(…) и :where(…) имеют область видимости

Ранее Svelte не анализировал селекторы внутри :is(...) и :where(...), фактически рассматривая их как глобальные. Svelte 5 анализирует их в контексте текущего компонента. Таким образом, некоторые селекторы теперь могут рассматриваться как неиспользуемые, если они полагались на это поведение. Чтобы исправить это, используйте :global(...) внутри селекторов :is(...)/:where(...).

При использовании директивы @apply от Tailwind добавьте селектор :global, чтобы сохранить правила, использующие селекторы :is(...), сгенерированные Tailwind:

main:global {
  @apply bg-blue-100 dark:bg-blue-900;
}

Позиция хеша CSS больше не является предсказуемой

Ранее в Svelte хеш CSS всегда добавлялся в конец. В Svelte 5 это больше не гарантируется. Это может вызвать проблемы, если у вас очень необычные CSS-селекторы.

Локальные стили теперь используют :where(…)

Чтобы избежать проблем, связанных с непредсказуемыми изменениями специфичности, селекторы в локальных стилях теперь применяют модификаторы :where(.svelte-xyz123) вместе с .svelte-xyz123 (где xyz123 по-прежнему представляет собой хеш содержимого <style>). Вы можете узнать больше здесь.

В случае, если вам нужно поддерживать устаревшие браузеры, которые не реализуют :where, вы можете вручную изменить сгенерированный CSS, что приведет к непредсказуемым изменениям специфичности:

css = css.replace(/:where\((.+?)\)/, '$1');

Коды ошибок и предупреждений были переименованы

Коды ошибок и предупреждений были изменены. Ранее они использовали дефисы для разделения слов, теперь используются подчеркивания (например, foo-bar становится foo_bar). Кроме того, несколько кодов были слегка переформулированы.

Уменьшено количество пространств имён

Количество допустимых пространств имён, которые можно передать в опцию компилятора namespace, было сокращено до html (по умолчанию), mathml и svg.

Пространство имён foreign было полезно только для Svelte Native, поддержку которого мы планируем реализовать иначе в минорной версии 5.x.

Изменения в beforeUpdate/afterUpdate

beforeUpdate больше не выполняется дважды при первоначальном рендере, если он изменяет переменную, на которую ссылается шаблон.

Обработчики afterUpdate в родительском компоненте теперь будут выполняться после обработчиков afterUpdate в любых дочерних компонентах.

beforeUpdate/afterUpdate больше не выполняются, когда компонент содержит <slot>, и его содержимое обновляется.

Обе функции запрещены в режиме рун — вместо этого используйте $effect.pre(...) и $effect(...).

Изменение поведения contenteditable

Если у вас есть узел contenteditable с соответствующим связыванием и реактивным значением внутри него (например: <div contenteditable=true bind:textContent>count is {count}</div>), то значение внутри contenteditable не будет обновляться при изменениях в count, поскольку связывание полностью контролирует содержимое и должно обновляться только через него.

Атрибуты oneventname больше не принимают строковые значения

В Svelte 4 было возможно указывать атрибуты событий на HTML-элементах в виде строки:

<button onclick="alert('привет')">...</button>

Это не рекомендуется, и в Svelte 5 это больше не возможно, где свойства, такие как onclick, заменяют on:click в качестве механизма для добавления обработчиков событий.

null и undefined становятся пустой строкой

В Svelte 4 null и undefined выводились как соответствующие строки. В 99 из 100 случаев вы хотите, чтобы это стало пустой строкой, что также делает большинство других фреймворков. Поэтому в Svelte 5 null и undefined становятся пустой строкой.

Значения bind:files могут быть только null, undefined или FileList

bind:files теперь является двусторонним связыванием. Таким образом, при установке значения оно должно быть либо ложноподобным (null или undefined), либо типа FileList.

Связывания теперь реагируют на сбросы форм

Ранее связывания не учитывали событие reset форм, и, следовательно, значения могли выходить из синхронизации с DOM. Svelte 5 исправляет это, добавляя слушатель reset на документ и вызывая связывания при необходимости.

walk больше не экспортируется

svelte/compiler повторно экспортировал walk из estree-walker для удобства. В Svelte 5 это больше не актуально, импортируйте его напрямую из этого пакета, если он вам нужен.

Содержимое внутри svelte:options запрещено

В Svelte 4 вы могли иметь содержимое внутри тега <svelte:options />. Оно игнорировалось, но вы могли что-то там написать. В Svelte 5 содержимое внутри этого тега вызывает ошибку компилятора.

Элементы <slot> в декларативных теневых корнях сохраняются

В Svelte 4 тег <slot /> был заменен на собственную версию слотов во всех местах. В Svelte 5 они сохраняются в случае, если являются дочерними элементами тега <template shadowrootmode="...">.

Тег <svelte:element> должен быть выражением

В Svelte 4 код <svelte:element this="div"> является допустимым. Это имеет мало смысла — вам просто нужно использовать <div>. В крайне редком случае, если вам действительно нужно использовать литеральное значение по какой-то причине, вы можете сделать это:

<svelte:element this={"div"}>

Обратите внимание, что в Svelte 4 тег <svelte:element this="input"> (например) обрабатывался так же, как <input>, для определения применимых директив bind:. В Svelte 5 это больше не так.

mount по умолчанию воспроизводит переходы

Функция mount, используемая для рендеринга дерева компонентов, по умолчанию воспроизводит переходы, если опция intro не установлена в false. Это отличается от устаревших классовых компонентов, которые при ручной инициализации не воспроизводили переходы по умолчанию.

Несоответствия гидратации для <img src={...}> и {@html ...} не исправляются

В Svelte 4, если значение атрибута src или тега {@html ...} отличается между сервером и клиентом (так называемое несоответствие гидратации), оно исправляется. Это может быть очень затратным: установка атрибута src (даже если он оценивается в одно и то же значение) приводит к перезагрузке изображений и iframe, а повторная вставка большого блока HTML занимает много времени.

Поскольку такие несоответствия встречаются крайне редко, Svelte 5 предполагает, что значения остались неизменными, но в режиме разработки будет предупреждать вас, если это не так. Для принудительного обновления вы можете сделать что-то вроде этого:

<script>
  let { markup, src } = $props();

  if (typeof window !== 'undefined') {
    // сохраняем значения...
    const initial = { markup, src };

    // отменяем их...
    markup = src = undefined;

    $effect(() => {
      // ...и сбрасываем их после монтирования
      markup = initial.markup;
      src = initial.src;
    });
  }
</script>

{@html markup}
<img {src} />

Гидратация работает по-другому

В Svelte 5 во время серверного рендеринга используются комментарии, которые обеспечивают более надежную и эффективную гидратацию на клиенте. Поэтому не следует удалять комментарии из вашего HTML-вывода, если вы планируете его гидратировать. Если вы вручную создавали HTML для гидратации компонентом Svelte, вам нужно будет отредактировать этот HTML, чтобы добавить указанные комментарии в нужные места.

Атрибуты onevent делегированы

Атрибуты событий заменяют директивы событий: вместо on:click={handler} теперь пишется onclick={handler}. Для обеспечения обратной совместимости синтаксис on:event по-прежнему поддерживается и работает так же, как в Svelte 4. Однако некоторые атрибуты onevent делегированы, что означает, что вам нужно быть осторожными и не останавливать распространение событий вручную, так как они могут не дойти до слушателя для этого типа события на корневом уровне.

--style-props использует другой элемент

В Svelte 5 для обёртывания компонента при использовании пользовательских свойств CSS используется дополнительный элемент <svelte-css-wrapper> вместо <div>.