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.
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.
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;
}
load({ cookies: anycookies }) {
cookies: anycookies.set(const name: voidname, 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>>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.
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>>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: PromiseConstructorRepresents 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.
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.
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.
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 pour cette raison 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.
Pour cette raison, 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<T extends RouteId | Pathname>(...args: ResolveArgs<T>): ResolvedPathnameresolveRoute } from '$app/paths';
const path = base + resolvePath('/blog/[slug]', { slug });
const const path: stringpath = resolveRoute<"/blog/[slug]">(route: "/blog/[slug]", params: Record<string, string>): ResolvedPathnameresolveRoute('/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. Ceci
signifie que 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@4vite@5typescript@5@sveltejs/vite-plugin-svelte@3(cette dépendance doit désormais être unepeerDependencyde 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