0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

viviONAdvent Calendar 2024

Day 22

npm workspace + Viteのモノレポ環境でHMRを有効にする

Last updated at Posted at 2024-12-22

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を使用する方法も覚えておくと役に立つ時が来るかもしれません。

参考

一緒に二次元業界を盛り上げていきませんか?

株式会社viviONでは、フロントエンドエンジニアを募集しています。

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?