Skip to main content

Migrer vers SvelteKit v2

Mettre à jour Sveltekit de la version 1 à la version 2 devrait en théorie se passer globalement bien. Il y a quelques breaking changes à connaître, qui sont listés ci-dessous. Vous pouvez utiliser npx sv migrate sveltekit-2 pour migrer certains de ces changements automatiquement.

Nous recommandons vivement de mettre à jour d’abord vers la dernière version en 1.x avant de passer à 2.0, afin de profiter des avertissements de dépréciation ciblés. Nous recommandons également de d’abord mettre à jour Svelte vers la version 4 : les dernières versions de SvelteKit 1.x le supportent, et SvelteKit 2.0 le nécessite.

Vous ne devez plus jeter manuellement redirect et error

Auparavant, vous deviez throw les valeurs renvoyées par error(...) et redirect(...) vous-même. Avec SvelteKit 2 ce n’est plus le cas — appeler les fonctions en question est suffisant.

import { function error(status: number, body: App.Error): never (+1 overload)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response without invoking handleError. Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
error
} from '@sveltejs/kit'
// ... throw error(500, "quelque chose s'est mal passé");
function error(status: number, body?: {
    message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)

Throws an error with a HTTP status code and an optional message. When called during request handling, this will cause SvelteKit to return an error response without invoking handleError. Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.

@paramstatus The HTTP status code. Must be in the range 400-599.
@parambody An object that conforms to the App.Error type. If a string is passed, it will be used as the message property.
@throwsHttpError This error instructs SvelteKit to initiate HTTP error handling.
@throwsError If the provided status is invalid (not between 400 and 599).
error
(500, "quelque chose s'est mal passé");

svelte-migrate fera ces changements automatiquement pour vous.

Si l’erreur ou la redirection est jetée dans un bloc try {...} (indice : ne faites pas ça !), vous pouvez les différencier des erreurs inattendues en utilisant les utilitaires isHttpError et isRedirect importés depuis @sveltejs/kit.

path est requis lorsque vous définissez des cookies

Lorsque vous recevez un en-tête Set-Cookie qui ne précise pas de path, les navigateurs vont définir le chemin du cookie en tant que parent de la ressource en question. Ce comportement n’est pas particulièrement utile ni intuitif, et entraîne souvent des bugs car le ou la développeuse s’attendait à ce que le cookie s’applique au domaine en entier.

Avec SvelteKit 2.0, vous avez besoin de définir un path lorsque vous appelez cookies.set(...), cookies.delete(...) ou cookies.serialize(...) afin qu’il n’y ait pas d’ambiguïté. La plupart du temps, vous voudrez probablement utiliser path: '/', mais vous pouvez le définir à la valeur qui vous arrange, incluant des chemins relatifs — '' signifiant “le chemin actuel”, '.' signifiant "le dossier actuel”.

/** @type {import('./$types').PageServerLoad} */
export function 
function load({ cookies }: {
    cookies: any;
}): {
    response: any;
}
@type{import('./$types').PageServerLoad}
load
({ cookies: anycookies }) {
cookies: anycookies.set(const name: void
@deprecated
name
, value, { path: stringpath: '/' });
return { response: anyresponse } }

svelte-migrate ajoutera des commentaires mettant en valeur les endroits qui ont besoin d’ajustements.

Les promesses à la racine ne sont attendues

Avec SvelteKit version 1, si les propriétés à la racine de l’objet renvoyé par une fonction load étaient des promesses, elles étaient automatiquement attendues avec await. Avec l’introduction du streaming ce comportement était devenu un peu bizarre car il vous forçait à imbriquer les données que vouliez streamer à un niveau plus profond dans l’objet.

Avec la version 2, SvelteKit ne fait plus aucune différence entre les promesses à la racine et les autres. Pour récupérer le comportement bloquant, utilisez await explicitement (avec Promise.all pour éviter les cascades, lorsque pertinent) :

// Si vous avez une seule promesse
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
@type{import('./$types').PageServerLoad}
load
({
fetch: {
    (input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
    (input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}

fetch is equivalent to the native fetch web API, with a few additional features:

  • It can be used to make credentialed requests on the server, as it inherits the cookie and authorization headers for the page request.
  • It can make relative requests on the server (ordinarily, fetch requires a URL with an origin when used in a server context).
  • Internal requests (e.g. for +server.js routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
  • During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the text and json methods of the Response object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
  • During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.

You can learn more about making credentialed requests with cookies here.

fetch
}) {
const const response: anyresponse = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch(const url: stringurl).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
then
(r: Responser => r: Responser.Body.json(): Promise<any>json());
return { response: anyresponse } }
// SI vous avez plusieurs promesses
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
@type{import('./$types').PageServerLoad}
load
({
fetch: {
    (input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
    (input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}

fetch is equivalent to the native fetch web API, with a few additional features:

  • It can be used to make credentialed requests on the server, as it inherits the cookie and authorization headers for the page request.
  • It can make relative requests on the server (ordinarily, fetch requires a URL with an origin when used in a server context).
  • Internal requests (e.g. for +server.js routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
  • During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the text and json methods of the Response object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
  • During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.

You can learn more about making credentialed requests with cookies here.

fetch
}) {
const a = fetch(url1).then(r => r.json()); const b = fetch(url2).then(r => r.json()); const [const a: anya, const b: anyb] = await var Promise: PromiseConstructor

Represents the completion of an asynchronous operation

Promise
.PromiseConstructor.all<[Promise<any>, Promise<any>]>(values: [Promise<any>, Promise<any>]): Promise<[any, any]> (+1 overload)

Creates a Promise that is resolved with an array of results when all of the provided Promises resolve, or rejected when any Promise is rejected.

@paramvalues An array of Promises.
@returnsA new Promise.
all
([
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch(const url1: stringurl1).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
then
(r: Responser => r: Responser.Body.json(): Promise<any>json()),
fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch(const url2: stringurl2).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.
@paramonrejected The callback to execute when the Promise is rejected.
@returnsA Promise for the completion of which ever callback is executed.
then
(r: Responser => r: Responser.Body.json(): Promise<any>json()),
]); return { a: anya, b: anyb }; }

Changements impactant goto(...)

goto(...) n’accepte plus les URLs externes. Pour naviguer vers une URL externe, utilisez window.location.href = url. L’objet state détermine désormais $page.state et doit adhérer à l’interface App.PageState, si déclarée. Voir la section sur le shallow routing pour plus d’informations.

Les chemins sont désormais relatifs par défaut

Avec SvelteKit 1, les occurences de %sveltekit.assets% dans votre fichier app.html étaient remplacées par un chemin relatif par défaut (c-à-d . ou .. ou ../.. etc, en fonction du chemin en train d’être rendu) lors du rendu côté serveur à moins que l’option de configuration paths.relative n’ait été explicitement définie à false. Le même comportement s’appliquait pour base et assets importés depuis $app/paths, mais seulement si l’option paths.relative avait été définie à true.

Cette incohérence est corrigée dans la version 2. Les chemins sont soit toujours relatifs, soit toujours absolus, en fonction de la valeur de paths.relative. Cette option vaut true par défaut car cela permet des applications plus portables : si la base vaut autre chose que ce que l’application attend (comme c’est le cas lorsqu’on visite l’application depuis l’Internet Archive, par exemple), ou une valeur inconnue au moment de la compilation (comme c’est le cas lorque vous déployez sur l’IPFS entre autres), il y a alors moins de risques de casser des choses.

Les requêtes depuis le serveur ne sont plus traçables

Auparavant il était possible de tracer les URLs depuis les requêtes fetch venant du serveur pour permettre de ré-exécuter les fonctions load. Ceci posait un problème de sécurité potentiel (faire fuiter les URLs privées), et en conséquence ce comportement était activable avec le paramètre dangerZone.trackServerFetches, qui n’existe plus avec la version 2.

Les arguments de preloadCode doivent être préfixés avec base

SvelteKit expose deux fonctions, preloadCode et preloadData, permettant de programmatiquement charger le code et les données associées à un chemin particulier. Avec la version 1, il y avait une incohérence subtile — le chemin passé à preloadCode n’avait pas besoin d’être préfixé avec le chemin base (si défini), alors que c’était le cas pour le chemin passé à preloadData.

Ceci est corrigé avec SvelteKit 2 — dans les deux cas, le chemin devrait être préfixé avec base si cette valeur est définie.

De plus, preloadCode prend désormais un seul argument plutôt n.

resolvePath a été supprimée

SvelteKit 1 possédait une fonction appelée resolvePath vous permettant de résoudre un ID de route (comme /blog/[slug]) et un ensemble de paramètres (comme { slug: 'hello' }) en tant que chemin. Malheureusement la valeur de retour n’incluait pas le chemin base, limitant son utilité dans les situations où la valeur base était définie.

En conséquence, SvelteKit 2 remplace resolvePath par une fonction (un tout petit peu mieux nommée) appelée resolveRoute, qui est importée depuis $app/paths et prend base en compte.

import { resolvePath } from '@sveltejs/kit';
import { base } from '$app/paths';
import { function resolveRoute(id: string, params: Record<string, string | undefined>): string

Populate a route ID with params to resolve a pathname.

@examplejs import { resolveRoute } from '$app/paths'; resolveRoute( `/blog/[slug]/[...somethingElse]`, { slug: 'hello-world', somethingElse: 'something/else' } ); // `/blog/hello-world/something/else`
resolveRoute
} from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug }); const const path: stringpath = function resolveRoute(id: string, params: Record<string, string | undefined>): string

Populate a route ID with params to resolve a pathname.

@examplejs import { resolveRoute } from '$app/paths'; resolveRoute( `/blog/[slug]/[...somethingElse]`, { slug: 'hello-world', somethingElse: 'something/else' } ); // `/blog/hello-world/something/else`
resolveRoute
('/blog/[slug]', { slug: anyslug });

svelte-migrate remplacera la méthode pour vous ; toutefois, si vous préfixiez dans un second temps le résultat avec base, vous devrez supprimer ces préfixes vous-même.

Meilleure gestion des erreurs

SvelteKit 1 gère les erreurs de manière inconsistante. Certaines erreurs déclenchent le hook handleError mais il n’y aucun moyen acceptable de discriminer leur statut (par exemple, le seul moyen de distinguer une 404 d’une 500 est de vérifier si event.route.id est null), tandis que d’autres erreurs (comme les 405 des requêtes POST vers des pages sans action) ne déclenchent pas du tout handleError, alors que cela devrait être le cas. Dans ce dernier cas, l’objet $page.error en résultant ne va pas correspondre au type App.Error, si défini.

SvelteKit 2 règle ces problèmes en appelant le hook handleError avec deux nouvelles propriétés : status et message. Pour les erreurs jetées depuis votre code (ou depuis du code de librairie exécuté depuis votre code), le statut sera 500 et le message sera Internal Error. Alors que error.message peut éventuellement contenir des informations sensibles ne devant pas être exposées aux utilisateurs et utilisatrices, message ne pose pas de problème.

Les variables d’environnement dynamiques ne peuvent pas être utilisées lors du pré-rendu

Les modules $env/dynamic/public et $env/dynamic/private fournissent un accès aux variables d’environnement d’exécution, tandis que les variables d’environnement de compilation sont exposées par $env/static/public et $env/static/private.

Lors du pré-rendu avec SvelteKit 1, ces deux types de variables d’environnement sont les mêmes. En conséquence, les pages pré-rendues utilisant les variables d’environnement “dynamiques” sont en réalité des values intégrées lors de la compilation, ce qui est incorrect. Pire encore, $env/dynamic/public est rempli dans le navigateur avec ces valeurs périmées s’il se trouve que l’utilisateur ou l’utilisatrice se trouve sur une page pré-rendue avant de naviguer vers des pages rendues dynamiquement.

À cause de ce problème, les variables d’environnement dynamiques ne peuvent plus être lues lors du pré-rendu avec SvelteKit 2 — vous devriez plutôt utiliser les modules static. Si l’utilisateur ou l’utilisatrice arrive sur une page pré-rendue, SvelteKit va requêter au serveur des valeurs à jour pour $env/dynamic/public (depuis un module /_app/env.js, par défaut) plutôt que de les lire depuis le HTML généré par le serveur.

form et data ont été supprimées des callbacks use:enhance

Si vous fournissez un callback à use:enhance, celui-ci sera exécuté avec un objet contenant différentes propriétés pratiques.

Avec SvelteKit 1, ces propriétés incluaient form et data. Ces dernières ont été dépréciées il y a quelques temps en faveur de formElement et formData, et ont été supprimées complètement de SvelteKit 2.

Les formulaires contenant des inputs de fichiers doivent utiliser multipart/form-data

Si un formulaire contient un élément <input type="file"> mais n’a pas pas d’attribut enctype="multipart/form-data", les soumissions non faites avec JavaScript vont omettre le fichier. SvelteKit 2 va jeter une erreur s’il rencontre un formulaire comme celui-là lors d’une soumission use:enhance pour assurer que vos formulaires fonctionnent correctement lorsque JavaScript n’est pas disponible.

Le fichier tsconfig généré est plus strict

Auparavant, le fichier tsconfig.json généré essayait au mieux de produire une configuration plus ou moins valide lorsque votre tsconfig.json incluait paths ou baseUrl. Avec SvelteKit 2, la validation est plus stricte et vous avertira lorsque si vous utilisez paths ou baseUrl dans votre tsconfig.json. Ces paramètres sont utilisés pour générer des alias de chemin, et vous devriez plutôt utiliser l’option de configuration alias de votre fichier svelte.config.js, pour créer également un alias pour le bundler.

getRequest ne jette plus d’erreurs

Le module @sveltejs/kit/node exporte des fonctions utilitaires à utiliser dans les environnements Node, parmi lesquelles getRequest, qui transforme un objet Node ClientRequest en un objet Request standard.

Avec SvelteKit 1, getRequest pouvait jeter une erreur si l’en-tête Content-Length dépassait la limite de taille spécifiée. Avec SvelteKit 2, l’erreur ne sera pas jetée avant que le corps de la requête (s’il existe) soit lu. Ceci permet d’obtenir de meilleurs diagnostics et du code plus simple.

vitePreprocess n’est plus exporté depuis @sveltejs/kit/vite

Puisque @sveltejs/vite-plugin-svelte est désormais une peer dependency, SvelteKit 2 ne ré-exporte plus vitePreprocess. Vous devriez l’importer directement depuis @sveltejs/vite-plugin-svelte.

Dépendances requises mises à jour

SvelteKit 2 requiert Node 18.13 ou supérieur, et les versions de dépendances minimum suivantes :

  • svelte@4
  • vite@5
  • typescript@5
  • @sveltejs/vite-plugin-svelte@3 (cette dépendance doit désormais être une peerDependency de SvelteKit — c’était auparavant une dépendance directe)
  • @sveltejs/adapter-cloudflare@3 (si vous utilisez ces adaptateurs)
  • @sveltejs/adapter-cloudflare-workers@2
  • @sveltejs/adapter-netlify@3
  • @sveltejs/adapter-node@2
  • @sveltejs/adapter-static@3
  • @sveltejs/adapter-vercel@4

svelte-migrate mettra à jour votre fichier package.json pour vous.

Une conséquence de la mise à jour de TypeScript est que le fichier tsconfig.json généré (celui que votre propre tsconfig.json étend) utilise désormais "moduleResolution": "bundler" (ce qui est recommandé par l’équipe TypeScript, puisque cela permet de résoudre les types de paquets avec une map exports dans votre fichier package.json) et verbatimModuleSyntax (qui remplace les options existantes importsNotUsedAsValues et preserveValueImports — si vous les avez incluses dans votre tsconfig.json, supprimez-les. svelte-migrate le fera pour vous).

SvelteKit 2.12 : $app/stores déprécié

SvelteKit 2.12 a apporté le module $app/state basé sur l’API de runes de Svelte 5. $app/state fournit tout ce que fournit $app/stores mais avec plus de flexibilité quant à où et quand vous vous en servez. Plus important encore, l’objet page est désormais finement réactif, ce qui implique par ex. que les mises à jour de page.state ne vont pas invalider page.data et vice-versa.

En conséquence, $app/stores est déprécié et sera certainement supprimé dans SvelteKit 3. Nous recommandons de vous mettre à jour vers Svelte 5, si vous ne l’avez pas déjà fait, puis de migrer pour remplacer vos occurrences de $app/stores. La plupart des changements devraient être plutôt simples : remplacez l’import de $app/stores par $app/state et supprimez les préfixes $ des endroits concernés.

<script>
	import { page } from '$app/stores';
	import { page } from '$app/state';
</script>

{$page.data}
{page.data}

Utilisez npx sv migrate app-state pour migrer automatiquement la plupart de vos occurrences de $app/stores présentes dans vos composants .svelte.

Modifier cette page sur Github llms.txt