Webpack?Create React App?No、Vite!! ~ ③webpackによるReact開発環境構築[開発サーバー/HMR編] ~

# 前置き
この「**Webpack?Create React App?No、Vite!!**」シリーズでは、5回に分けて以下の内容を扱います。

1. JSにおける「コンパイル」「バンドル」「ビルド」の理解
2. 表題のツールを用いたReact開発環境構築を通じて、1の内容を理解する
3. viteの技術的な凄みとその「爆速」ぶりを体感

現状以下のようなタイトルを想定しています。

①コンパイル、バンドル、ビルドを理解しよう

https://qiita.com/Yuppe-jp-eng/items/fbe8c1af9779e7ba8f87

②webpackによるReact開発環境構築[バンドラ/コンパイラ編]←前回

https://qiita.com/Yuppe-jp-eng/items/27762672599e1d4cbfa0

### ③webpackによるReact開発環境構築[開発サーバー/HMR編]←今回
④Create React AppによるReact開発環境構築
⑤ViteによるReact開発環境構築とその技術的な凄み
ここからの説明は、以下のリポジトリにあるReactを用いたコンポーネント志向の簡単なアプリをベースに進めます。手元に置いていただいて、確認しながら進めていただけるといいと思います。

https://github.com/Yuppe-jp-eng/webpack-sample


# 3.webpackによるReact開発環境構築[開発サーバー/HMR編]
前回までで一通りコンパイルとバンドルに関する設定を終えました。ただ、実際に開発をしていくとなると、このままでは非常に不便なことがあります。それは、コードの更新を確認する際にいちいち既存のコードを**手動で**ビルドし、ブラウザで動作確認/検証をする必要があります。とても面倒です。
そこで**開発サーバー**の出番です。開発サーバーの役割としては、本番さながらにHTTPリクエストを受け付け、レスポンスを返す簡易サーバーを立てることで、動作確認などといった開発者体験(DX)を高めるということが挙げられます。以下少し詳しく見ていきます。

## 3.1.開発サーバーで快適な開発環境を準備する
開発用サーバーは、開発者がローカル環境でアプリケーションを実行、テスト、デバッグするために使用されるソフトウェアツールです。開発用サーバーは以下の機能を提供します。

**ライブリロード**：
ソースコードに変更が加えられたときに**ブラウザを自動的に更新**します。これにより、開発者は変更の結果をすぐに視覚的に確認できます。

**ホットモジュールリプレースメント（HMR）**：
アプリケーションの実行中に特定のモジュール（ソースコードの一部）の変更があればそれを検知し、交換します。この際、**ブラウザを完全にリロードすることなく、特定のコードの変更を反映**できます。

**APIモック**：
フロントエンド開発では、サーバーのAPI応答を模倣（モック）することで、バックエンドのサーバーがまだ準備が整っていない場合や、オフラインで作業している場合でも開発を進めることができます。

**静的ファイルのサービス**：
開発者は開発用サーバーを使用して静的ファイル（HTML、CSS、JavaScriptなど）をブラウザに提供し、それらのファイルがブラウザで正しく動作することを確認できます。

これらの機能を提供することで、開発用サーバーは開発者が効率的に、そして問題を素早く検出して解決できる環境を提供します。ただし、注意点として開発用サーバーはあくまで開発環境で使用されることを想定したものであり、パフォーマンスやセキュリティの観点から本番環境では専用の本番用サーバーが使用されるべきです。

webpackでは、**webpack-dev-server**を使います。webpack-dev-serverはwebpackを利用した開発サーバーです。今回は、ファイルの変更を監視してブラウザをリロードするライブリロード機能[^1]とHMRについて理解し、HMRの設定を行なっていきます。セットアップする前にそれぞれの機能がどのような流れで実現されるか理解しましょう。(とりあえず動けばokな人は飛ばしても問題ないです)

