エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

ViteとVitestで開発のリズムを上げる

【Unit4 ブログリレー 1日目】

Viteは"ヴィート"と読みます♫ もう覚えましたね♫*1

こんにちは、エムスリーエンジニアリンググループの山田(@Satoki_1226)です。

本日より、Unit4でもブログリレーを行うこととなりました。Unit4は医療系ポータルサイト m3.com の開発・運営を担当するチームです。Unit4エンジニアがリレー形式でテックブログを執筆し、どんなメンバーがいるのか・どのような開発をしているのかなど、ご紹介できればと思います。

トップバッターである私からは、m3ラウンジというサービスのVite移行時に得られた知見をもとに、

  1. Vite/Vitestへの移行によって感じた「速さ」
  2. 移行時の手順とポイント

をお伝えできればと思います。Viteが気になっている方や、Vite移行の進め方に困っている方にとって少しでも参考になれば幸いです。なお掲載しているサンプルコードは、記事向けに簡略化と改変をしています。

青空の下に草木が伸び伸びと生い茂る様子。本文とは関係ありません。

m3ラウンジ

m3ラウンジとは簡単に言えば医師限定、そして実名をベースにしたオンラインコミュニケーションサービスです。m3.comの会員基盤をベースに、組織や地域、世代の枠を超えて医師の交流が広がる場となることを目指しています。m3ラウンジはフロントエンドをVue.jsで開発しており、サービスローンチ時はVue CLI Serviceを利用していましたが、先日ViteとVitestに移行しました。ここからはViteとVitestに焦点を当てますが、サービスの構成などに興味を持っていただけた場合はぜひ下記の記事も参照してください。

www.m3tech.blog

Viteは速い

ViteはネイティブESMを利用しています。その結果、アプリケーション全体を事前にバンドルする必要がなくなり、開発サーバーの起動を速くします。ソースコードが変更された際のHMRも同様にネイティブESM上で行うため、更新に伴うバンドルの再構築も高速化します。

実際に開発サーバーの起動時間を比較したものが以下になります。時間はm3ラウンジのものです。

Viteの開発サーバー起動時間
Vue CLI Serviceの開発サーバー起動時間

ツール 起動までの時間
Vite 0.6秒
Vue CLI Service 29秒

見ての通り、圧倒的にViteが速いです。時間でいえば30秒程度ですが、この差は見過ごせません。開発サーバーの起動に30秒かかると、起動待ちの時間にSlackを確認したり、コーヒーを淹れたりと、他の作業を始める人もいらっしゃると思います。Slackの確認を始めたら気づかぬ間に10分、20分と経過していることもあるのではないでしょうか?Viteではそういった事がなくなります。すぐに開発サーバーを起動できるため、開発のリズムが良くなると感じました。

Vitestも速い

VitestはViteを利用したテストフレームワークです。以下の2点において「ユニットテスト開発の速さ」を感じました。

watch mode

Vitestはデフォルトの設定時、watchモードでテストを実行します。watchモードにおいては、テストの実行後もファイルの変更を検知すると関連のある部分のみを自動的に再実行します。ここでVite serverが威力を発揮します。ViteのHMRが高速であるため、テストの再実行も非常に速く、自身の変更が即座にテスト結果として反映されるため、リズムに乗って開発を進めることができます。リズムに乗るどころか、Vitestに置き去りにされる感覚を覚えることもあるため、実装を早く終わらせる力が身につきます。

UI mode

Vitestの実行時にuiオプションを指定することで、テストの実行結果をVite serverの提供するUI上で確認できます*2

vitest --ui

デフォルトではlocalhost:51204/__vitest__/でUIエディタが開きます。もちろんターミナル上でもテストの結果は確認できますが、ターミナルをスクロールして失敗したテストを探す手間が省けます。また、テストコードの修正と再実行をVitest UI上で行うことも可能です。

画像は公式より。ダークモードに切り替えることも出来ます。

CIも速くしたい

実装のリズムを速めたら、次はCIも速めたくなりますね。これはJestにもある機能ですが、shardオプションを利用することでユニットテストの分割実行が可能です。

vitest --shard <shard>

例えばshard数を4にした場合、全体のテストを4分割して並列実行できるため、CIにおけるテストの実行時間を短縮できます*3。m3ラウンジではGitLabを利用しているため、ここではGitLab CIにおける設定ファイルの記載例を示します。

 test:
   stage: test
+  parallel: 4
   script:
     - yarn install
+    - yarn test:unit --shard $CI_NODE_INDEX/$CI_NODE_TOTAL
-    - yarn test:unit

ただし注意点として、分割実行した場合、テストカバレッジが下がる傾向にあります。coverageThresholdを設定している場合、本来は問題ないカバレッジが下がってテストが失敗する、なんてこともあります。この問題を解決するためには、nyc を利用して各shardのカバレッジレポートを一度マージし、マージされた結果を元に出力するなどの対応が必要です。以下に例を載せておきます。

# test実行ジョブに追加
 test:
   stage: test
   parallel: 4
   script:
     - yarn install
     - yarn test:unit --shard $CI_NODE_INDEX/$CI_NODE_TOTAL
+    - mv src/coverage/coverage-final.json src/coverage/$CI_NODE_INDEX.json
+  artifacts:
+    paths:
+      - vue/src/coverage/


