😵

Nuxt 3のServer Routesでmswを使おうとしてハマった話

2022/06/23に公開

はじめに

弊社では、現在、社内プロダクトの開発で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とすることで、サーバでのみプラグインが適用されます)

plugins/msw.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-fetchrequire()によって読み込んでいるようです。

https://github.com/mswjs/msw/blob/2176577bebda3b6e3a3f6027f4f0ebe2974af86a/src/context/fetch.ts#L5-L6

それに対して、Nuxt 3はデフォルトでサーバを.mjs形式でビルドします。

.mjsだとrequireが未定義なため、このエラーが起きてしまうようです...

require is not definedエラーを解消するために試してみたこと

1. nuxt.config.tsvite.build.commonjsOptionsを設定する

Nuxt 3では、nuxt.config.tsviteオプションを設定することで、Viteの挙動をカスタマイズできます。

そこで、vite.build.commonjsOptionsを設定し、msw/nodeをESM形式に変換すればうまくいくのではないかと思い、試してみました。

https://github.com/vitejs/vite/blob/v2.9.9/docs/guide/dep-pre-bundling.md#monorepos-and-linked-dependencies

下記のような内容をnuxt.config.tsに追記してみました。

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 から引用

2. globalThis.requireを設定する

あまり理想的な方法ではないですが、最終的にこの方法でグローバルにrequireを設定することで解決しました...

plugins/msw.server.ts
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.tsdev_pluginsへ移動します。

$ git mv plugins/msw.server.ts dev_plugins/msw.server.ts

そして、nuxt.config.tsからdev_plugins内のプラグインを読み込むように設定します。

nuxt.config.ts
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などを活用してシステムを開発しています。

弊社チームの紹介ページがあるので、もし興味がありましたらぜひ見に来てください!

https://mediaengine.notion.site/ba128c5708fc480198f5d8c9440a7062

Discussion