MSW im SvelteKit

So implementieren Sie MSW für die lokale Entwicklung in SvelteKit

Verwenden von MSW in SvelteKit

Dies ist ein Tutorial, das Ihnen zeigt, wie Sie die „Mock Service Worker“-Bibliothek namens MSW in Ihrer SvelteKit-Anwendung implementieren. Bitte beachten Sie, dass diese Anleitung nicht zeigt, wie Sie MSW mit Jest einrichten, sondern wie Sie MSW während der Live-Entwicklung verwenden.

Die vollständige Implementierung ist im Repository von spikze.club verfügbar, Link auch im Anhang am Ende der Seite.

Machen Sie sich keine Sorgen, wenn diese Anleitung auf den ersten Blick überwältigend wirkt, die Änderungen sind recht einfach. Die Verwendung von MSW in SvelteKit zum Nachahmen von Anfragen während der lokalen Entwicklung erfordert nur etwas mehr Aufwand.

Abhängigkeiten installieren

Lassen Sie uns zunächst alle erforderlichen Abhängigkeiten installieren.

npm i -D msw ts-node concurrently

Wir verwenden „ts-node“, um auch die Server-Mocks zu starten. Bevor wir fortfahren, rufen Sie bitte den folgenden Befehl im Stammverzeichnis Ihres SvelteKit-Projekts auf.

npx msw init ./static

Dadurch wird eine Javascript-Datei mit dem Standard-Service-Worker-Handler von MSW generiert. Wir verwenden „statisch“ für unsere SvelteKit-App, da es das Äquivalent zum öffentlichen Verzeichnis ist. Zum Schluss aktualisieren wir die package.json-Datei, damit MSW weiß, wo nach der Datei gesucht werden muss.

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

Als praktisches Skript habe ich meiner „scripts“-Konfiguration auch den folgenden Befehl hinzugefügt, der benötigt wird, wenn Sie MSW sowohl über den Client als auch über den Server (für serverseitige Mocks) starten möchten.

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

MSW-Module vorbereiten

Bevor wir mit dem Schreiben des eigentlichen Codes beginnen, müssen wir unser neues Arbeitsverzeichnis für MSW in der Konfiguration von SvelteKit sowie in Typescript-config definieren. Wir beginnen mit der Typescript-Datei.

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

Als nächstes aktualisieren wir die 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")
        }
      }
    }
  }

Hinzufügen der Mocks und Handler

In Ordnung, Sie haben es durch den ersten Teil des Leitfadens geschafft! Jetzt fügen wir den eigentlichen Code hinzu, der bei der Verwendung von MSW ausgeführt wird. Bitte beachten Sie, dass dies ein Minimalbeispiel ist, daher habe ich hinzugefügt, was notwendig ist, damit alles funktioniert, aber nicht mehr.

Wie Sie vielleicht aufgrund unserer Änderungen an der SvelteKit-Konfiguration erraten haben, wird der gesamte Code in einem neuen Verzeichnis namens „msw“ abgelegt, das sich direkt in „src“ befindet.

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

Hier ist der Code für die nächsten hinzuzufügenden Module. Sie sollten sie einfach kopieren und einfügen können, der Dateiname + Verzeichnispfad ist als Kommentar in jedem Block oben notiert.

//
// 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" });
  }
}

MSW starten

Eine Herausforderung bei der Verwendung von MSW während der Live-Entwicklung besteht darin, dass wir sicherstellen müssen, dass es keine Race-Condition gibt. Wir müssen eine Ausführungsreihenfolge definieren, sonst könnte der Servicemitarbeiter von MSW aktiv werden, nachdem alle Anfragen bereits gestellt wurden.

Um dieses Ziel zu erreichen, modifizieren wir unsere Root-Layout-Datei. Da diese Datei für jede Seite gemountet wird, ist sie ein guter Ort, um die weitere Ausführung zu blockieren, bis MSW fertig ist.

<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}

Demoseite hinzufügen

Die folgenden Codeausschnitte zeigen einfach Inhalte für zwei Demoseiten und den einen Endpunkt, der in diesem Tutorial verwendet wird.

<!-- 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"]
  }
});

Hinzufügen einer Umgebungsvariable

Wir sind fast fertig. Was fehlt, ist das „VITE_MSW_ENABLED“-Flag zu unserer Umgebung hinzuzufügen. Wir verwenden „.env.local“ als unsere Datei, um das Flag zu halten, da dies von Vite verbraucht und nicht zu Git hinzugefügt wird.

VITE_MSW_ENABLED=true

Ausführen der Anwendung

In Ordnung, alles sollte jetzt einsatzbereit sein! Um das Spotten auf dem Client zu aktivieren, stellen Sie einfach sicher, dass das Flag gesetzt ist. Führen Sie dann den üblichen „dev“-Befehl aus, um clientseitige Anfragen zu simulieren.

npm run dev

Um auch serverseitige Anfragen zu simulieren, führen Sie einfach den neuen Befehl aus, den wir zu Beginn hinzugefügt haben.

npm run dev:msw-server

Jetzt können Sie Endpunkte während der lokalen Entwicklung simulieren. Wie im Codebeispiel angemerkt, enthält diese Demoseite keine serverseitigen Mocks, obwohl alles dafür vorbereitet ist. Das bedeutet, dass der MSW-Aufruf lokal nur dann ausgelöst wird, wenn Sie von der Index-Seite der Demo-Seite über einen Klick auf den Link zur eigentlichen Demo-Seite navigieren.

Um Serveranfragen zu simulieren, haben Sie kompliziertere Endpunkte, die auch Daten aus Datenbanken abrufen. Diese Anfragen können dann im Server-Handler verspottet werden. Beachten Sie, dass für die Server-Mocks nur absolute URLs gültig sind.