# ジョブの実行後にカバレッジをマージするジョブ
coverage:
  stage: test
  needs:
    - test
  script:
    - yarn install
    - mkdir -p vue/src/coverage-result
    - npx nyc merge src/coverage/ src/coverage-result/merge.json     # 分割されたカバレッジをマージ
    - npx nyc report --reporter=text-summary -t src/coverage-result/ # マージされた結果を元に出力
  coverage: '/Statements   : (\d+\.?\d*)%/'

なおすでにお気づきかもしれませんが、CIを速くするための分割実行なのに、結果的にジョブが増えています。最終的にどちらが速いのかはよく考えましょう。

移行時のポイント

ここまで、ViteとVitestによる開発の速さについて説明してきました。ここからは、移行における手順とポイントを記載します。

Vite移行の手順とポイント

  1. Viteのインストールとnpmスクリプトの修正(公式ドキュメント
  2. 環境変数の書き方を修正(公式ドキュメント
  3. vue.configをvite.configに移行

の3つが主な手順です。

ここでは3について、個人的にポイントと感じた点を記載します。Viteは設定の容易さも売りの1つですが、もともとwebpackやvue.config.jsで複雑な設定をしていた場合、残念ながら設定パズルから逃れることはできません。

debug mode

起動時にdebugオプションを指定することで、デバッグログを出力できるため、移行中は付与することをおすすめします。

vite --debug

「とりあえずViteにしたら画面が真っ白…」という場合などは、ログを確認すると原因がわかるかもしれません。実際、僕は自身の書いた設定によってリクエストが意図せぬ形でモックされていることに気づくことができました。

マルチページビルド

m3ラウンジには、PC向けとスマートフォン(以下SP)向けの2つのエントリーページがあります。そのためマルチページへの対応が必要になりますが、Viteではvite.config.tsbuildオプションにrollupOptionsを指定することで、複数のページをビルドできます。Viteは本番向けビルドにRollupを利用しているため、Rollupのオプションを指定します。以下に例を載せておきます。

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        pc: resolve(root, 'pc-index.html'),
        sp: resolve(root, 'sp-index.html'),
      },
    },
  },
})

上記の例では、PC向けとSP向けにそれぞれpc-index.htmlsp-index.htmlを用意してビルドしています。その上で、別途nginxの設定でPC向けとSP向けの振り分けを行っています。Viteはプロジェクトルートにあるvite.config.js(ts)の設定をもとに、index.htmlファイルをアプリケーションのエントリーポイントとして扱います。Vue CLI利用時にpublic/に配置していたindex.htmlはデフォルトの設定では認識されません。m3ラウンジではsrc/rootに設定した上で、src/配下にpc-index.htmlsp-index.htmlを移動させました*4。 なお、PCサイトとSPサイトの構成について気になる方は、下記の記事も参照してください。 www.m3tech.blog

開発サーバーの設定

フロントエンド開発において、バックエンドへのリクエストをモックして開発することも多々あると思います。m3ラウンジにおいても、webpack-dev-serverを利用してAPIをモックしていました。Viteにもデフォルトでserverが用意されており、ここにAPIモックを追加できます。

// webpack-dev-serverの設定例
setupMiddlewares(middlewares, devServer) => {
  devServer.app.get("/api/v1/my-api", (req, res) => {
    return res.writeHead(200).send(JSON.stringify({}));
  });
}


// viteの設定例
configureServer(server) {
  server.middlewares.use((req, res, next) => {
    if (req.url?.startsWith("/api/v1/my-api")) {
      return res.writeHead(200).end(JSON.stringify({}));
    }
    next();
  });
},

ビルドされるアセット

ビルドで生成されるアセットの配置先はassetsDir、またはrollupOptionsoutputで指定できます。デフォルトではassets/配下になります。パスペースでのアクセス制御を入れている場合、Vue CLIの利用時と配置先が変わる可能性があるので注意が必要です*5

Vitest移行の手順とポイント

最後に、Vitestへの移行についてです。VitestはViteを利用しており、Jestとの互換性も意識して作られています。m3ラウンジではユニットテストのライブラリとしてvue-test-utilsを利用していますが、比較的容易に移行できました。

  1. jestviに置換する(公式ドキュメント
  2. npmスクリプトを変更する(公式ドキュメント
  3. Vitest用の設定を追加する

の3つが主な手順です。ポイントは、3においてVitest専用の設定ファイルが不要であることです。Vite移行時に作成したvite.config.tsにテスト用の設定を追加することで、Vitestの設定ができます。

+ /// <reference types="vitest" />
 export default defineConfig({
+   test: {
+     // vitestの設定
+   },
 })

Vitest用にvitest.config.tsを作成して設定もできますが、同じファイルに書くことが推奨されています*6。その他、移行の際にはマイグレーションガイドも参考にしてください。

終わりに

ここまでお読みいただき、ありがとうございました。ViteやVitestについては、まだ公式ドキュメントだけでは扱うのが難しい場面もあると思いますが、本記事が少しでも役に立てば幸いです。

We are hiring!

エムスリーには今回出てきたm3ラウンジをはじめ、多種多様なサービスと技術があります。興味を持っていただいた方は、下記よりお問い合わせください。 jobs.m3.com

*1:公式ドキュメントで発音を確認できます

*2:ライブラリの追加が必要です:公式

*3:テストの実行時間のみではなく、実行環境の起動時間なども大きなウェイトを占める点はご留意ください

*4:Viteに倣うなら、pc/index.html, sp/index.htmlという配置がよかったかもしれません。今回は既存のnginxの設定をそのまま利用するようにしました。

*5:m3ラウンジはAWS ALBで制御していたため、QA時に気づくことになりました

*6:公式より