## 3.2. Live Reloading
繰り返しになりますが、ライブリロードは、ソースコードに変更が加えられたときに**ブラウザを自動的に更新**する機能です。一般的には以下のような流れで行われます。

```mermaid
sequenceDiagram
    participant B as ブラウザ
    participant FDS as 開発サーバー
    participant FS as ファイルシステム
    Note over FS,FDS: コードの変更
    FDS->>FS: 更新イベントの検知
    FS-->>FDS: 更新されたファイルの読み込み
    FDS->>FDS: 変更検知したファイルをコンパイル、バンドル
    FDS->>B: ブラウザにリフレッシュの信号を送る
    B->>FDS: 更新されたファイルをリクエスト
    FDS-->>B: 更新後のファイルを返す
    B->>B: 更新されたバンドルをもとに画面をリロード
```
1. ファイルシステム[^1]でソースコードが変更されます。
2. 開発サーバーがこれを検知し、更新されたソースファイルを読み込みます。
3. 開発サーバーは更新されたソースファイルをコンパイルし、バンドルします。
4. 開発サーバーはブラウザにリフレッシュの信号を送ります。
5. ブラウザは更新されたバンドルをリクエストします。
6. 開発サーバーは更新されたバンドルをブラウザに返します。
7. ブラウザは更新されたバンドルでページをリロードします。

webpackでを用いて開発サーバーをセットアップする場合、このライブリロードか後述するHMRのどちらか一方を選択して使用することができます。[^2]

## 3.3.HMR(Hot Module Replacement)
HMRは、特定のモジュール（ソースコードの一部）を交換する機能を提供します。**ブラウザを完全にリロードすることなく、特定のコードの変更を反映**できます。より詳しい仕組みの説明に関しては以下の記事を参照していただけるといいかなと思います。


https://qiita.com/haradakunihiko/items/40486ec2b6b9aea119bb

https://rajaraodv.medium.com/webpack-hot-module-replacement-hmr-e756a726a07#.s9cn8z6mn

https://webpack.js.org/concepts/hot-module-replacement/

:::note warn
注意
一つ目の記事はwebpackのHMRの内部構造理解の参考として載せていますが、執筆年が多少古いので、一部現在のwebpackのバージョンでは不必要な設定が含まれています。(例えばwebpack.HotModuleReplacementPluginは4系以降HMR有効化の場合自動適用されます)
:::

上記記事を踏まえた上で、一般的には以下のフローで機能が実現されています。

```mermaid
sequenceDiagram
    participant B as ブラウザ
    participant HR as HMRランタイム
    participant WS as Webpackサーバ
    participant WC as Webpackコンパイラ
    participant FS as ファイルシステム
    Note over FS,WC: ソースコード変更
    FS->>WC: 変更を通知
    WC->>WC: 更新されたモジュールをコンパイル
    WC->>WS: 更新情報 (HMRマニフェスト)を通知
    WS->>HR: HMRマニフェストをWebSocket経由で送信
    HR->>WS: 更新されたモジュールをHTTP経由でリクエスト
    WS-->>HR: 更新されたモジュールを返す
    HR->>B: ページ全体のリロードなしで更新を適用
```
1. ファイルシステムでソースコードが変更されます。
2. ファイルシステムはその変更をWebpackコンパイラに通知します。
3. Webpackコンパイラは更新されたモジュールをコンパイルします。
4. Webpackコンパイラは更新情報（HMRマニフェスト）をWebpackサーバに通知します。
5. WebpackサーバはHMRマニフェストをWebSocket経由でHMRランタイムに送信します。
6. HMRランタイムは更新されたモジュールをHTTP経由でリクエストします。
7. Webpackサーバは更新されたモジュールをHMRランタイムに返します。
8. HMRランタイムは全ページのリロードなしでブラウザに更新を適用します。

## 3.4.開発サーバーとHMRの設定
事前知識の補強はおしまいにして、セットアップしていきましょう。
まずはインストールをば。

