viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方はこちらまで。
はじめに
npm workspace + Viteのモノレポ環境で、パッケージを更新した際にアプリケーション側でHMRする方法を検証してみました。
今回は2つの方法を紹介します。
環境
- node 20+
- npm 10.2.4
- vite@6.0.3
サンプルのプロジェクト構成
以下はプロジェクト構成です。
appsディレクトリにアプリケーション、packagesディレクトリにパッケージを配置しています。
.
├── package.json
├── apps
│ └── my-app
│ │ └── vite.config.mts
│ └── src/index.ts
└── packages/
├── a/
│ └── package.json
│ └── vite.config.mts
│ └── src/index.ts
├── b/
│ └── package.json
│ └── vite.config.mts
│ └── src/index.ts
各パッケージの名前は次のとおりです。
ディレクトリ | パッケージ名 |
---|---|
packages/a |
@my-org/a |
packages/b |
@my-org/b |
aliasを使用する
アプリケーションが依存するパッケージのソースを直接aliasに指定します。
import { defineConfig } from "vite";
import path from "node:path";
export default defineConfig({
resolve: {
alias: {
"@my-org/a": path.resolve(__dirname, "../../packages/a/src"),
},
},
});
全てのパッケージでHMRが有効になる一方、パッケージに更に依存するパッケージはビルド済みのファイルを参照しようとするためHMRの対象になりません。
そのため、HMRの対象にしたい全ての依存パッケージをaliasに設定する必要があります。
例えば、パッケージaがパッケージbに依存していた場合、アプリケーション側のvite.confit.mtsは以下になります。
import { defineConfig } from "vite";
import path from "node:path";
export default defineConfig({
resolve: {
alias: {
"@my-org/a": path.resolve(__dirname, "../../packages/a/src"),
"@my-org/b": path.resolve(__dirname, "../../packages/b/src"),
},
},
});
メリットとデメリット
メリット
- HMRがシンプルに動作する
- 依存関係が少ない場合は設定が簡単
- パッケージのビルドが不要
デメリット
- パッケージ間の依存関係を手動管理する必要がある
- ビルドされていないソースを参照するため、初回読み込み時、ページリロード時のパフォーマンスを低下させる可能性がある
- Viteは依存関係にあるファイルを全てブラウザで読み込むため
build --watchを使用する
次に紹介する方法は、aliasを使用せずにvite build --watch
を利用するものです。
この方法では、パッケージの変更を検知して自動的に再ビルドを行い、その結果をアプリケーション側に反映します。
アプリケーション側はHMRが有効ですが、vite build --watch
を使用しているパッケージ側はファイル変更時にフルビルドが走ります。
また、パッケージ数だけwatchのプロセスを立ち上げる必要があります。
問題点と解決策
vite build --watch
は再ビルド時に一度出力先のファイルを削除するため、HMRでwatchしていたファイルが存在しなくなり、アプリケーション側で以下のエラーが発生します。
GET https://localhost:3000/@fs/my-app/packages/a/dist/index.js?t=1734664538544 net::ERR_ABORTED 404 (Not Found)
この問題に対応するためには、vite.config.mtsのbuild.emptyOutDir
を無効にする必要があります。
emptyOutDirを無効にすることで、再ビルド時にファイルが削除されず、404エラーを回避できます。
modeがdevelopmmentの場合のみ、emptyOutDirを無効にします。
import { defineConfig } from "vite";
export default defineConfig(({ mode }) => {
return {
build: {
emptyOutDir: mode !== 'development',
},
}
});
これにより、再ビルド時に出力先ファイルが削除されなくなり、404エラーを回避できます。
メリットとデメリット
メリット
- アプリケーション側がパッケージ間の依存関係を意識する必要がなくなる
- 複雑なalias設定が不要
デメリット
- パッケージごとにwatchプロセスが必要
- 再ビルド時にフルビルドが走るため、ビルド時間が増加する
まとめ
npm workspaceのモノレポ構成 + Viteを使用する環境で、HMRを有効にして開発する2つの方法を紹介しました。
基本的にはaliasを使うことをお勧めしますが、選択肢の一つとして vite build --watch
を使用する方法も覚えておくと役に立つ時が来るかもしれません。
参考
- https://github.com/vitejs/vite/discussions/7155
- https://www.reddit.com/r/vuejs/comments/ypsju8/vite_hmr_monorepo_any_way_to_improve_this/
一緒に二次元業界を盛り上げていきませんか?
株式会社viviONでは、フロントエンドエンジニアを募集しています。
また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。