Nuxt 3のServer Routesでmswを使おうとしてハマった話
はじめに
弊社では、現在、社内プロダクトの開発でNuxt 3を実験的に採用しています。
既存プロダクトの中にNuxt 2を使用して開発しているものがあり、将来的なアップデートに向けて、まずは比較的リスクの低い社内プロダクトから実験していきたいなどの意図があります。
そんな中、Nuxt 3における開発でmswを利用しようとしたところ、少々問題にハマってしまいました...
この記事では、具体的にどういう方法で問題を解消したかなどについて解説していきます。
やりたいこと
Nuxt 3にはServer Routesという機能があります。
この機能を利用すると、例えば、server/api
配下にファイルを配置することで、APIエンドポイントを定義することができます。
開発中のプロダクトの中では、このServer Routesから別サービスのAPIを呼んでいる箇所があり、この別サービスへのAPI呼び出しをmswでスタブしようと試みました。
mswサーバを起動するためのNuxtプラグインを用意する
まず、mswサーバをセットアップするために、Nuxtプラグインを用意することにしました。
plugins/msw.server.ts
という名前のファイルを用意します。(プラグインファイルの拡張子を.server.ts
とすることで、サーバでのみプラグインが適用されます)
import { resolve } from 'node:path'
export default defineNuxtPlugin(async () => {
if (process.env.MSW_ENABLED === '1' || process.env.MSW_ENABLED === 'true') {
const { setupServer } = await import('msw/node')
const { handlers } = await import('~/msw/handlers')
const server = setupServer(...handlers)
server.printHandlers()
server.listen({ onUnhandledRequest: 'error' })
}
})
この状態でNuxt 3のdevサーバを起動してみたところ、下記エラーが発生してしまいました...
[nuxt] [request error] require is not defined
require is not defined
エラーが発生する原因
msw
は内部でnode-fetchをrequire()
によって読み込んでいるようです。
それに対して、Nuxt 3はデフォルトでサーバを.mjs
形式でビルドします。
.mjs
だとrequire
が未定義なため、このエラーが起きてしまうようです...
require is not defined
エラーを解消するために試してみたこと
nuxt.config.ts
でvite.build.commonjsOptions
を設定する
1. Nuxt 3では、nuxt.config.ts
でvite
オプションを設定することで、Viteの挙動をカスタマイズできます。
そこで、vite.build.commonjsOptions
を設定し、msw/node
をESM形式に変換すればうまくいくのではないかと思い、試してみました。
下記のような内容をnuxt.config.ts
に追記してみました。
export default defineNuxtConfig({
vite: {
optimizeDeps: {
include: ['node_modules/msw/node']
},
build: {
commonjsOptions: {
include: ['node_modules/msw/node']
}
}
},
// 省略...
})
しかし、結論として、これは効果がありませんでした...
正直なところ根本原因はわかってないのですが、Viteのドキュメントを見る限り、devビルド時はViteのbuild.commonjsOptions
オプションが効いていないのが原因ではないかと疑っています...
NOTE Dependency pre-bundling only applies in development mode, and uses esbuild to convert dependencies to ESM. In production builds, @rollup/plugin-commonjs is used instead.
https://github.com/vitejs/vite/blob/v2.9.9/docs/guide/dep-pre-bundling.md#the-why から引用
globalThis.require
を設定する
2. あまり理想的な方法ではないですが、最終的にこの方法でグローバルにrequire
を設定することで解決しました...
import { resolve } from 'node:path'
export default defineNuxtPlugin(async () => {
if (process.env.MSW_ENABLED === '1' || process.env.MSW_ENABLED === 'true') {
// グローバルに`require`を設定
const { default: nodeModule } = await import('node:module')
globalThis.require = nodeModule.createRequire(resolve('./node_modules'))
const { setupServer } = await import('msw/node')
const { handlers } = await import('~/msw/handlers')
const server = setupServer(...handlers)
server.printHandlers()
server.listen({ onUnhandledRequest: 'error' })
// NOTE: もう不要なため`require`は削除
// @ts-expect-error
delete globalThis.require
}
})
これで開発時はうまくmswが効くようになりました。
しかし、このプラグイン経由でのmswの有効化による影響で、また別の大きな問題が発生してしまいました。。。
本番サーバがうまく起動しなくなった
mswによるAPIのスタブがローカルでは問題なく動いたため、ステージング環境にデプロイしたところ、サーバがうまく起動できなくなってしまいました。。。
どうやら、ステージング環境上でもplugins/msw.server.ts
が読み込んでしまっているのが原因です。
そもそも、このプラグインは開発やテストのみで使用する想定であり、本番やステージング環境などでは読み込む必要がなさそうです。
そのため、こういった開発時などのみ使用するプラグインを配置するために、dev_plugins
というディレクトリを用意することにしました。
まず、plugins/msw.server.ts
をdev_plugins
へ移動します。
$ git mv plugins/msw.server.ts dev_plugins/msw.server.ts
そして、nuxt.config.ts
からdev_plugins
内のプラグインを読み込むように設定します。
const plugins =
process.env.MSW_ENABLED === '1' || process.env.MSW_ENABLED === 'true' ? [{ src: '~/dev_plugins/msw.server.ts' }] : []
export default defineNuxtConfig({
plugins,
// 省略...
})
これにより、本番環境ではこのプラグインが読み込まれなくなるため、うまく動作するようになりました。
おわりに
以上、Nuxt 3アプリでmswを使用する方法などについて解説いたしました。
もしこの記事の内容が少しでも参考になりましたら幸いです。
最後に少し宣伝が入りますが、弊社では現在、Nuxt 3などを活用してシステムを開発しています。
弊社チームの紹介ページがあるので、もし興味がありましたらぜひ見に来てください!
Discussion