ТБО в SvelteKit

Как внедрить MSW для локальной разработки в SvelteKit

Использование MSW в SvelteKit

Это руководство покажет вам, как внедрить библиотеку «Mock Service Worker», называемую MSW, в ваше приложение SvelteKit. Обратите внимание, что в этом руководстве показано не то, как настроить MSW с помощью Jest, а то, как использовать MSW во время живой разработки.

Полная реализация доступна в репозитории spikze.club, ссылка также в дополнении в конце страницы.

Не волнуйтесь, если поначалу это руководство может показаться вам ошеломляющим, изменения довольно просты. Использование MSW в SvelteKit для имитации запросов во время локальной разработки требует немного больше усилий.

Установка зависимостей

Во-первых, давайте установим все необходимые зависимости.

npm i -D msw ts-node concurrently

Мы также используем «ts-node» для запуска макетов сервера. Прежде чем мы продолжим, вызовите следующую команду в корне вашего проекта SvelteKit.

npx msw init ./static

Это создаст Javascript-файл с обработчиком сервисного работника по умолчанию от MSW. Мы используем «статический» для нашего приложения SvelteKit, поскольку он эквивалентен общедоступному каталогу. Наконец, давайте обновим файл package.json, чтобы MSW знал, где искать файл.

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

В качестве удобного сценария я также добавил следующую команду в свою конфигурацию «скрипты», которая необходима, если вы хотите запускать MSW как через клиент, так и через сервер (для моков на стороне сервера).

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

Подготовка MSW-модулей

Прежде чем мы начнем писать реальный код, нам нужно определить наш новый рабочий каталог для MSW в конфигурации SvelteKit, а также в конфигурации Typescript. Начнем с Typescript-файла.

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

Далее обновим 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")
        }
      }
    }
  }

Добавление макетов и обработчиков

Хорошо, вы прошли первую часть руководства! Теперь мы добавим фактический код, который выполняется при использовании MSW. Обратите внимание, что это минимальный пример, поэтому я добавил то, что необходимо, чтобы все заработало, но не более того.

Как вы могли догадаться из наших изменений в SvelteKit-config, весь код будет помещен в новый каталог с именем «msw», который находится непосредственно в «src».

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

Вот код для добавления следующих модулей. Вы должны иметь возможность просто скопировать и вставить их, имя файла + путь к каталогу записываются в виде комментария в каждом блоке вверху.

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

Одна из проблем при использовании MSW во время живой разработки заключается в том, что мы должны убедиться, что нет условий гонки. Мы должны определить порядок выполнения, иначе сервис-воркер из MSW может стать активным после того, как все запросы уже сделаны.

Для достижения этой цели мы модифицируем наш корневой файл макета. Поскольку этот файл монтируется для каждой страницы, это хорошее место, чтобы заблокировать все дальнейшее выполнение, пока MSW не завершит работу.

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

Добавление демо-страницы

В следующих фрагментах кода просто показано содержимое двух демонстрационных страниц и одной конечной точки, используемой в этом руководстве.

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

Добавление переменной среды

Мы почти закончили. Чего не хватает, так это добавить флаг «VITE_MSW_ENABLED» в нашу среду. Мы используем «.env.local» в качестве нашего файла для хранения флага, так как он будет использован Vite и не будет добавлен в git.

VITE_MSW_ENABLED=true

Запуск приложения

Хорошо, теперь все должно быть готово к использованию! Чтобы включить насмешку на клиенте, просто убедитесь, что флаг установлен. Затем запустите обычную команду «dev», чтобы имитировать запросы на стороне клиента.

npm run dev

Чтобы также имитировать запросы на стороне сервера, просто запустите новую команду, которую мы добавили в начале.

npm run dev:msw-server

Теперь вы готовы смоделировать конечные точки во время локальной разработки. Как отмечено в примере кода, эта демо-страница не включает макеты на стороне сервера, хотя для этого все подготовлено. Это означает, что локально MSW-вызов запускается только в том случае, если вы переходите с индексной страницы демонстрационного сайта на фактическую демонстрационную страницу, щелкнув ссылку.

Для имитации серверных запросов у вас будут более сложные конечные точки, которые также извлекают данные из баз данных. Затем эти запросы могут быть смоделированы в обработчике сервера. Обратите внимание, что для макетов сервера допустимы только абсолютные URL-адреса.