MSW dans SvelteKit

Comment mettre en œuvre MSW pour le développement local dans SvelteKit

Utilisation de MSW dans SvelteKit

Il s'agit d'un tutoriel qui vous montrera comment implémenter la bibliothèque "Mock Service Worker", appelée MSW, dans votre application SvelteKit. Veuillez noter que ce guide ne montre pas comment configurer MSW avec Jest, mais plutôt comment utiliser MSW pendant le développement en direct.

L'implémentation complète est disponible dans le référentiel de spikze.club, lien également dans l'addendum en fin de page.

Ne vous inquiétez pas si ce guide peut sembler écrasant au début, les changements sont assez simples. L'utilisation de MSW dans SvelteKit pour simuler des demandes lors du développement local nécessite juste un peu plus d'efforts.

Installation des dépendances

Tout d'abord, installons toutes les dépendances nécessaires.

npm i -D msw ts-node concurrently

Nous utilisons "ts-node" pour démarrer également les simulations de serveur. Avant de continuer, veuillez appeler la commande suivante à la racine de votre projet SvelteKit.

npx msw init ./static

Cela générera un fichier Javascript avec le gestionnaire de service worker par défaut de MSW. Nous utilisons "static" pour notre application SvelteKit, car c'est l'équivalent du répertoire public. Enfin, mettons à jour le fichier package.json afin que MSW sache où chercher le fichier.

{
  "scripts": ...
  ...
  "msw": {
    "workerDirectory": "static"
  }
}

En tant que script pratique, j'ai également ajouté la commande suivante à ma configuration "scripts" qui est nécessaire si vous souhaitez démarrer MSW à la fois via le client et le serveur (pour les simulations côté serveur).

{
  "scripts": {
    "dev": "svelte-kit dev",
    "dev:msw-server": "concurrently \"cd msw && ts-node server.ts\" \"npm run dev\"",
    ...
  },
}

Préparation des modules MSW

Avant de commencer à écrire du code réel, nous devons définir notre nouveau répertoire de travail pour MSW dans la configuration de SvelteKit ainsi que Typescript-config. Nous allons commencer par le fichier Typescript.

{
  "compilerOptions": {
    ...,
    "paths": {
      "$lib": ["src/lib"],
      "$lib/*": ["src/lib/*"],
      "$msw": ["src/msw"],
      "$msw/*": ["src/msw/*"]
    }
  }
}

Ensuite, mettons à jour le SvelteKit-config.

import adapter from "@sveltejs/adapter-auto";
import preprocess from "svelte-preprocess";
import path from "path";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: preprocess(),
  kit: {
    adapter: adapter(),
    vite: {
      ...,
      resolve: {
        alias: {
          $msw: path.resolve("./src/msw"),
          $lib: path.resolve("./src/lib")
        }
      }
    }
  }

Ajout des mocks et des handlers

Très bien, vous avez terminé la première partie du guide ! Nous allons maintenant ajouter le code réel qui est exécuté lors de l'utilisation de MSW. Veuillez noter qu'il s'agit d'un exemple minimal, j'ai donc ajouté ce qui est nécessaire pour que tout fonctionne, mais pas plus.

Comme vous l'avez peut-être deviné d'après nos modifications apportées au SvelteKit-config, tout le code sera placé dans un nouveau répertoire appelé "msw", qui vit directement dans "src".

|- app/
|-- src/
|--- pages/
|--- msw/
|---- fixtures/
...

Voici le code des prochains modules à ajouter. Vous devriez pouvoir simplement les copier-coller, le nom du fichier + le chemin du répertoire sont écrits sous forme de commentaire dans chaque bloc en haut.

//
// app/src/msw/handlers.server.ts
//

import { rest } from "msw";
import { values } from "./fixtures/msw-demo";

export const handlers = [
  // Here, you can mock absolute URL requests,
  // e.g. to a database. For the current implementation,
  // no data is mocked in this place.
  //
  // Note: This is also the place to mock absolute
  // SSR-imports. Everything in 'handlers.workers.ts'
  // is mocked client-side.
];
//
// app/src/msw/handlers.worker.ts
//

import { rest } from "msw";
import { values } from "./fixtures/msw-demo";

// Mock relative URLs that map to your
// routes' data endpoints. This mock only
// happens for client-side requests.
//
// Note that if you use shadow endpoints, this still works
// as the endpoint gets created by SvelteKit.
export const handlers = [
  rest.get("/msw/demo/__data.json", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json({ values }));
  })
];
//
// app/src/msw/server.ts
//

import { setupServer } from "msw/node";
import { handlers } from "./handlers.server";

export const server = setupServer(...handlers);
//
// app/src/msw/worker.ts 
//

import { setupWorker } from "msw";
import { handlers } from "./handlers.worker";

export const worker = setupWorker(...handlers);
//
// app/src/msw/fixtures/msw-demo.ts
//

export const values = ["foo", "bar"];
//
// app/src/msw/index.ts 
//

import { browser, dev } from "$app/env";

/**
 * Lazy-inject the MSW handler
 * so that no errors happen during
 * build/runtime due to invalid
 * imports from server/client.
 */
