MSW en SvelteKit

Kiel efektivigi MSW por loka evoluo en SvelteKit

Uzante MSW en SvelteKit

Ĉi tio estas lernilo, kiu montros al vi kiel efektivigi la bibliotekon "Mock Service Worker", caleld MSW, en via SvelteKit-aplikaĵo. Bonvolu noti, ke ĉi tiu gvidilo ne montras kiel agordi MSW kun Jest, sed prefere kiel uzi MSW dum viva disvolviĝo.

La kompleta efektivigo haveblas en la deponejo de spikze.club, ligo ankaŭ en la aldono ĉe la fino de la paĝo.

Ne zorgu, se ĉi tiu gvidilo eble aspektus superforta komence, la ŝanĝoj estas sufiĉe simplaj. Uzi MSW en SvelteKit por moki petojn dum loka evoluo nur postulas iom pli da peno.

Instalado de dependecoj

Unue, ni instalu ĉiujn necesajn dependecojn.

npm i -D msw ts-node concurrently

Ni uzas "ts-node" por ankaŭ komenci la servil-mokojn. Antaŭ ol ni daŭrigu, bonvolu voki la sekvan komandon en la radiko de via SvelteKit-projekto.

npx msw init ./static

Ĉi tio generos Javascript-dosieron kun la defaŭlta servo-laboristo de MSW. Ni uzas "senmovan" por nia SvelteKit-apo, ĉar ĝi estas la ekvivalento al la publika dosierujo. Fine, ni ĝisdatigu la package.json-file por ke MSW sciu kie serĉi la dosieron.

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

Kiel oportuna skripto, mi ankaŭ aldonis la sekvan komandon al mia "skriptoj"-agordo, kiu estas bezonata se vi volas lanĉi MSW kaj per kliento kaj ankaŭ per servilo (por servilflankaj mokoj).

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

Preparado de MSW-moduloj

Antaŭ ol ni komencas skribi realan kodon, ni devas difini nian novan labordosierujon por MSW en la agordo de SvelteKit same kiel Typescript-config. Ni komencos per la Typescript-dosiero.

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

Poste, ni ĝisdatigu la 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")
        }
      }
    }
  }

Aldonante la mokojn kaj prizorgantojn

Bone, vi trapasis la unuan parton de la gvidilo! Nun ni aldonos la realan kodon, kiu estas efektivigita kiam vi uzas MSW. Bonvolu noti, ke ĉi tio estas minimuma ekzemplo, do mi aldonis tion, kio necesas por ke ĉio funkciu, sed ne pli.

Kiel vi eble divenis el niaj ŝanĝoj al la SvelteKit-config, la tuta kodo estos metita en novan dosierujon nomitan "msw", kiu loĝas rekte en "src".

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

Jen la kodo por la venontaj aldonotaj moduloj. Vi devus povi simple kopii-alglui ilin, la dosiernomo + dosieruja vojo estas notitaj kiel komento en ĉiu bloko supre.

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

Komencante MSW

Unu defio kiam vi uzas MSW dum viva disvolviĝo estas, ke ni devas certigi, ke ne ekzistas raskondiĉo. Ni devas difini ordonon de ekzekuto, alie la servisto de MSW povus iĝi aktiva post ĉiuj petoj jam faritaj.

Por plenumi ĉi tiun celon, ni modifas nian radikan aranĝ-dosieron. Ĉar ĉi tiu dosiero estas muntita por ĉiu paĝo, ĝi estas bona loko por bloki ĉian pluan ekzekuton ĝis MSW finiĝos.

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

Aldonante demo paĝo

La sekvaj kodaj fragmentoj simple montras enhavon por du demo-paĝoj kaj la unu finpunkto uzata en ĉi tiu lernilo.

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

Aldonante mediovariablon

Ni preskaŭ finis. Kio mankas estas aldoni la flagon "VITE_MSW_ENABLED" al nia medio. Ni uzas ".env.local" kiel nian dosieron por teni la flagon, ĉar ĉi tio estos konsumita de Vite kaj ne aldonita al git.

VITE_MSW_ENABLED=true

Rulante la aplikaĵon

Bone, ĉio devus esti preta por uzi nun! Por ebligi mokadon pri la kliento, simple certigu, ke la flago estas fiksita. Rulu la komunan "dev"-komandon tiam por moki klientflankajn petojn.

npm run dev

Por ankaŭ moki servilflankajn petojn, simple rulu la novan komandon, kiun ni aldonis komence.

npm run dev:msw-server

Nun vi pretas moki finpunktojn dum loka evoluo. Kiel notite en la kodekzemplo, ĉi tiu demo-paĝo ne inkluzivas servilflankajn mokojn, kvankam ĉio estas preta por ĝi. Ĉi tio signifas, ke loke, la MSW-voko nur ekfunkciiĝas se vi navigas de la indeksa paĝo de la demonstra retejo al la reala demo-paĝo per klako sur la ligilo.

Por simuli servilpetojn, vi havos pli komplikajn finpunktojn, kiuj ankaŭ ricevas datumojn el datumbazoj. Tiuj petoj tiam povas esti mokitaj en la servilo-traktilo. Notu, ke por la servilaj mokoj, nur absolutaj URL-oj validas.