🤖

【MSW】Svelte 開発でモックを使う方法

2023/12/07に公開

フロントエンドのテストにmswというライブラリを使うのが流行っとるみたいです。便利そうや!とおもい自分の推しフレームワークsvelteで使ってみたところ、引っかかった部分があったので記事にしました。svelte + mswは便利!

引っかかってしまった例

「svelte msw」で検索すると海外の技術ブログがトップで出てきました。

https://flaming.codes/posts/msw-in-sveltekit-for-local-development

はじめはこのブログにしたがって実装しました。ざっくりと説明すると、src/routes/+layout.tsのscriptタグで開発時のみmswをロードする感じです。

npm run devではうまく動作したのですが、この状態でnpm run buildするとエラーが発生しました。

% npm run build

> my-app@0.0.1 build
> vite build


vite v4.4.9 building SSR bundle for production...
✓ 80 modules transformed.
✓ built in 380ms
[commonjs--resolver] No known conditions for "./browser" specifier in "msw" package
error during build:
Error: No known conditions for "./browser" specifier in "msw" package

mswは開発時のみ必要なのですが、おそらくビルド時にもロードされてしまっています。develop時のみロードするように書いたつもりなのですが。

*ひと様の、それも言語が異なる方の記事に文句をつけるようになってしまうのはよくないとわかっていますが、ディスっているわけではなく対応策をシェアするのがこの記事の目的です。

それではイチから始めます。

svelteのセットアップ

まずsvelteの環境を整えます。この記事ではアプリ名をmy-appとします。

npm create svelte@latest my-app
cd my-app
npm install

モックできていることを確認するためにsrc/routes/+page.svelteを編集してサンプルをつくります。/loginにpostをすると許可されるというfetchです。

+page.svelte
<script lang="ts">
	let isLoggedIn = false;

	function handleLogin() {
		fetch('/login', { method: 'POST' })
			.then((res) => {
				return res.json;
			})
			.then((_data) => {
				isLoggedIn = true;
			});
	}
</script>

<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

{#if isLoggedIn}
	<p>Logged in. Enjoy!</p>
{:else}
	<p>NOT logged in</p>
	<button on:click={handleLogin}>LOG IN</button>
{/if}

*このコードではログインに失敗してもログインしたかのように表示されます。修正はご自身でやってみてください。

vite-plugin-mswの導入

svelteはビルドツールにviteを利用しています。mswをviteで利用するプラグインを開発している方がいらっしゃいました。

https://github.com/iodigital-com/vite-plugin-msw

今回はこのプラグインを導入します。このプラグインを導入することで、mswを利用するときに、開発時のみ必要となるmockServiceWorker.jsファイルをviteが自動で用意してくれます。このプラグインなしでmswのドキュメントにしたがって自分でmockServiceWorker.jsを配置しても動くのですが、build時にややこしいことになります。

MSWリポジトリのイシューではvite-plugin-staticというプラグインがオススメされていました。こちらでもいいと思いますよ)。

プラグインのインストール

npm install --save-dev @iodigital/vite-plugin-msw

パッケージマネージャは適宜読みかえてください。

モックを定義する

次にモックを定義します。src/mocksディレクトリを作成し、handlers.tsを作成します。たとえば「/loginにpostするとログインが成功する」とすると、こんな感じです。

import { http, HttpResponse } from 'msw';

export const handlers = [
	http.post('/login', () => {
		sessionStorage.setItem('is-authenticated', 'true');

		return HttpResponse.json({ status: 200 });
		}
	)
]

このハンドラーを編集・追加することで自由にモックをしてくれます。

viteに伝える

そしてvite.config.tsにプラグインを使うように書きます。

+ // プラグインをインポート
+ import msw from "@iodigital/vite-plugin-msw";
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

+ // 先ほど定義したハンドラーをインポート
+ import { handlers } from "./src/mocks/handlers";

+ // プラグインに渡す
export default defineConfig({
	plugins: [
		sveltekit(),
+		msw({ handlers})],
	test: {
		include: ['src/**/*.{test,spec}.{js,ts}']
	}
});

mockServiceWorker.jsをリポジトリに含めておくとビルド時に除外する必要があり面倒やったのでこのプラグインで助かります。

mswの導入

さて、mswをインストールしsvelteから呼び出すところまでやりましょう。

svelte hooksの作成

Hooksといってもreactのhooksとは違います。svelteのhooksはフレームワークの一部を上書きしたい場合に使います。src/hooks.client.tsを作成します。

hooks.client.ts
import { dev } from '$app/environment';

if (dev) {
	const { worker } = await import('./mocks/browser');

	await worker.start({
		onUnhandledRequest(request, print) {
			// Do not warn on unhandled internal Svelte requests.
			// Those are not meant to be mocked.
			if (request.url.includes('svelte')) {
				return;
			}

			print.warning();
		}
	});
}

ほぼ公式の例のまんまです。develop時のみ有効になるように環境変数devを使っています。

ついでにテストなどで使うかもしれないのでサーバーサイドも書いておきましょう。src/hooks.server.tsをつくります。

hooks.server.ts
import { dev } from '$app/environment';

if (dev) {
	const { server } = await import('./mocks/server');

	server.listen();
}

アプリ起動!

npm run devでアプリを起動すると、mswがブラウザで動いているのがコンソールでわかります。

キャプチャー画像。コンソールでmswが動いていることがわかる

課題が残されている...

公式のサンプルのように実装するとうまく行ったのですが、最初に参考にした方法でなぜダメだったのかがわかっていません。

  1. src/routes/+layout.sveltedevがTruthy時のみmswをロードする
  2. src/hooks.client.tsdevがTruthy時のみmswをロードする

1のバリエーションとして別のコンポーネントに切り出してみたり、src/routes/+layout.tsに書いてみたりしたのですが、結局解決しませんでした。

この違いがなんなのか、ご存知の方がいらっしゃればコメントお願いします!

参考

https://github.com/iodigital-com/vite-plugin-msw
https://github.com/mswjs/examples-new/tree/main/examples/with-svelte
https://kit.svelte.jp/docs/hooks
https://github.com/mswjs/msw/discussions/712#discussioncomment-4385920

Discussion