Guide de migration vers Svelte 5
La version 5 apporte une syntaxe et un système de réactivité revisités. Bien qu’il paraisse très différent au premier abord, vous remarquerez très vite des ressemblances. Ce guide passe en revue les changements de manière détaillée et vous aide à faire votre mise à jour. De plus, nous fournissons également quelques informations sur pourquoi nous avons fait ces changements.
Vous n’avez pas besoin de migrer vers la nouvelle syntaxe tout de suite — Svelte 5 continue de supporter la syntaxe de la version 4, et il est possible de mélanger des composants utilisant la nouvelle syntaxe avec des composants utilisant l’ancienne syntaxe. Nous pensons que beaucoup de gens seront capables de mettre à jour Svelte en changeant initialement uniquement quelques lignes de code. Nous proposons également un script de migration qui vous aide avec beaucoup de ces étapes automatiquement.
Nouvelle syntaxe de réactivité
La nouvelle API de runes est au coeur de Svelte 5. Les runes sont simplement des instructions pour
le compilateur qui informent Svelte à propos de la réactivité attendue. Syntactiquement, les runes
sont des fonctions dont le nom commence par un $
.
let -> $state
En Svelte 4, une déclaration let
à la racine d’un composant était implicitement réactive. En
Svelte 5 les choses sont plus explicites : une variable est réactive lorsqu’elle a créée avec la
rune $state
. Migrons le compteur ci-dessous en rune en l’entourant d’un $state
:
<script>
let count = $state(0);
</script>
Rien d’autre ne change. count
est toujours le nombre lui-même, et vous pouvez le lire ou le
modifier directement, sans aucun intermédiaire comme .value
ou getCount()
.
Pourquoi ce changement
Le fait qu’une déclaration
let
à la racine d’un composant soit implicitement réactive fonctionnait très bien, mais cela impliquait que la réactivité était contrainte — déclarer unlet
à un autre endroit ne le rendait pas réactif. Cela vous forçait à utiliser des stores lorsque vous souhaitiez sortir du code d’un composant pour le réutiliser ailleurs. Cela signifiait également que vous deviez apprendre un modèle de réactivité complètement distinct, et au final cela se révélait souvent moins simple. Puisque la réactivité est plus explicite avec Svelte 5, vous pouvez continuer d’utiliser la même API en dehors de la racine des composants. Allez sur le tutoriel pour en apprendre plus.
$: -> $derived/$effect
Avec Svelte 4, une déclaration $:
à la racine d’un composant pouvait être utilisée pour déclarer
une dérivation, c-à-d un état entièrement défini par un calcul utilisant un autre état. Avec Svelte
5, ceci s’écrit en utilisant la rune $derived
:
<script>
let count = $state(0);
$: const double = $derived(count * 2);
</script>
Comme avec $state
, rien d’autre ne change. double
est toujours le nombre lui-même, et vous
pouvez le lire directement, sans intermédiaire comme .value
ou getDouble()
.
Une déclaration $:
pouvait également être utilisée pour créer des effets de bord. Avec Svelte 5,
nous pouvons le faire en utilisant la rune $effect
:
<script>
let count = $state(0);
$:$effect(() => {
if (count > 5) {
alert('Le compteur est trop élevé');
}
});
</script>
Pourquoi ce changement
$:
était un raccourci simple et efficace pour débuter : vous pouviez placer un$:
devant la plupart de votre code, et cela fonctionnait. L’intuitivité était également un inconvénient lorsque votre code devenait de plus en plus complexe, parce que cela devenait difficile de raisonner sur les liens de réactivité entre les différents morceaux de code. L’intention de ce code était-elle de créer une dérivation, ou un effet de bord ? Avec$derived
et$effect
, vous devez choisir entre les deux (spoiler alert : 90% du temps vous voulez$derived
), mais le futur-vous et les autres développeurs et développeuses de votre équipe vous remercieront.Il y avait également quelques inconvénients difficiles à détecter :
$:
ne se mettait à jour que juste avant le rendu, ce qui signifiait que vous pouviez lire des valeurs périmées entre deux rendus$:
n’était exécuté qu’une seule fois par tick, ce qui impliquait que des déclarations pouvaient être exécutées moins souvent que vous ne le pensiez- les dépendances de
$:
étaient déterminées via analyse statique. Ceci fonctionnait dans la plupart des cas, mais pouvait casser de manière subtile au cours d’un refactor lors duquel les dépendances sont déplacées dans une fonction et ne sont en conséquence plus visibles- les déclarations
$:
étaient également ordonnées en utilisant l’analyse statique des dépendances. Dans certains cas, il pouvait y avoir des égalités et l’ordre en résultait mauvais, demandant des interventions manuelles. L’ordre pouvait aussi se briser lors d’un refactor qui rendrait “invisibles” certaines dépendancesEnfin, cette syntaxe n’était pas vraiment compatible avec TypeScript (notre outillage d’éditeur devait faire des circonvolutions pour le rendre compatible avec TypeScript), ce qui était bloquant pour rendre le modèle de réactivité de Svelte vraiment universel.
$derived
et$effect
résolvent tous ces problèmes :
- ils renvoient toujours la valeur la plus à jour
- ils sont exécutés aussi souvent que nécéssaire pour rester stable
- ils déterminent leurs dépendances lors de l’exécution, et sont donc insensibles aux refactors
- ils exécutent leurs dépendences correctement et sont donc insensibles aux problèmes d’ordre
- ils sont adaptés à l’utilisation de TypeScript
export let -> $props
Avec Svelte 4, les propriétés d’un composant étaient déclarées en utilisant export let
. Chaque
propriété nécessitait une déclaration. Avec Svelte 5, toutes les propriétés sont déclarées avec la
rune $props
, via déstructuration :
<script>
export let optional = 'unset';
export let required;
let { optional = 'unset', required } = $props();
</script>
Il y a plusieurs cas dans lesquels la déclaration de propriétés est moins évidente que simplement
déclarer quelques export let
:
- vous souhaitez renommer la propriété, par exemple parce que le nom est en conflit avec un
identifiant réservé (
class
par ex.) - vous ne savez pas à l’avance à quelles autres propriétés vous attendre
- vous souhaitez transférer toutes les propriétés à un autre composant
Toutes ces situations nécessitent une syntaxe spéciale avec Svelte 4 :
- renommer :
export { klass as class}
- autres propriétés :
$$restProps
- toutes les propriétés :
$$props
Avec Svelte 5, la rune $props
rend ces situations plus simples sans ajouter de syntaxe spécifique
à Svelte :
- renommer : utilisez le renommage de propriété
let { class: klass } = $props();
- autres propriétés : utilisez la décomposition
let { foo, bar, ...rest } = $props();
- toutes les propriétés : ne déstructurez pas
let props = $props();
<script>
let klass = '';
export { klass as class};
let { class: klass, ...rest } = $props();
</script>
<button class={klass} {...$$restPropsrest}>cliquez moi</button>
Pourquoi ce changement
export let
était l’un des choix d’API les plus controversés, et il y a eu beaucoup de débats autour de si vous deviez pensez aux propriétés comme étantexport
ées ouimport
ées.$props
n’a pas ce problème. De plus, cette syntaxe est alignée avec les autres runes, dont la philosophie générale peut se résumer par “toute ce qui est relatif à la réactivité en Svelte est une rune”.Il y avait également beaucoup de limitations liées à
export let
, qui requiert des API additionnelles, comme montré plus haut.$props
réunit toutes ces API en un unique concept syntactique qui repose énormément sur la syntaxe JavaScript classique de déstructuration.
Changements sur les évènements
La gestion des évènements a été refondue avec Svelte 5. Alors qu’avec Svelte 4 il faut utiliser la
directive :on
pour attacher un gestionnaire d’évènement à un élément, avec Svelte 5 ce ne sont que
des propriétés comme n’importe quelles autres (autrement dit, enlevez le :
) :
<script>
let count = $state(0);
</script>
<button on:click={() => count++}>
clics : {count}
</button>
Puisque ce ne sont que des propriétés, vous pouvez utiliser la syntaxe raccourcie...
<script>
let count = $state(0);
function onclick() {
count++;
}
</script>
<button {onclick}>
clics : {count}
</button>
... même si lorsque vous utilisez un gestionnaire d’évènement personnalisé il est généralement recommandé d’utiliser un nom plus descriptif.
Évènements de composant
Avec Svelte 4, les composants pouvaient émettre des évènements en créant un dispatcher avec
createEventDispatcher
.
Cette fonction est dépréciée avec Svelte 5. À la place, les composants doivent accepter des props de callback — ce qui signifie que vous passez des fonctions comme propriétés à ces composants :
<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}>nouveau ballon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
<script lang="ts">
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}>nouveau ballon</button>
<span class="boom">💥</span>
{:else}
<span class="balloon" style="scale: {0.01 * size}">
🎈
</span>
{/if}
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let { inflate, deflate } = $props();
let power = $state(5);
</script>
<button onclick={() => dispatch('inflate', power)inflate(power)}>
gongler
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
dégonfler
</button>
<button onclick={() => power--}>-</button>
Puissance de la pompe : {power}
<button onclick={() => power++}>+</button>
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let { inflate, deflate } = $props();
let power = $state(5);
</script>
<button onclick={() => dispatch('inflate', power)inflate(power)}>
gongler
</button>
<button onclick={() => dispatch('deflate', power)deflate(power)}>
dégonfler
</button>
<button onclick={() => power--}>-</button>
Puissance de la pompe : {power}
<button onclick={() => power++}>+</button>
Remonter des évènements
Plutôt que d’écrire <button on:click>
pour “relayer” un évènement depuis l’élément vers le
composant, le composant devrait accepter une prop de callback onclick
:
<script>
let { onclick } = $props();
</script>
<button on:click {onclick}>
cliquez moi
</button>
Notez que ceci signifie également que vous pouvez spread les gestionnaires d’évènement sur un élément avec les autres propriétés plutôt que relayer péniblement chaque évènement séparément :
<script>
let props = $props();
</script>
<button {...$$props} on:click on:keydown on:all_the_other_stuff {...props}>
cliquez moi
</button>
Modificateurs d’évènement
Avec Svelte 4, vous pouvez ajouter des modificateurs aux gestionnaires d’évènement :
<button on:click|once|preventDefault={handler}>...</button>
Les modificateurs sont spécifiques à on:
et ne sont pas compatibles avec la nouvelle syntaxe de
gestion des évènements. L’ajout de choses comme event.preventDefault()
dans le gestionnaire
lui-même est préférable, puisque toute la logique est placée à un seul endroit plutôt que d’être
séparée entre gestionnaire et modificateur.
Puisque les gestionnaires d’évènements ne sont que des fonctions, vous pouvez créer vos propres wrappers si nécessaire :
<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>
Il y a trois modificateurs — capture
, passive
et nonpassive
— qui ne peuvent pas être
exprimés comme des fonctions wrapper, puisqu’ils doivent être appliqués lorsque le gestionnaire
d’évènement est lié et non lorsqu’il est exécuté.
Pour capture
, le modificateur s’ajoute au nom de l’évènement :
<button onclickcapture={...}>...</button>
Changer l’option
passive
d’un gestionnaire d’évènement n’est en revanche pas quelque chose à prendre à la légère. Si vous
avez un cas d’usage — et ce n’est probablement pas le cas ! — il vous faudra utiliser une action
pour ajouter le gestionnaire d’évènement vous-même.
Gestionnaires multiples
Avec Svelte 4, ceci est possible :
<button on:click={one} on:click={two}>...</button>
Les attributs/propriétés dupliquées — ce qui inclut donc maintenant les gestionnaires d’évènement – ne sont pas permis. À la place, faites ceci :
<button
onclick={(e) => {
one(e);
two(e);
}}
>
...
</button>
Lorsque vous “étalez” des props, les gestionnaires d’évènement locaux doivent être appliqués après le spread, ou ils risquent d’être écrasés :
<button
{...props}
onclick={(e) => {
doStuff(e);
props.onclick?.(e);
}}
>
...
</button>
Pourquoi ce changement
createEventDispatcher
a toujours été un peu pénible à utiliser :
- importer la fonction
- appeler la fonction pour obtenir une fonction de dispatch
- exécuter la fonction de dispatch avec une chaîne de caractères et éventuellement une payload
- récupérer la payload en question de l’autre côté via la propriété
.detail
, car l’évènement lui-même était systématiquement unCustomEvent
Il a toujours été possible d’utiliser des props de callback pour les composants, mais parce qu’il était nécessaire d’écouter les évènements du DOM avec
on:
, cela avait du sens d’utilisercreateEventDispatcher
pour les évènements de composant pour garder une consistance syntactique. Maintenant que nous avons les attributs d’évènement (onclick
), c’est le contraire : les props de callback sont maintenant la méthode recommandée.La suppression des modificateurs d’évènement est indiscutablement un des changements ressemblant le plus à un retour en arrière pour celles et ceux qui ont apprécié la syntaxe raccourcie des modificateurs d’évènement. Étant donné qu’ils ne sont pas utilisés si souvent que ça, nous avons choisi de privilégier le côté explicite plutôt qu’une surface plus restreinte. Les modificateurs étaient également inconsistents, puisque la plupart d’entre eux n’étaient utilisables que sur des éléments DOM.
L’utilisation de plusieurs gestionnaires pour le même évènement n’est également plus possible, mais cela était de toutes façon une méthode non-recommandée, car elle compliquait la lecture du code : s’il y avait beaucoup d’attributs, il devenait difficile de se rendre compte qu’il y avait deux gestionnaires à moins qu’ils ne soient positionnés juste à côté l’un de l’autre. De plus, cela laissait entendre que les deux gestionaires étaient indépendants, alors que quelque chose comme
event.stopImmediatePropagation()
dansone
empêcheraittwo
d’être exécuté.En dépréciant
createEventDispatcher
et la directiveon:
en faveur des props de callback et des propriétés normales d’éléments, cela nous permet de :
- réduire la courbe d’apprentissage de Svelte
- supprimer du boilerplate, particulièrement autour de
createEventDispatcher
- supprimer le surcoût de créer des objets
CustomEvent
pour des évènements qui n’ont peut-être même pas de gestionnaire associé- ajouter la possibilité de spread les gestionnaires d’évènement
- ajouter la possibilité de savoir quels gestionnaires sont fournis au composant
- ajouter la possibilité d’exprimer si un gestionnaire donné est requis ou optionnel
- augmenter la sécurité du typage (auparavant, il était dans les faits impossible pour Svelte de garantir qu’un composant n’émettait pas un évènement particulier)
Des snippets plutôt que des slots
Avec Svelte 4, du contenu peut être passé aux composants en utilisant des slots. Svelte 5 les remplace par les snippets, qui sont plus puissants et plus flexibles. En conséquence, les slots sont dépréciés par Svelte 5.
Ils continuent toutefois de fonctionner, et vous pouvez passer des snippets à un composant qui utilise des slots :
<slot />
<hr />
<slot name="foo" message="hello" />
<script>
import Child from './Child.svelte';
</script>
<Child>
default child content
{#snippet foo({ message })}
message from child: {message}
{/snippet}
</Child>
<script lang="ts">
import Child from './Child.svelte';
</script>
<Child>
default child content
{#snippet foo({ message })}
message from child: {message}
{/snippet}
</Child>
(L’inverse n’est pas vrai — vous ne pouvez pas passer du contenu slotté à un composant qui utilise
des balises {@render ...}
.)
Lorsque vous utilisez des éléments personnalisés, vous devez toujours utiliser <slot />
comme
avant. Dans une version future, lorsque Svelte supprimera sa version interne des slots, les slots
des éléments personnalisés seront laissés tels quels, c-à-d générant une vraie balise DOM plutôt que
de la transformer.
Contenu par défaut
Avec Svelte 4, la façon la plus simple de passer un morceau d’interface à un enfant était d’utiliser
un <slot />
. Avec Svelte 5, vous pouvez faire cela en utilisant plutôt la prop children
, qui
s’utilise avec {@render children()}
:
<script>
let { children } = $props();
</script>
<slot />
{@render children?.()}
Contenu générique multiple
Si vous souhaitiez plusieurs morceaux d’interface génériques, vous deviez utiliser les slots nommés.
Avec Svelte 5, vous pouvez plutôt utiliser des props, les nommer comme vous le souhaitez et les
afficher avec {@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>
Remonter des données
Avec Svelte 4, vouss pouviez passer des données à un <slot />
puis les récupérer avec let:
dans
le composant parent. Avec Svelte 5, les snippets ont désormais cette responsabilité :
<script>
import List from './List.svelte';
</script>
<List items={['one', 'two', 'three']} let:item>
{#snippet item(text)}
<span>{text}</span>
{/snippet}
<span slot="empty">Pas encore d'éléments</span>
{#snippet empty()}
<span>Pas encore d'éléments</span>
{/snippet}
</List>
<script lang="ts">
import List from './List.svelte';
</script>
<List items={['one', 'two', 'three']} let:item>
{#snippet item(text)}
<span>{text}</span>
{/snippet}
<span slot="empty">Pas encore d'éléments</span>
{#snippet empty()}
<span>Pas encore d'éléments</span>
{/snippet}
</List>
<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}
<script lang="ts">
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}
Pourquoi ce changement
Les slots étaient simples à prendre en main, mais dans les cas d’usage avancés, la syntaxe devenait de plus en plus confuse et complexe :
- la syntaxe
let:
n’était pas claire pour beaucoup de gens car elle crée une variable alors que toutes les autres directives:
reçoivent une variable- le scope d’une variable déclarée avec
let:
n’était pas clair. Dans l’example ci-dessus, il semble que vous puissiez utiliser la prop de slotitem
dans le slotempty
, mais ce n’est en réalité pas possible- les slots nommés devaient être utilisés sur un élément utilisant l’attribut
slot
. Parfois vous ne vouliez pas créer un élément, et il a donc fallu ajouter l’API<svelte:fragment>
- les slots nommés pouvaient aussi être utilisés sur un composant, ce qui changeait la sémantique de où les directives
let:
sont disponibles (même encore aujourd’hui les mainteneurs du projet ne se souviennent pas toujours de comment ça fonctionne précisément)Les snippets résolvent tous ces problèmes en étant beaucoup clairs et lisibles. Ils sont de plus plus puissants puisqu’ils vous permettent de définir des sections d’interface que vous pouvez afficer partout, et pas seulement en les passant en props à un composant.
Script de migration
Vous devriez avoir maintenant une idée plus claire des changements apportés, notamment ce que change la nouvelle syntaxe par rapport à l’ancienne. Vous avez probablement également compris que beaucoup de ces migrations sont plutôt techniques et répétitives — des choses que vous n’avez pas envie de faire à la main.
Nous sommes d’accord, c’est pourquoi nous fournissons un script de migration pour faire la plupart
de ces migrations automatiquement. Vous pouvez migrer votre projet en utilisant npx sv migrate svelte-5
. Ceci va faire les choses suivantes :
- mettre à jour les versions des dépendences principales dans votre
package.json
- migrer votre code pour utiliser les runes (
let
->$state
etc.) - migrer les éléments DOM pour qu’ils utilisent les attributs d’évènements (
on:click
->onclick
) - migrer les créations de slots pour utiliser les balises de rendu (
<slot />
->{@render children()}
) - migrer l’utilisation des slots pour utiliser des snippets (
<div slot="x">...</div>
->{#snippet x()}<div>...</div>{/snippet}
) - migrer les créations évidentes de composant (
new Component(...)
->mount(Component, ...)
)
Vous pouvez aussi migrer un seul composant à la fois dans VS Code via la commande Migrate Component to Svelte 5 Syntax
, ou dans notre bac à sable via le bouton Migrer
.
Tout ne sera pas migré automatiquement, et certaines migrations auront besoin d’un nettoyage manuel a posteriori. Les sections suivantes fournissent plus de détails sur ces situations.
run
Vous remarquerez peut-être que le script de migration convertit certaines de vos déclarations $:
en une fonction run
qui est importée depuis svelte/legacy
. Ceci se produit si le script de
migration n’a pas pu déterminer avec certitude si une déclaration devrait être migrée en une rune
$derived
, et en a déduit qu’il s’agissait d’un effet de bord. Dans certains cas il se peut que
cela soit faux, et il sera alors recommandé de le modifier manuellement pour utiliser plutôt un
$derived
. Dans d’autres cas cela sera correct, mais puisque les déclarations $:
sont également
exécutées sur le serveur alors que $effect
non, c’est un changement risqué à faire tel quel. À la
place run
est utilisé comme bouche-trou. run
reproduit la plupart des caractéristiques de $:
,
en ce qu’il n’est exécuté sur le serveur qu’une seule fois, et est exécuté comme $effect.pre
sur
le client ($effect.pre
est joué avant que les changements ne soient appliqués au DOM ; dans la
plupart des cas vous voudrez utiliser plutôt $effect
).
<script>
import { run } from 'svelte/legacy';
run(() => {
$effect(() => {
// some side effect code
})
</script>
Modificateurs d’évènements
Les modificateurs d’évènements ne sont pas applicables sur les attributs d’évènement (c-à-d que vous
ne pouvez pas écrire onclick|preventDefault={...}
). En conséquence, lorsque vous migrez des
directives d’évènement vers des attributs d’évènement, une fonction est nécessaire pour remplacer
ces modificateurs. Ces fonctions sont importées depuis svelte/legacy
, et devraient être remplacées
à terme par l’usage, par exemple, de event.preventDefault()
.
<script>
import { preventDefault } from 'svelte/legacy';
</script>
<button
onclick={preventDefault((event) => {
event.preventDefault();
// ...
})}
>
cliquez moi
</button>
Choses non auto-migrées
Le script de migration ne convertit pas createEventDispatcher
. Vous devrez les migrer
manuellement. La raison est qu’il est trop risqué de le faire automatiquement, cela pourrait
entraîner des incohérences problématiques pour les consommateurs du composant, ce que le script de
migration ne peut pas détecter.
Le script de migration ne convertit pas beforeUpdate/afterUpdate
. Il ne le fait pas car il est
impossible de déterminer l’intention sous-jacente du code concerné. En règle générale, vous pouvez
les migrer en utilisant une combinaison de $effect.pre
(est exécuté au même moment que l’était
beforeUpdate
) et tick
(importé depuis svelte
, qui vous permet d’attendre que les changements
soient appliqués au DOM pour exécuter du code).
Les composants ne sont plus des classes
En Svelte 3 et 4, les composants sont des classes. En Svelte 5 ce sont des fonctions et doivent donc
être instanciés différemment. Si vous avez besoin d’instancier manuellement des composants, vous
devriez plutôt utiliser mount
ou hydrate
(importés depuis svelte
). Si vous voyez cette erreur
en utilisant SvelteKit, essayez d’abord de mettre à jour SvelteKit, car sa version la plus récente
fournit le support de Svelte 5. Si vous utilisez Svelte sans SvelteKit, vous aurez certainement un
fichier main.js
(ou similaire) que vous ajuster comme ceci :
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app;mount
et hydrate
ont exactement la même API. La différence est que hydrate
va récupérer le
HTML rendu sur le serveur par Svelte, le placer à l’endroit de sa cible, et l’hydrater. Les deux
renvoient un objet avec les exports du composant et potentiellement les accesseurs de propriété (si
compilé avec accessors: true
). En revanche, elles n’ont pas de méthodes $on
, $set
, et
$destroy
que vous connaissez peut-être de la précédente API utilisant les classes. Voici leurs
remplacements :
Pour remplacer $on
, plutôt que d’écouter les évènements, passez les via la propriété events
dans
l’argument d’options.
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
app.$on('event', callback);
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>
app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
events?: Record<string, (e: any) => any> | undefined
Allows the specification of events.
event: any
event: callback } });Notez que l’utilisation de
events
n’est pas recommandée — utilisez plutôt des callbacks
Pour remplacer $set
, utilisez $state
pour créer un object de propriétés réactives et les
manipuler. Si vous faites ceci dans un fichier .js
ou .ts
, pensez à ajuster le nom du fichier
pour y inclure .svelte
, c-à-d .svelte.js
ou .svelte.ts
.
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount } from 'svelte';
import type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$set({ foo: 'baz' });
const const props: {
foo: string;
}
props = function $state<{
foo: string;
}>(initial: {
foo: string;
}): {
foo: string;
} (+1 overload)
namespace $state$state({ foo: string
foo: 'bar' });
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
props?: Record<string, any> | undefined
Component properties.
const props: {
foo: string;
}props.foo: string
foo = 'baz';Pour remplacer $destroy
, utilisez unmount
.
import { function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports
Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
mount, function unmount(component: Record<string, any>, options?: {
outro?: boolean;
} | undefined): Promise<void>
Unmounts a component that was previously mounted using mount
or hydrate
.
Since 5.13.0, if options.outro
is true
, transitions will play before the component is removed from the DOM.
Returns a Promise
that resolves after transitions have completed if options.outro
is true, or immediately otherwise (prior to 5.13.0, returns void
).
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, { target: document.body });
// later...
unmount(app, { outro: true });
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app"), props: { foo: 'bar' } });
app.$destroy();
const const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app = mount<Record<string, any>, {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<...>>(component: ComponentType<...> | Component<...>, options: MountOptions<...>): {
...;
} & Record<...>Mounts a component to the given target and returns the exports and potentially the props (if compiled with accessors: true
) of the component.
Transitions will play during the initial render unless the intro
option is set to false
.
const App: LegacyComponentType
App, { target: Document | Element | ShadowRoot
Target element where the component will be mounted.
var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
function unmount(component: Record<string, any>, options?: {
outro?: boolean;
} | undefined): Promise<void>Unmounts a component that was previously mounted using mount
or hydrate
.
Since 5.13.0, if options.outro
is true
, transitions will play before the component is removed from the DOM.
Returns a Promise
that resolves after transitions have completed if options.outro
is true, or immediately otherwise (prior to 5.13.0, returns void
).
import { mount, unmount } from 'svelte';
import App from './App.svelte';
const app = mount(App, { target: document.body });
// later...
unmount(app, { outro: true });
const app: {
$on?(type: string, callback: (e: any) => void): () => void;
$set?(props: Partial<Record<string, any>>): void;
} & Record<string, any>app);Comme solution bouche-trou, vous pouvez également utiliser createClassComponent
ou
asClassComponent
(importés depuis svelte/legacy
) pour garder la même API post-instantiation que
Svelte 4.
import { function createClassComponent<Props extends Record<string, any>, Exports extends Record<string, any>, Events extends Record<string, any>, Slots extends Record<string, any>>(options: ComponentConstructorOptions<Props> & {
component: ComponentType<SvelteComponent<Props, Events, Slots>> | Component<Props>;
}): SvelteComponent<Props, Events, Slots> & Exports
Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte'
const app = new App({ target: document.getElementById("app") });
const const app: SvelteComponent<Record<string, any>, any, any> & Record<string, any>
app = createClassComponent<Record<string, any>, Record<string, any>, any, any>(options: ComponentConstructorOptions<Record<string, any>> & {
component: Component<...> | ComponentType<...>;
}): SvelteComponent<...> & Record<...>Takes the same options as a Svelte 4 component and the component function and returns a Svelte 4 compatible component.
component: Component<Record<string, any>, {}, string> | ComponentType<SvelteComponent<Record<string, any>, any, any>>
component: const App: LegacyComponentType
App, ComponentConstructorOptions<Props extends Record<string, any> = Record<string, any>>.target: Document | Element | ShadowRoot
target: var document: Document
document.Document.getElementById(elementId: string): HTMLElement | null
Returns a reference to the first object with the specified value of the ID attribute.
const app: SvelteComponent<Record<string, any>, any, any> & Record<string, any>
app;Si le composant en question n’est pas sous votre contrôle, vous pouvez utiliser l’option de
compilateur compatibility.componentApi
pour obternir une rétro-compatibilité automatique, ce qui
implique que le code utilisant new Component(...)
continuera de fonctionner sans ajustements
(notez que ceci rajoute du code supplémentaire pour tous vos composants). Ceci va également ajouter
les méthodes $set
et $on
pour toutes les instances de composants que vous obtiendrez via
bind:this
.
/// svelte.config.js
export default {
compilerOptions: {
compatibility: {
componentApi: number;
};
}
compilerOptions: {
compatibility: {
componentApi: number;
}
compatibility: {
componentApi: number
componentApi: 4
}
}
};
Notez que mount
et hydrate
ne sont pas synchrones, ce que implique que des choses comme
onMount
n’auront pas été appelées au moment où la fonction termine son exécution et que les blocs
de promesses n’auront pas encore été rendus (parce que #await
attend la fin d’une micro-tâche pour
réagir à la potentielle résolution immédiate d’une promesse). Si vous avez besoin de ces garanties,
appelez flushSync
(importé depuis svelte
) après avoir appelé mount
ou hydrate
.
Changements de l’API serveur
De même, les composants n’ont plus de méthode render
lorsqu’ils sont compilés pour le rendu côté
serveur. Passez plutôt la fonction à render
importé depuis svelte/server
:
import { function render<Comp extends SvelteComponent<any> | Component<any>, Props extends ComponentProps<Comp> = ComponentProps<Comp>>(...args: {} extends Props ? [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options?: {
props?: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
}] : [component: Comp extends SvelteComponent<any> ? ComponentType<Comp> : Comp, options: {
props: Omit<Props, "$$slots" | "$$events">;
context?: Map<any, any>;
}]): RenderOutput
Only available on the server and when compiling with the server
option.
Takes a component and returns an object with body
and head
properties on it, which you can use to populate the HTML when server-rendering your app.
type App = SvelteComponent<Record<string, any>, any, any>
const App: LegacyComponentType
App from './App.svelte';
const { html, head } = App.render({ props: { message: 'salut' }});
const { const html: string
html, const head: string
HTML that goes into the <head>
render<SvelteComponent<Record<string, any>, any, any>, Record<string, any>>(component: ComponentType<SvelteComponent<Record<string, any>, any, any>>, options?: {
...;
} | undefined): RenderOutputOnly available on the server and when compiling with the server
option.
Takes a component and returns an object with body
and head
properties on it, which you can use to populate the HTML when server-rendering your app.
const App: LegacyComponentType
App, { props?: Omit<Record<string, any>, "$$slots" | "$$events"> | undefined
props: { message: string
message: 'salut' }});Avec Svelte 4, le rendu d’un composant en chaîne de caractères fournissait aussi le CSS pour tous
les composants. Avec Svelte 5 ce n’est plus le cas par défaut, car la plupart du temps vous utilisez
un outillage qui s’occupe de gérer cela d’une autre manière (SvelteKit par exemple). Si vous avez
besoin que render
vous fournisse le CSS, vous pouvez mettre l’option de compilateur injected
à
'injected'
, les éléments <style>
seront alors ajoutés au head
.
Changements sur le typage de composant
Le passage des classes aux fonctions pour les composants est aussi reflété dans le typage :
SvelteComponent
, la classe de base en Svelte 4, est dépréciée en faveur du nouveau type
Component
qui définit la forme de la fonction d’un composant Svelte. Pour définir manuellement une
forme de composant dans un fichier .d.ts
, vous pouvez écrire :
import type { interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
you export a component called MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file
with TypeScript:
<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
Component } from 'svelte';
export declare const const MyComponent: Component<{
foo: string;
}, {}, string>
MyComponent: interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
you export a component called MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file
with TypeScript:
<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
Component<{
foo: string
foo: string;
}>;
Pour déclarer qu’un composant d’un certain type est requis :
import { import ComponentA
ComponentA, import ComponentB
ComponentB } from 'component-library';
import type { SvelteComponent } from 'svelte';
import type { interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
you export a component called MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file
with TypeScript:
<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
Component } from 'svelte';
let C: typeof SvelteComponent<{ foo: string }> = $state(
let let C: Component<{
foo: string;
}, {}, string>
C: interface Component<Props extends Record<string, any> = {}, Exports extends Record<string, any> = {}, Bindings extends keyof Props | "" = string>
Can be used to create strongly typed Svelte components.
Example:
You have component library on npm called component-library
, from which
MyComponent
. For Svelte+TypeScript users,
you want to provide typings. Therefore you create a index.d.ts
:
import type { Component } from 'svelte';
export declare const MyComponent: Component<{ foo: string }> {}
Typing this makes it possible for IDEs like VS Code with the Svelte extension
to provide intellisense and to use the component like this in a Svelte file with TypeScript:<script lang="ts">
import { MyComponent } from "component-library";
</script>
<MyComponent foo={'bar'} />
foo: string
foo: string }> = function $state<any>(initial: any): any (+1 overload)
namespace $state$state(
var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.random(): number
Returns a pseudorandom number between 0 and 1.
import ComponentA
ComponentA : import ComponentB
ComponentB
);Les deux types utilitaires ComponentEvents
et ComponentType
sont aussi dépréciés.
ComponentEvents
est obsolète car les évènements sont désormais définis en tant que props de
callback, et ComponentType
est obsolète car le nouveau type Component
est déjà le type du
composant (c-à-d que ComponentType<SvelteComponent<{ prop: string }>>
est équivalent à
Component<{prop: string }>
).
Changements liés à bind:this
Puisque les composants ne sont plus des classes, utiliser bind:this
ne renvoie plus une instance
de classe possédant les méthodes $set
, $on
et $destroy
. Cela renvoie uniquement les exports de
l’instance (export function
/ export const
) et, si vous utilisez l’option accessors
, une paire
de getter/setter pour chaque propriété.
<svelte:component> n’est plus nécessaire
En Svelte 4, les composants sont statiques — si vous rendez <Thing>
, que la valeur de Thing
change, rien ne se produit. Pour le
rendre dynamique vous devez utiliser <svelte:component>
.
Ce n’est plus le cas avec 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>
<!-- ces deux écritures sont équivalentes -->
<Thing />
<svelte:component this={Thing} />
Lorsque vous migrez, gardez à l’esprit que le nom de votre composant doit être en majuscules
(Thing
) pour le distinguer des éléments, à moins que vous n’utilisiez la notation “dot”.
La notation “dot” indique un composant
Avec Svelte 4, <foo.bar>
créait un élément avec un nom de balise "foo.bar"
. Avec Svelte 5,
foo.bar
est à la place traité comme un composant. Cette syntaxe est particulièrement utile dans
les blocs #each
:
{#each items as item}
<item.component {...item.props} />
{/each}
Gestion des espaces
Auparavant, Svelte utilisait un algorithme très complexe pour déterminer si les espace devaient être gardés ou non. Svelte 5 simplifie ce processus, ce qui en rend la compréhension plus simple pour les développeurs et développeuses. Les règles sont :
- Les espaces entre noeuds sont fusionnés en un seul
- Les espaces au début et à la fin d’une balise sont complètement supprimés
- Il y a quelques exceptions comme celle de garder les espaces au sein des balises
pre
Comme avant, vous pouvez désactiver la réduction des espaces en utilisant l’option
preserveWhitespace
dans les paramètres de compilation, ou composant par composant dans
<svelte:option>
.
Un navigateur moderne est requis
Svelte 5 requiert un navigateur moderne (autrement dit, pas Internet Explorer) pour plusieurs raisons :
- il se sert des
Proxies
- les éléments avec des liaisons
clientWidth
/clientHeight
/offsetWidth
/offsetHeight
utilisentResizeObserver
plutôt qu’une bidouille tordue impliquant uneiframe
<input type="range" bind:value={...} />
ne se sert que d’un gestionnaire d’évènementinput
, plutôt qu’également écouter les évènementschange
comme fallback
L’option de compilateur legacy
, qui générait beaucoup de code mais qui permettait de supporter
Internet Explorer, n’existe plus.
Changements des options de compilateur
- Les valeurs
false
/true
(déjà dépréciées précédemment) et"none"
ne sont plus des valeurs valides pour l’optioncss
- L’option
legacy
a un rôle différent - L’option
hydratable
a été supprimée. Les composants Svelte sont désormais toujours hydratables - L’option
enableSourcemap
a été supprimée. Les sourcemaps sont désormais toujours générés, votre outillage peut choisir de les ignorer - L’option
tag
a été supprimée. Utilisez plutôt<svelte:options customElement="tag-name" />
au dans votre fichier de composant - Les options
loopGuardTimeout
,format
,sveltePath
,errorMode
etvarsReport
ont été supprimées
La prop children est réservée
Le contenu dans une balise de composant devient une prop de snippet appelée children
. Vous ne
pouvez pas avoir une prop distincte qui porte ce nom.
Changements bloquants en mode runes
Certains changements bloquants concernent uniquement les composants en mode runes.
Les liaisons à des exports de composant sont interdites
Les exports provenant de composants en mode runes ne peuvent pas être impliquées directement dans
une liaison. Par exemple, écrire export const foo = ...
dans le composant A
puis écrire <A bind:foo />
provoque une erreur. Utilisez plutôt bind:this
— <A bind:this={a} />
— et accédez à
l’export via a.foo
. Ce changement clarifie la séparation entre props et exports.
Les liaisons doivent être explicitement définies avec $bindable()
Avec la syntaxe de Svelte 4, chaque propriété (déclarée via export let
) peut accepter une liaison,
ce qui signifie que vous pouvez la lier avec bind:
. En mode runes, les propriétés ne sont pas
liables par défait : vous devez les marquer comme props de liaison avec la rune $bindable
.
Si une propriété de liaison a une valeur par défaut (par ex. let { foo = $bindable('bar') } = $props();
), vous devez lui passer une valeur non-undefined
si vous décidez de lui créer une
liaison. Ceci empêche des comportements ambigus — le parent et l’enfant doivent avoir la même valeur
– et permet de meilleures performances (en Svelte 4, la valeur par défaut était reflétée sur le
parent, ce qui entraînait des cycles de rendu additionnels).
L’option accessors est ignorée
Définir l’option accessors
à true
rend les propriétés d’un composant directement accessibles sur
l’instance du composant. En mode runes, les propriétés ne sont jamais accessibles sur l’instance du
composant. Si vous avez besoin de les exposer, vous pouvez plutôt utiliser les exports de composant.
L’option immutable est ignorée
Définir l’option immutable
n’a pas d’effet en mode runes. Ce concept est remplacé par la façon
dont $state
et ses variations fonctionnent.
Les classes ne sont plus “auto-réactives”
Avec Svelte 4, faire la chose suivante déclenche la réactivité :
<script>
let foo = new Foo();
</script>
<button on:click={() => (foo.value = 1)}>{foo.value}</button
>
Ceci s’explique par le fait que le compilateur de Svelte considère l’assignation à foo.value
comme
une instruction pour mettre à jour tout ce qui référence foo
. Avec Svelte 5, la réactivité est
déterminée au moment de l’exécution plutôt qu’à la compilation, ce qui implique que vous devriez
définir value
comme un champ réactif $state
sur la classe Foo
. Entourer new Foo()
avec
$state(...)
n’aura aucun effet — seuls les objets simples et les tableaux sont profondément
réactifs.
Les évènements touch et wheel sont passifs
Lorsque vous utilisez les attributs d’évènements onwheel
, onmousewheel
, ontouchstart
et
ontouchmove
, les gestionnaires y sont attachés en mode
passif
pour être en phase avec le comportement par défaut des navigateurs. Ceci améliore significativement
la responsivité en permettant au navigateur de faire défiler le document immédiatement, plutôt que
d’attendre de voir si le gestionnaire d’évènement appelle event.preventDefault()
.
Dans les très rares cas où vous auriez besoin de ne pas avoir ce comportement par défaut, vous
pouvez alors utiliser on
(par exemple au sein d’une action).
La syntaxe des attributs/props est plus stricte
Avec Svelte 4, les valeurs complexes d’attribut n’ont pas besoin d’être en guillemets :
<Component prop=ceci{est}valide />
Cette écriture est une mauvaise idée. En mode runes, si vous souhaitez concaténer des choses, vous devez entourer la valeur de guillemets :
<Component prop="ceci{est}valide" />
Notez que Svelte 5 vous préviendra également si vous avez une expression simple entre guillements,
comme reponse="{42}"
— en Svelte 6, ceci entraînera la conversion de la valeur en chaîne de
caractères, plutôt que de la passer en tant que nombre.
La structure HTML est plus stricte
Avec Svelte 4, vous étiez autorisé•e à écrire du code HTML rendu côté serveur qui serait ensuite réparé par le navigateur. Vous pouviez par exemple écrire ceci...
<table>
<tr>
<td>salut</td>
</tr>
</table>
... et le navigateur insérait automatiquement un élément <tbody>
:
<table>
<tbody>
<tr>
<td>salut</td>
</tr>
</tbody>
</table>
Svelte 5 est plus strict au niveau de la structure HTML et lèvera une erreur de compilation dans les cas où le navigateur aurait besoin de réparer le DOM.
Autres changements bloquants
Validation de l’assignation à @const plus stricte
Les assignations à des parties déstructurées d’une déclaration @const
ne sont plus permises. Cela
n’aurait jamais dû être possible.
:is(...) et :where(...) sont scopés
Auparavant, Svelte n’analysait pas les sélecteurs au sein de :is(...)
et de :where(...)
, les
traitant de facto comme globaux. Svelte 5 les analyse dans le contexte du composant courant. Ainsi,
certains sélecteurs peuvent désormais être traités comme non utilisés s’ils ne se basaient que sur
ces traitements. Pour corriger cela, utilisez :global(...)
dans les sélecteurs
:is(...)
/ :where(...)
.
Si vous utilisez la directive Tailwind @apply
, ajoutez un sélecteur :global
pour conserver les
règles qui utilisent les sélecteurs :is(...)
générés par Tailwind :
main :global {
@apply bg-blue-100 dark:bg-blue-900;
}
La position du hash CSS n’est plus déterministe
Auparavant, Svelte insérait systématiquement le hash CSS en dernière position. Ce n’est plus garanti avec Svelte 5. Ce changement est problématique si vous avez des sélecteurs CSS bizarres.
Le CSS scopé utilisé :where(...)
Pour éviter les problèmes causés par des changements de spécificité imprévisibles, les sélecteurs
CSS scopés utilisent désormais des modificateurs de sélecteurs de type :where(.svelte-xyz123)
en
combinaison avec .svelte-xyz123
, (où xyz123
est, comme avant, un hash du contenu de <style>
).
Vous pouvez en apprendre plus ce sujet ici.
Dans le cas où vous avez besoin de supporter d’anciens navigateurs qui n’implémentent pas :where
,
vous pouvez modifier manuellement le CSS généré, en contrepartie de changements de spécificité
imprévisibles :
css = css.replace(/:where\((.+?)\)/, '$1');
Les codes d’erreur et d’avertissement ont été renommés
Les codes d’erreur et d’avertissement ont été renommés. Ils utilisaient précédemment des tirets pour
séparer les mots, ils utilisent désormais des tirets bas (par ex. foo-bar
devient foo_bar
). De
plus, un petit nombre de codes ont été légèrement reformulés.
Le nombre de namespaces a été réduit
Le nombre de namespaces valides que vous pouvez passer à l’option de compilateur namespace
a été
réduit à html
(par défaut), mathml
et svg
.
Le namespace foreign
était uniquement utile pour Svelte Native, que nous prévoyons de supporter de
manière différente dans une mise à jour mineure 5.x future.
Changements concernant beforeUpdate / afterUpdate
beforeUpdate
n’est plus exécuté deux fois au rendu initial si son callback modifie une variable
référencée dans le template.
Les callbacks afteUpdate
d’un composant parent sont désormais exécutés après les callbacks
afterUpdate
des composants enfants.
beforeUpdate/afterUpdate
ne sont plus exécutées lorsque le composant contient un <slot>
et que
son contenu est mis à jour.
Les deux fonctions ne sont plus autorisées en mode runes — utilisez $effect.pre(...)
et
$effect(...)
à la place.
Changement du comportement de contenteditable
Si vous avez un noeud contenteditable
avec une liaison attachée et une valeur réactive utilisé
dans son contenu (exemple : <div contenteditable=true bind:textContent>count vaut {count}</div>
),
alors la valeur au sein de contenteditable
ne sera pas mise à jour par les mises à jour de count
car la liaison prend immédiatement le contrôle total du contenu, qui sera donc uniquement mis à jour
à travers cette liaison.
Les attributs oneventname n’acceptent plus de valeurs de type string
En Svelte 4, il était possible de préciser des attributs d’évènement sous forme de chaînes de caractères sur des éléments HTML :
<button onclick="alert('salut')">...</button>
Ceci n’est pas recommandé, et n’est plus possible en Svelte 5, où les propriétés comme onclick
remplacent on:click
comme mécanisme pour l’ajout de gestionnaires d’évènement.
null et undefined deviennent la chaîne de caractères vide
En Svelte 4, null
et undefined
était affichés comme leur chaîne de caractères correspondante.
Dans 99 cas sur 100 vous souhaitez plutôt qu’ils soient remplacés par la chaîne de caractères vide,
ce qui est également ce que la plupart des autres frameworks existants font. En conséquence, en
Svelte 5, null
et undefined
deviennent la chaîne de caractères vide.
Les valeurs de bind:files peuvent uniquement être null, undefined ou FileList
La liaison bind:files
est désormais à double sens. Dès lors, lorsque vous définissez une valeur,
celle-ci doit être soit falsy (null
ou undefined
) soit de type FileList
.
Les liaisons réagissent désormais aux réinitialisations de formulaires
Auparavant, les liaisons ne prennaient pas en compte les évènement reset
des formulaires, ce qui
impliquait que leurs valeurs pouvaient se désynchroniser du DOM. Svelte 5 corrige cela en plaçant un
gestionnaire reset
sur le document et en invoquant les liaisons lorsque nécéssaire.
walk n’est plus exportée
Le module svelte/compiler
ré-exportait walk
depuis estree-walker
par commodité. Ce n’est plus
le cas avec Svelte 5 ; importez le directement depuis ce estree-walker
si vous en avez besoin.
Le contenu dans svelte:options est interdit
Avec Svelte 4 vous pouviez avoir du contenu dans une balise <svelte:options />
. Ce contenu était
ignoré, mais vous pouviez y écrire quelque chose. Avec Svelte 5, le contenu dans cette balise
provoque une erreur de compilation.
Les éléments <slot> dans des shadowroot sont préservés
Svelte 4 remplaçait toutes les balises <slot />
avec sa propre version des slots. Svelte 5 les
conserve dans le cas où ils sont enfants d’un élément <template shadowrootmode="...">
.
La balise <svelte:element> doit être une expression
En Svelte 4, <svelte:element this="div">
est du code valide. Ceci n’a que peu de sens — vous
devriez simplement écrire <div>
. Dans le cas extrêmement rare où vous avez besoin d’utiliser une
valeur litérale pour une raison précise, vous pouvez faire ceci :
<svelte:element this={"div"}>
Notez que là où Svelte 4 traitait <svelte:element this="input">
(par exemple) de la même manière
que <input>
pour déterminer quelles directives bind:
pouvaient être appliquées, ce n’est pas le
cas de Svelte 5.
mount joue les transitions par défaut
La fonction mount
utilisée pour rendre un arbre de composant joue par défaut les transitions à
moins que l’option intro
ait pour valeur false
. Ceci diffère des anciens composants (qui étaient
des classes) qui, lorsqu’instanciés manuellement, ne jouaient pas les transitions par défaut.
Les incohérences d’hydratation de <img src={...}> and {@html ...} ne sont pas réparées
En Svelte 4, si la valeur d’un attribut src
ou d’une balise {@html ...}
différait entre le
server et le client (situation connue sous le nom d’"incohérence d’hydratation”), l’incohérence est
corrigée. Ceci a un coût élevé : définir un attribut src
(même si sa valeur ne change pas)
déclenche le chargement des images et des iframes, et réinsérer un gros morceau d’HTML est un
procédé lent.
Puisque ces incohérences sont extrêmement rares, Svelte 5 suppose que les valeurs n’ont pas changé, mais en mode développement, Svelte 5 vous préviendra qu’elles sont différentes. Pour forcer la mise à jour, vous pouvez faire quelque chose comme ça :
<script>
let { markup, src } = $props();
if (typeof window !== 'undefined') {
// gardez les valeurs...
const initial = { markup, src };
// mettez-les à undefined...
markup = src = undefined;
$effect(() => {
// ... et réinitialisez-les après le montage du composant
markup = initial.markup;
src = initial.src;
});
}
</script>
{@html markup}
<img {src} />
L’hydratation fonctionne différemment
Svelte 5 utilise des commentaires lors du rendu côté serveur, commentaires qui sont utilisés pour une hydratation plus robuste et plus efficace sur le client. Dès lors, vous ne devriez pas supprimer ces commentaires du HTML généré si vous avez l’intention de l’hydrater ; de plus, si vous avez manuellement écrit du HTML qui doit être hydraté par un composant Svelte, vous devez ajuster ce HTML pour inclure ces commentaires aux endroits adéquats.
Les attributs onevent sont délégués
Les attributs d’évènement remplacent les directives : plutôt que d’écrire on:click={gestionnaire}
,
vous écrivez désormais onclick={gestionnaire}
. Pour des raisons de rétro-compatibilité, la syntaxe
on:event
est toujours supportée et se comporte comme en Svelte 4. Certains des attributs onevent
sont cependant délégués, car ils se peut qu’ils n’atteignent jamais le gestionnaire de ce type
d’évènement à la racine.
--style-props utilise un élément différent
Svelte 5 utilise un élément <svelte-css-wrapper>
supplémentaire plutôt qu’une <div>
pour
entourer le composant lorsque vous utilisez des propriétés CSS personnalisées.
Modifier cette page sur Github