export async function inject() {
  if (dev && browser) {
    const { worker } = await import("../msw/worker");
    // For live development, I disabled all warnings
    // for requests that are not mocked. Change how
    // you think it best fits your project.
    return worker.start({ onUnhandledRequest: "bypass" }).catch(console.warn);
  }
  if (dev && !browser) {
    const { server } = await import("../msw/server");
    // Same as in worker-mock above.
    return server.listen({ onUnhandledRequest: "bypass" });
  }
}

Démarrage de MSW

Un défi lors de l'utilisation de MSW pendant le développement en direct est que nous devons nous assurer qu'il n'y a pas de condition de concurrence. Nous devons définir un ordre d'exécution, sinon le service worker de MSW pourrait devenir actif après que toutes les requêtes aient déjà été faites.

Pour atteindre cet objectif, nous modifions notre fichier de mise en page racine. Comme ce fichier est monté pour chaque page, c'est un bon endroit pour bloquer toute exécution ultérieure jusqu'à ce que MSW soit terminé.

<script>
  import "../app.css";
  import { dev } from "$app/env";
  
  // Loaded from .env.local, guide covers this
  // step in a moment.
  const isMswEnabled = dev && import.meta.env.VITE_MSW_ENABLED === "true";
  // Flag to defer rendering of components
  // until certain criteria are met on dev,
  // e.g. MSW init.
  let isReady = !isMswEnabled;
  
  if (isMswEnabled) {
    import("$msw")
      .then((res) => res.inject())
      .then(() => (isReady = true));
  }
</script>

{#if isReady}
  <slot />
{/if}

Ajout d'une page de démonstration

Les extraits de code suivants affichent simplement le contenu de deux pages de démonstration et du point de terminaison utilisé dans ce didacticiel.

<!-- app/src/routes/msw/index.svelte -->

<script>
  import DisplayProse from "$lib/display/views/DisplayProse.svelte";
  import ProminentDisplayTitle from "$lib/display/views/ProminentDisplayTitle.svelte";
  import PageLayout from "$lib/layout/views/PageLayout.svelte";
  import SectionLayout from "$lib/layout/views/SectionLayout.svelte";
</script>

<PageLayout>
  <SectionLayout withContentTopSpacing withHeaderSpacing>
    <ProminentDisplayTitle slot="header" color="primary">MSW Landing Page</ProminentDisplayTitle>
    <DisplayProse>
      <p>
        This compoonent has no purpose other than being part of an MSW demo implementation. See <a
          href="https://flaming.codes"
          alt="Link to flaming.codes with blog posts">flaming.codes</a
        > for more details.
      </p>
      <p>
        This page doesn't fetch any data for shows how client-side fetches are mocked with MSW in
        SvelteKit.
      </p>
      <p>Simply click the link below to access the page with data.</p>
      <p>
        <a href="/msw/demo" alt="Link to demo page with data">msw/demo</a>
      </p>
    </DisplayProse>
  </SectionLayout>
</PageLayout>
<!-- app/src/routes/msw/demo.svelte -->

<script lang="ts">
  import ProminentDisplayTitle from "$lib/display/views/ProminentDisplayTitle.svelte";
  import PageLayout from "$lib/layout/views/PageLayout.svelte";
  import SectionLayout from "$lib/layout/views/SectionLayout.svelte";
  export let values: string[];
</script>

<PageLayout>
  <SectionLayout withHeaderSpacing withContentTopSpacing>
    <ProminentDisplayTitle slot="header" color="primary">MSW Demo</ProminentDisplayTitle>
    <p>
      This compoonent has no purpose other than being part of an MSW demo implementation. See <a
        href="https://flaming.codes"
        alt="Link to flaming.codes with blog posts">flaming.codes</a
      > for more details.
    </p>
    <p>
      Values: {values}
    </p>
  </SectionLayout>
</PageLayout>
//
// app/src/routes/msw/demo.ts
//

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

// Just for demo purposes.
export const get: RequestHandler = async () => ({
  status: 200,
  body: {
    values: ["production", "data", "not", "msw"]
  }
});

Ajout d'une variable d'environnement

Nous avons presque terminé. Ce qui manque, c'est d'ajouter le drapeau "VITE_MSW_ENABLED" à notre environnement. Nous utilisons ".env.local" comme fichier pour contenir le drapeau, car il sera consommé par Vite et non ajouté à git.

VITE_MSW_ENABLED=true

Lancer l'application

Très bien, tout devrait être prêt à l'emploi maintenant ! Pour activer la simulation sur le client, assurez-vous simplement que l'indicateur est défini. Exécutez ensuite la commande commune "dev" pour simuler les requêtes côté client.

npm run dev

Pour simuler également les requêtes côté serveur, exécutez simplement la nouvelle commande que nous avons ajoutée au début.

npm run dev:msw-server

Vous êtes maintenant prêt à vous moquer des endpoints lors du développement local. Comme indiqué dans l'exemple de code, cette page de démonstration n'inclut pas les simulations côté serveur, bien que tout soit préparé pour cela. Cela signifie que localement, l'appel MSW n'est déclenché que si vous naviguez de la page d'index du site de démonstration à la page de démonstration réelle via un clic sur le lien.

Pour simuler des requêtes de serveur, vous aurez des points de terminaison plus complexes qui récupèrent également des données à partir de bases de données. Ces demandes peuvent ensuite être simulées dans le gestionnaire de serveur. Notez que pour les simulacres de serveur, seules les URL absolues sont valides.