Vite 7.1以降で外部ファイル監視時のフルリロードを復旧
Vite 7.1で導入されたEnvironment APIの変更により、@sourceディレクティブで監視される外部ファイル(PHP、HTMLなど)の変更がフルページリロードをトリガーしなくなっていた問題が修正されました。@tailwindcss/viteプラグインが新しいHMR APIに対応し、テンプレートファイルの変更が再び正しくブラウザのリロードを引き起こすようになります。
背景
Tailwind CSS v4では、CSSファイル内の@sourceディレクティブでテンプレートファイルのパスパターンを指定できます。これにより、PHPやBladeなどの非標準ファイルもTailwindの監視対象に含められます。
@import "tailwindcss";
@source "../../**/*.php";
Vite 7.0.6以前では、これらのファイルを編集すると期待通りフルページリロードが発生していました。しかしVite 7.1以降では、ファイル変更時にCSS HMR更新のみが実行され、ページ全体のリロードが行われなくなっていました。#19637がこの問題を報告しています。
Vite 7.1で導入されたEnvironment APIが、この動作変更の原因です。従来のserver.ws.sendを使ったWebSocket APIが非推奨となり、server.hot.sendを使う新しいHMR APIへの移行が推奨されています。@tailwindcss/viteプラグインは旧APIのみに対応しており、Viteのモジュールグラフに含まれない外部ファイルの変更を新しい仕組みで伝播できていませんでした。
技術的な変更
packages/@tailwindcss-vite/src/index.tsにhotUpdateフックが追加され、外部ファイルの変更を検知してフルリロードを明示的にトリガーするようになりました。
hotUpdate({ file, modules, timestamp, server }) {
// Viteのモジュールグラフに含まれないがTailwindが監視している
// ファイル(PHP、HTMLなど)の変更時にフルリロードを発火
let isExternalFile = modules.every((mod) => mod.type === 'asset' || mod.id === undefined)
if (!isExternalFile) return
for (let env of new Set([this.environment.name, 'client'])) {
let roots = rootsByEnv.get(env)
if (roots.size === 0) continue
if (!isScannedFile(file, modules, roots)) {
continue
}
let invalidatedModules = new Set<vite.EnvironmentModuleNode>()
for (let mod of modules) {
this.environment.moduleGraph.invalidateModule(
mod,
invalidatedModules,
timestamp,
true,
)
}
// Vite 7.1+では server.hot.send、それ以前では server.ws.send
if ('hot' in server && server.hot) {
server.hot.send({ type: 'full-reload', path: '*', triggeredBy: file })
} else if ('ws' in server && server.ws) {
server.ws.send({ type: 'full-reload', path: '*' })
}
}
}
フックは変更されたファイルが外部ファイル(type === 'asset'またはid === undefined)かを判定します。addWatchFileで監視対象に追加されたファイルは、Viteのモジュールグラフには存在するものの、実際には処理されていない状態として扱われます。Vite 7.0.6ではtype: 'js'とHARD_INVALIDATED状態で表現されていましたが、7.1以降ではtype: 'asset'となり、明示的なリロード処理が必要になりました。
外部ファイルと判定された場合、isScannedFileで実際にTailwindが監視しているファイルかを確認した上で、server.hot.sendまたはserver.ws.sendを使ってフルリロードを発火します。server.hotの存在確認により、Vite 7.1以降とそれ以前の両方に対応しています。
統合テストでは、Vite 6.x、7.0.8、7.1.12、7.3.1の各バージョンで動作を検証する体制が追加されました。
describe.sequential.each([['^6'], ['7.0.8'], ['7.1.12'], ['7.3.1']])(
'Using Vite %s',
(version) => {
test('external source file changes trigger a full reload', {
// テスト実装...
})
}
)
テストは、PHPファイルを@sourceで監視対象に追加し、そのファイルを編集した際にfull-reloadイベントが発生することを確認します。
設計判断
hotUpdateフックを実装する方式が採用されました。Viteの推奨する移行パスに従い、従来のhandleHotUpdateではなく新しいhotUpdateフックを使用しています。
PR内の検証では、モックプラグインで旧APIと新APIの動作を比較し、server.hot.sendの使用が必須であることを確認しています。一方で、後方互換性のためにserver.wsへのフォールバックも残されています。これにより、Vite 6.xから7.3.xまでの広範なバージョン範囲をサポートできます。
外部ファイルの判定ロジックでは、modules配列内のすべてのモジュールがtype === 'asset'またはid === undefinedの条件を満たすかをチェックしています。部分的なマッチではなく全数一致を要求することで、実際にViteで処理されているファイル(JSやCSSモジュール)を誤ってリロード対象にしないよう設計されています。
まとめ
この修正により、@sourceで監視される外部ファイルの変更が、Vite 7.1以降でも正しくフルページリロードをトリガーするようになりました。Environment APIへの移行に伴う破壊的変更に対応しつつ、旧バージョンとの互換性も維持した実装です。Tailwind CSS v4をViteで使用するプロジェクトにおいて、テンプレートファイル編集時の開発体験が復旧します。