```
npm i -D webpack-dev-server 
```
configに設定を書きます。
```diff_javascript:webpack.config.js
const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.join(__dirname, "dist", "assets"),
        filename: "bundle.js"
    },
...
+    devServer: {
+        // 自動でブラウザを開く
+        open: true,
+        //開くポート
+        port: 9000,
+        // HMRの有効化(webpack4系からデフォルトで有効化しているみたいです)
+        hot: true,
+        //配信するコンテンツのルートディレクトリ
+       static: [
+            {
+                directory: path.join(__dirname, "dist"),
+            }
+        ]
+    },
};


```

次に再ビルドしたものをもとにHTMLを自動生成するプラグインを追加します。

```
npm i -D html-webpack-plugin
```
設定も
```diff_javascript:webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); 
const path = require("path");

module.exports = {
    entry: "./src/index.js",
    output: {
        path: path.join(__dirname, "dist", "assets"),
        filename: "bundle.js"
    },
...
    devServer: {
        // 自動でブラウザを開く
        open: true,
        //開くポート
        port: 9000,
        // HMRの有効化(webpack4系からデフォルトで有効化しているみたいです)
        hot: true,
+    plugins: [
+        new HtmlWebpackPlugin({
+            template: 'index.html',
+            filename: 'index.html',
+            hash: true,
+        }),
    ]
    },
};
```

これだけでいけそうですよね？残念です。上記で基本的なJSファイルのためのセットアップは完了しましたが、**Reactのための設定はまた別途必要**です。元々はreact-hot-loaderというものが使われてたようですが、現在ではreact側で**Fast Reflesh**がネイティブサポートされているようなのでそれを使います。



インストールをば。

```
npm i react-refresh
```
また、上記react-refreshとwebpack側との連携をよしなにしてくれるプラグインをインストール

```
npm i -D @pmmmwh/react-refresh-webpack-plugin
```

babel-loaderの設定にreact-refresh/babelプラグインを追加

```diff_javascript:webpack.config.js
const path = require("path");
+ const ReactRefreshPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module.exports = {
    mode: 'development',
...
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /(node_modules)/,
                loader: "babel-loader",
+                options: { plugins: ['react-refresh/babel']},
            },
        ],
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html',
            filename: 'index.html',
            hash: true,
        }),
+        new ReactRefreshPlugin()
    },
};
```

ここまでしてようやく

![画面収録-2023-07-17-14.23.56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/714305/b890a60c-37a1-d2da-0802-517c31b522b8.gif)

HMRできます。

かなり長かったですね。webpackでも設定をしっかりすればHMRを有効化できることがわかりました。というかそもそもHMRを広く広めたのはwebpackみたいですね。
ここまで2回に分けて、webpackを用いた快適なReact開発環境構築を扱ってきました。本来こんなにたくさんの設定をwebpack側にしてやる必要があるのですね。

さて、次回はCreate React AppというFaceBook社が作った開発ツールを使って、これまでやってきた過程がどのくらい短縮されるのか見ていきます。

次回が気になったり、ためになったと思ってくださった方は、ぜひいいねやストックしてくださると、今後の励みになります。よろしくお願いします。


# 参考

https://github.com/pmmmwh/react-refresh-webpack-plugin


https://qiita.com/haradakunihiko/items/40486ec2b6b9aea119bb

https://rajaraodv.medium.com/webpack-hot-module-replacement-hmr-e756a726a07#.s9cn8z6mn

https://webpack.js.org/concepts/hot-module-replacement/


[^1]:少し話が逸れますが、DockerやVMを用いて開発環境をセットアップしている場合、ローカルファイルのマウントの仕様によってはうまく機能しない場合があります。そのような事象に遭遇した場合、[この記事](https://zenn.dev/mkazutaka/articles/215042ae6e95fc#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)が参考になると思います。



[^2]:https://webpack.js.org/configuration/dev-server/#devserverlivereload