Fermer

avril 12, 2024

Mise en cache des clients dans SvelteKit

Mise en cache des clients dans SvelteKit


Découvrez quelques approches de mise en cache dans le client avec SvelteKit et voyez un exemple de mise en œuvre.

Page répertoriant trois articles de blog

Récupération intégrée

Il existe toutes sortes de façons de mettre en cache vos données dans un framework. La manière la plus courante dans SvelteKit est d’utiliser Contrôle du cache en-têtes avec fetch. Vous pouvez également définir le type de demande de cache en utilisant fetch sur le frontend.

Récupération de SvelteKit est spécial et permet d’appeler ces requêtes directement sur le serveur sans utiliser d’URL directe. Il peut appeler une fonction directement depuis le serveur, au lieu de récupérer directement les données.

Mise en cache des clients

Mais que se passe-t-il si vous souhaitez simplement ne pas lire deux fois les mêmes données ? Une façon de procéder serait d’installer un package complet comme Requête TanStack pour Svelte, mais vous avez alors beaucoup de frais généraux. Imaginez que vous soyez dans une situation où le Wi-Fi est lent et que vous souhaitiez simplement afficher une page vers laquelle vous avez déjà accédé. Imaginez que vous avez déjà téléchargé les informations de publication et que vous souhaitez simplement cliquer sur la page de détail de la publication. Pourquoi auriez-vous besoin de réinterroger la base de données pour les données que vous avez déjà consultées dans votre application ouverte ?

Cette méthode peut également être utile avec des bases de données comme Firebase où vous êtes facturé pour chaque lecture ! J’ai donc décidé de créer un mécanisme de mise en cache rapide pour SvelteKit afin de gérer ces cas d’utilisation pour un chargement plus rapide.

cacheRécupérer

Je voulais quelque chose d’extrêmement simple à utiliser, mais qui pourrait être implémenté dans le page.ts dans le load fonction. Il doit récupérer les données en arrière-plan et les mettre en cache par URL. Puisque la récupération nécessite tout cela json manipulation, on pourrait mettre tout ça là-dedans aussi. Encore une fois, en utilisant un package externe comme Axios serait exagéré.

1. Créez un point de terminaison

Nous devons d’abord simuler une base de données en créant un point de terminaison.

/routes/data/+server.ts

import { error, json, type RequestHandler } from "@sveltejs/kit";

const posts = [ ... ];

export const GET = (async ({ url }) => {

    const id = Number(url.searchParams.get('id'));

    // simulate loading data slowly
    await new Promise((res) => setTimeout(res, 2000));

    if (id) {
        const post = posts.find(post => post.id === id);
        if (!post) {
            throw error(404, 'Post not found!');
        }
        return json(post);
    }

    return json(posts);

}) satisfies RequestHandler;

Tout cela ne fait qu’obtenir tous les messages s’il y a une demande directe à /data et obtenez un message spécifique s’il y a une demande de /data?id=x. La promesse superflue ici simulera une base de données lente afin que vous puissiez voir le cache en action.

Je viens de demander à l’IA de générer des données factices, mais vous pouvez utiliser n’importe quoi :

const posts = [
    {
        "id": 1,
        "title": "The Joy of Gardening",
        "content": "Discover the endless benefits of spending time in your garden. From growing your own food to the therapeutic aspects of gardening, this post explores why gardening is a hobby worth considering.",
        "author": "Alex Smith",
        "created_at": "2023-11-19T08:00:00Z"
    },
    {
        "id": 2,
        "title": "Tech Trends in 2023",
        "content": "In this post, we dive into the latest technology trends of 2023. We cover everything from AI advancements to sustainable tech solutions that are shaping our world.",
        "author": "Jordan Lee",
        "created_at": "2023-11-18T15:30:00Z"
    },
    {
        "id": 3,
        "title": "Exploring World Cuisines",
        "content": "Join us on a culinary journey as we explore different world cuisines. From the spicy dishes of Mexico to the intricate flavors of Japanese cuisine, get ready to expand your taste palette.",
        "author": "Maria Gonzalez",
        "created_at": "2023-11-17T19:45:00Z"
    }
];

2. Créez les modèles

J’ai créé un modèle de publication de mise en page de base pour afficher les publications. Assurez-vous d’installer Vent arrière dans votre application SvelteKit vierge en premier.

/routes/+layout.svelte

<script>
	import { navigating } from '$app/stores';
	import '../app.css';
</script>

<nav class="m-5">
	<h1 class="text-xl underline hover:no-underline"><a href={`/`}>Home</a></h1>
</nav>

