【MSW】Svelte 開発でモックを使う方法
フロントエンドのテストにmsw
というライブラリを使うのが流行っとるみたいです。便利そうや!とおもい自分の推しフレームワークsvelte
で使ってみたところ、引っかかった部分があったので記事にしました。svelte + msw
は便利!
引っかかってしまった例
「svelte msw」で検索すると海外の技術ブログがトップで出てきました。
はじめはこのブログにしたがって実装しました。ざっくりと説明すると、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です。
<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で利用するプラグインを開発している方がいらっしゃいました。
今回はこのプラグインを導入します。このプラグインを導入することで、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
を作成します。
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
をつくります。
import { dev } from '$app/environment';
if (dev) {
const { server } = await import('./mocks/server');
server.listen();
}
アプリ起動!
npm run dev
でアプリを起動すると、mswがブラウザで動いているのがコンソールでわかります。
課題が残されている...
公式のサンプルのように実装するとうまく行ったのですが、最初に参考にした方法でなぜダメだったのかがわかっていません。
- ❌
src/routes/+layout.svelte
でdev
がTruthy時のみmswをロードする - ✅
src/hooks.client.ts
でdev
がTruthy時のみmswをロードする
1のバリエーションとして別のコンポーネントに切り出してみたり、src/routes/+layout.ts
に書いてみたりしたのですが、結局解決しませんでした。
この違いがなんなのか、ご存知の方がいらっしゃればコメントお願いします!
参考
Discussion