CakePHP + React + Viteで　開発実行時にエラー　「 @vitejs/plugin-react can't detect preamble. Something is wrong. See 」

## 経緯
CakePHPで開発中のサービスにReact + Viteを組み込んだのですが、ディベロッパーモードがうまく稼働しませんでした。
AIとやり取りしても解決しなかったので、備忘録として記事に残します。

## 前提
・PHP、React、CakePHPがローカルにインストールされて稼働
・ローカルでウェブサーバが稼働

※今回のケースではウェブサーバは下記で立ちあげています。
```
php -S localhost:8765 -t webroot
```

## 環境
PHP 8.4.5
CakePHP 5.1.0
React 19.0.0
vite 6.2.3

CakePHP をバックエンドに、React + Vite（ホットリロード対応）をフロントエンドとして連携させた開発環境を構築しています。

## 経緯

①以下のようにtsxファイルを読みこみ
```
//templates/layout/default.php
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/src/index.tsx"></script>
```
<br>

②以下のコマンドを実行して、開発環境を立ち上げ
```
yarn dev
```
※yarn dev でViteが localhost:5173 に起動され、ReactのHMR（ホットリロード）付き開発が可能になります。

<br>
③ブラウザで表示を確認したところ、下記のエラー

```
Uncaught Error: @vitejs/plugin-react can't detect preamble. Something is wrong
```

ChatGPTに尋ねて、「ファイル読み込みに関係するエラー」であることはわかりましたが、提示された解決方法を試しても解消しませんでした。


## 解決法
以下のようにコードを修正したところ、エラーが解消されHMR付き開発が可能になりました。

```
//追記したコードーーーーーーーー
<script type="module">
    import RefreshRuntime from "http://localhost:5173/@react-refresh"
    RefreshRuntime.injectIntoGlobalHook(window)
    window.$RefreshReg$ = () => {}
    window.$RefreshSig$ = () => (type) => type
    window.__vite_plugin_react_preamble_installed__ = true
</script>
//ここまでーーーーーーーーーーーー

//下記は不要
<!-- <script type="module" src="http://localhost:5173/@vite/client"></script> -->

//以下が問題なく読み込まれるようになる
<script type="module" src="http://localhost:5173/src/index.tsx"></script>
```

## 原因
公式のIssueによれば、nodeが稼働する環境ではないと、viteクライアントを普通に読み込んでも正常稼働しないとのこと。

そのため、先述のコードを追記することが必要な模様です。

※以下は、該当箇所の引用です。

>Ah, so you are serving the HTML over your PHP server. Unfortunately because of this, Vite plugins (in this case @vitejs/plugin-react-refresh) won't be able to inject its HTML modifications.

>Since you are not using Node.js, you can't leverage Vite's programmatic API to inject those HTML modifications, so in this case you'll have to do it manually inject this code into your HTML:

## 補足：本番公開時の挙動

本番ビルドでは、ReactのHMRや@vite/clientは不要になります。
生成されたJSは静的ファイルとして動作するため、本番サーバにアップロードしても今回の問題は発生しません。

以下のように分岐を作っておけば、HMRができない環境であっても、
問題なく稼働させることが可能です。

```
<?php
//このパスはviteの設定によって異なる可能性あり
$manifestPath = WWW_ROOT . 'dist/.vite/manifest.json';
$manifest = json_decode(file_get_contents($manifestPath), true);
function assetPath($entry, $manifest) {
    return '/react-dist/' . ($manifest[$entry]['file'] ?? '');
}

<?php if (..HMRできない環境や本番環境であれば..): ?>
    <script type="module" src="<?= h(assetPath('src/main.tsx', $manifest)) ?>"></script>
<?php elseif(..HMR可能な開発環境であれば..): ?>
    <script type="module">
        import RefreshRuntime from "http://localhost:5173/@react-refresh"
        RefreshRuntime.injectIntoGlobalHook(window)
        window.$RefreshReg$ = () => {}
        window.$RefreshSig$ = () => (type) => type
        window.__vite_plugin_react_preamble_installed__ = true
    </script>
    <script type="module" src="http://localhost:5173/src/index.tsx"></script>
?>
```

## 参考
- [Vite 公式 GitHub Issue #1984](https://github.com/vitejs/vite/issues/1984)
vite-plugin-react can't detect preamble. Something is wrong." when using styled-components

## おまけ：バグ報告、もっと楽にしませんか？

もしこの記事が参考になったり、開発効率に課題を感じている方は、  
バグレポートツール「Altary」をぜひ試してみてください。
https://qiita.com/Alterly/items/a890ae9d040e4e235a26


スクリーンショットやログ、環境情報を自動で取得して、チームへの共有が驚くほど楽になります。

 公式サイト：https://altary.ip