{#if $navigating}
	<div
		class="m-5 inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"
		role="status"
	>
		<span
			class="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]"
		>
			Loading...
		</span>
	</div>
{:else}
	<main class="m-5">
		<slot />
	</main>
{/if}

Cela affichera une icône de chargement lorsque la page est en cours de navigation ou de récupération. Le $navigating est une astuce du mécanisme Svelte pour ce faire.

/routes/+page.svelte

<script lang="ts">
	import type { PageData } from './$types';
	import Post from '@components/post.svelte';

	export let data: PageData;
</script>

<h1 class="text-3xl font-bold underline">Posts</h1>

{#each data.posts as post}
	<Post {post} />
{/each}

Cela montre tous les messages une fois récupérés.

/itinéraires/poste/urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38/+page.svelte

<script lang="ts">
	import type { PageData } from './$types';
	import Post from '@components/post.svelte';

	export let data: PageData;
</script>

<Post post={data.post} />

Cela montre une seule publication avec une entrée d’ID.

/composants/post.svelte

<script lang="ts">
	export let post: Post;
</script>

<div class="border border-1 p-3 my-3">
	<h1 class="text-xl underline hover:no-underline">
		<a href={`/post/${post.id}`}>{post.title}</a>
	</h1>
	<small>By {post.author} - {new Date(post.created_at).toDateString()}</small>
	<article>
		{post.content}
	</article>
</div>

Cela affiche les informations de base sur la publication.

/app.d.ts

// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
	type Post = {
		id: number;
		title: string;
		content: string;
		author: string;
		created_at: string;
	};

	namespace App {
		// interface Error {}
		// interface Locals {}
		// interface PageData {}
		// interface Platform {}
	}
}

export {};

Ajoutez un type global pour la publication.

3. Créez les mécanismes de mise en cache

/lib/cache.ts

import { browser } from "$app/environment";
import { error } from "@sveltejs/kit";

export const cache = new Map();

export const cacheFetch = async <T>(
    key: string,
    fetchCallback: () => ReturnType<typeof fetch>
) => {

    if (browser && cache.has(key)) {
        return cache.get(key) as T
    }
    const response = await fetchCallback();

    if (!response.ok) {
        const message = await response.json();
        throw error(response.status, message);
    }
    const result = await response.json();

    cache.set(key, result);

    return result as T;
};

Tout d’abord, il suffit d’exécuter le cache sur le browser. Le cache lui-même n’est qu’un singleton Carte objet. Parce que vous l’importez dans vos fonctions de chargement, les données seront conservées sur le client. Le fichier n’est exécuté qu’une seule fois, gardant la carte intacte, tandis que la fonction cacheFetch peut être exécuté plusieurs fois.

J’ai également décidé de simplifier le mécanisme de récupération. Répéter la vérification des erreurs et json() techniques de réponse avec fetch peut devenir fastidieux. Tout ce qu’il fait, c’est renvoyer un cache s’il existe, sinon récupérer les données, définir le cache et le renvoyer.

Note: Vous pourriez facilement vous débarrasser du fetch passe-partout ici si vous utilisez un autre mécanisme comme Prisma, Firebase, Supabase, etc.

/routes/+page.ts

import type { PageLoad } from "./$types";
import { cacheFetch } from "$lib/cache";

export const load = (async ({ fetch, url }) => {

    const { pathname } = url;

    return {
        posts: cacheFetch<Post[]>(pathname, () => fetch(`/data`))
    };

}) satisfies PageLoad;

Regardez à quel point ce code est propre. Le cacheFetch La fonction vous permet de transmettre le type de retour souhaité, et cela fonctionne. Vous pouvez utiliser fetch comme vous le souhaitez avec toutes les options, car la fonction elle-même est un rappeler fonction. pathname est utilisé comme clé pour le cache.

Vous pouvez également simplement retourner le fetchou cacheFetch dans ce cas sans awaitcomme le fera SvelteKit fetch pour vous avec toutes les autres fonctions de chargement. Notez que nous utilisons également +page.ts au lieu de +page.server.tscar cela s’exécute à la fois sur le client et sur le serveur.

/itinéraires/poste/urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38/+page.ts

import type { PageLoad } from "./$types";
import { cacheFetch } from "$lib/cache";

export const load = (async ({ fetch, params, url }) => {

    const { id } = params;
    const { pathname } = url;

    return {
        post: cacheFetch<Post>(pathname, () => fetch(`/data?id=${id}`))
    };

}) satisfies PageLoad;

Cela charge les données pour la publication individuelle. Vous obtenez le id du dossier de routage basé sur des fichiers urn:uuid:304ed28e-4911-41ef-8a8e-e0879d531a38. Si vous entrez un mauvais idcela générera une erreur comme prévu.

Résultat final

Le premier chargement est plus lent, mais beaucoup plus rapide au rechargement

La première fois que vous chargez une page, vous voyez une icône de chargement. Lors des demandes ultérieures, il se charge immédiatement. C’est le pouvoir de la mise en cache. Maintenant, garde à l’esprit que ça commence réellement prélecture lorsque vous survolez le lien lui-même, pas lorsque vous cliquez dessus. C’est l’une des grandes choses de SvelteKit.

Une dernière étape

Maintenant, nous pourrions aller plus loin en mettant en cache toutes les publications lors de la récupération initiale. Étant donné que nous recevons en fait tous les messages depuis le début, il n’y a aucune raison de les récupérer à nouveau.

import type { PageLoad } from "./$types";
import { cache, cacheFetch } from "$lib/cache";

export const load = (async ({ fetch, url }) => {

    const { pathname } = url;

    const posts = await cacheFetch<Post[]>(pathname, () => fetch(`/data`));

    posts.map(post => cache.set(`${pathname}post/${post.id}`, post));

    return {
        posts
    };

}) satisfies PageLoad;

Nous pouvons simplement parcourir toutes les publications téléchargées et les placer individuellement dans le cache avec map et en important le cache depuis cache.ts directement.

Lorsque vous chargez votre page une fois, vous n’avez jamais besoin de récupérer quoi que ce soit d’autre ! Cela peut être utile pour prévenir Problème de requête N+1.

Ce n’est pas toujours une solution. Par exemple, si la page de détail de votre publication contient plus d’informations que la page de liste de publications. Vous devez décider si une récupération excessive la première fois vaut la peine de ne pas récupérer du tout sur les pages de détails des publications.

Quoi qu’il en soit, vous disposez désormais d’options de mise en cache client et vous savez comment implémenter les vôtres !

Dépôt : GitHub




Source link