SvelteKit'te MSW

SvelteKit'te yerel geliştirme için MSW nasıl uygulanır?

SvelteKit'te MSW'yi kullanma

Bu, MSW olarak adlandırılan "Mock Service Worker" kitaplığının SvelteKit uygulamanızda nasıl uygulanacağını gösteren bir eğitimdir. Lütfen bu kılavuzun Jest ile MSW'nin nasıl kurulacağını değil, canlı geliştirme sırasında MSW'nin nasıl kullanılacağını gösterdiğini unutmayın.

Uygulamanın tamamı spikze.club deposunda mevcuttur, bağlantı ayrıca sayfanın sonundaki ekte.

Bu kılavuz ilk başta bunaltıcı görünebilirse endişelenmeyin, değişiklikler oldukça basittir. Yerel geliştirme sırasında isteklerle alay etmek için SvelteKit'te MSW'yi kullanmak biraz daha fazla çaba gerektirir.

Bağımlılıkları yükleme

İlk olarak, gerekli tüm bağımlılıkları yükleyelim.

npm i -D msw ts-node concurrently

Sunucu alaylarını da başlatmak için “ts-node” kullanıyoruz. Devam etmeden önce, lütfen SvelteKit projenizin kök dizininde aşağıdaki komutu arayın.

npx msw init ./static

Bu, MSW tarafından varsayılan hizmet çalışanı işleyicisine sahip bir Javascript dosyası oluşturacaktır. Genel dizine eşdeğer olduğu için SvelteKit uygulamamız için "statik" kullanıyoruz. Son olarak, MSW'nin dosyayı nerede arayacağını bilmesi için package.json dosyasını güncelleyelim.

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

Uygun bir komut dosyası olarak, MSW'yi hem istemci hem de sunucu aracılığıyla başlatmak istiyorsanız (sunucu tarafı alaylar için) gerekli olan "komut dosyaları" yapılandırmama aşağıdaki komutu da ekledim.

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

MSW modüllerinin hazırlanması

Gerçek kodu yazmaya başlamadan önce, Typescript-config'in yanı sıra SvelteKit'in yapılandırmasında MSW için yeni çalışma dizinimizi tanımlamamız gerekiyor. Typescript dosyasıyla başlayacağız.

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

Ardından, SvelteKit-config'i güncelleyelim.

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

Alayları ve işleyicileri ekleme

Pekala, kılavuzun ilk bölümünü başardınız! Şimdi MSW kullanırken yürütülen gerçek kodu ekleyeceğiz. Lütfen bunun minimal bir örnek olduğunu unutmayın, bu yüzden her şeyin çalışması için gerekli olanı ekledim, daha fazlasını değil.

SvelteKit-config'te yaptığımız değişikliklerden tahmin edebileceğiniz gibi, tüm kodlar doğrudan "src" içinde bulunan "msw" adlı yeni bir dizine yerleştirilecektir.

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

İşte eklenecek sonraki modüllerin kodu. Bunları basitçe kopyalayıp yapıştırabilmeniz gerekir, dosya adı + dizin yolu en üstteki her blokta bir yorum olarak yazılır.

//
// 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'yi başlatma

Canlı geliştirme sırasında MSW kullanırken karşılaştığımız zorluklardan biri, yarış koşulu olmadığından emin olmamız gerektiğidir. Bir yürütme sırası tanımlamamız gerekiyor, aksi takdirde MSW'den gelen hizmet çalışanı, tüm istekler yapıldıktan sonra aktif hale gelebilir.

Bu amaca ulaşmak için kök düzen dosyamızı değiştiriyoruz. Bu dosya her sayfaya eklendiğinden, MSW bitene kadar diğer tüm yürütmeleri engellemek için iyi bir yerdir.

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

Demo sayfası ekleme

Aşağıdaki kod parçacıkları, yalnızca iki demo sayfasının içeriğini ve bu eğitimde kullanılan bir uç noktayı gösterir.

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

Bir ortam değişkeni ekleme

Neredeyse tamamız. Eksik olan, ortamımıza “VITE_MSW_ENABLED” bayrağını eklemektir. Bayrağı tutmak için dosyamız olarak “.env.local” kullanıyoruz, çünkü bu Vite tarafından tüketilecek ve git'e eklenmeyecektir.

VITE_MSW_ENABLED=true

Uygulamayı çalıştırma

Pekala, artık her şey kullanıma hazır olmalı! İstemcide alay etmeyi etkinleştirmek için bayrağın ayarlandığından emin olun. Ortak "dev" komutunu çalıştırın, ardından istemci tarafı istekleriyle alay edin.

npm run dev

Sunucu tarafı istekleriyle de alay etmek için başlangıçta eklediğimiz yeni komutu çalıştırmanız yeterlidir.

npm run dev:msw-server

Artık yerel geliştirme sırasında uç noktalarla alay etmeye hazırsınız. Kod örneğinde belirtildiği gibi, bu demo sayfası, her şey bunun için hazırlanmış olmasına rağmen, sunucu tarafı mock'ları içermez. Bu, yerel olarak, MSW çağrısının yalnızca, bağlantıya bir tıklama yoluyla demo sitesinin dizin sayfasından asıl demo sayfasına giderseniz tetikleneceği anlamına gelir.

Sunucu isteklerini simüle etmek için veritabanlarından da veri alan daha karmaşık uç noktalarınız olacaktır. Bu istekler daha sonra sunucu işleyicisinde alay edilebilir. Sunucu alayları için yalnızca mutlak URL'lerin geçerli olduğunu unutmayın.