0からWebpackでReact + TypeScriptアプリの雛形を構築

## 初めに
create-react-app（CRA）を使えば素早くReactアプリを立ち上げることができますが、企業開発や複雑な要件に対応するには、柔軟にカスタマイズ可能なwebpackによる構築が必要となる場合があります。そこで今回は、モダンフロントエンドのビルドプロセスを理解することも兼ねて、React + TypeScriptのアプリを0からwebpackで構築しました。

## ディレクトリ構成
```bash
react-webpack-app/
├── src/
│   ├── components/     # 再利用可能なUI部品（ボタン、モーダルなど）
│   ├── pages/          # 画面ごとのReactコンポーネント（Home, Aboutなど）
│   ├── styles/         # SCSSやCSSなどのスタイルファイル
│   ├── App.tsx         # アプリケーション全体のルートコンポーネント
│   └── index.tsx       # アプリのエントリーポイント（ReactDOM.render）
├── public/
│   └── index.html      # HTMLテンプレート（Webpackで埋め込まれる）
├── test/               # ユニットテストや統合テストのコードを配置
├── webpack/            # Webpack構成ファイル群（環境ごとに分離）
│   ├── webpack.common.js  # 共通設定（entry/output/module/resolve等）
│   ├── webpack.dev.js     # 開発用設定（devServer/SourceMapなど）
│   ├── webpack.prod.js    # 本番用設定（圧縮・最適化/ファイル名にhashなど）
│   └── webpack.stg.js     # ステージング環境用設定（本番に近いが一部調整あり）
├── .babelrc            # Babelのトランスパイル設定（必要な場合）
├── tsconfig.json       # TypeScriptのコンパイル設定
├── jest.config.ts      # Jestテストの設定ファイル
├── package.json        # 依存管理、スクリプト、メタ情報
└── README.md           # プロジェクト概要やセットアップ方法の説明

```
## 使用技術

- [React](https://reactjs.org/)
- [TypeScript](https://www.typescriptlang.org/)
- [Webpack](https://webpack.js.org/)
- [SCSS](https://sass-lang.com/)
- [react-slick](https://react-slick.neostack.com/)（スライダー）
- [MUI](https://mui.com/)（UIライブラリ）
- [Jest](https://jestjs.io/ja/)（ユニットテスト）

## セットアップ

```bash
# 依存関係のインストール
npm install

# 開発サーバーの起動
npm start

# 本番ビルド
npm run build

# ステージングビルド
npm run build:stg

# テストの実行
npm test

```

## 主要機能
- SPA構成：react-routerなどの導入も容易
- SCSS対応：コンポーネントごとにスタイルを管理可能
- MUIによるUI構築：ボタンなどの共通UI部品が簡単に使える

# webpack の設定説明
環境（開発環境、ステージング環境、本番環境）ごとにWebpack設定ファイルを分割することで、それぞれの目的に応じた最適なビルド設定を管理しやすくしています。  

- 開発環境では、開発モードでのビルドを有効にし、最適化を無効化することでビルド時間を短縮し、デバッグしやすくします。
- ステージング環境では、本番環境に近い構成を保ちつつ、デバッグや検証もしやすいように調整されています。
- 本番環境では、コードの最適化・圧縮・キャッシュ制御など、パフォーマンスとセキュリティを重視します。

## webpack ソースコード説明
- webpack.common.js
共通設定
```js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

// NODE_ENV が 'production' なら本番環境とみなす
const isProd = process.env.NODE_ENV === 'production'; // ← 本番環境かどうか判定

module.exports = {
    // エントリーポイント（最初に読み込まれるファイル）
    entry: './src/index.tsx',

    // モジュール解決設定
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
        alias: {
            '@pages': path.resolve(__dirname, '../src/pages/'),
            '@components': path.resolve(__dirname, '../src/components/'),
        },
    },

    // 各種ファイルに対するローダー設定
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,    // TypeScript/TSXファイルにts-loaderを適用
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.css$/i,    // CSSファイルには style-loader と css-loader を使用
                use: [
                    'style-loader',   // スタイルを <style> タグとして挿入（開発用）
                    'css-loader',     // CSS を JavaScript に変換
                ],
            },
            {
                test: /\.scss$/,   // SCSSファイルの処理
                use: [
                    isProd ? MiniCssExtractPlugin.loader : 'style-loader',  // 本番はCSSをファイルとして分離
                    'css-loader',   // CSS を JS に変換
                    {
                        loader: 'postcss-loader',   // px→rem変換などのPostCSS処理
                        options: {
                            postcssOptions: {
                                plugins: [
                                    require('postcss-pxtorem')({
                                        rootValue: 37.5,  // 1rem = 37.5px
                                        propList: ['*'],  // すべてのプロパティのpxを変換
                                        unitPrecision: 5, // 小数点5桁まで
                                        minPixelValue: 2, // 2px未満は変換対象外
                                        exclude: /node_modules/,   // ライブラリは対象外
                                    }),
                                ],
                            },
                        },
                    },
                    'sass-loader',  // Sass/SCSS を CSS に変換
                ],
            },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',   // HTMLテンプレートファイル
        }),
    ],
    optimization: {
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    // ライブラリ（外部モジュール）を vendors.js に分離
                    test: /[\\/]node_modules[\\/]/,    
                    name: 'vendors',
                    chunks: 'all',
                },
                common: {
                    // 複数ページで使われるコンポーネントを共通化
                    test: /[\\/]src[\\/]components[\\/]/,    
                    name: 'common',
                    minChunks: 2,
                    priority: -10,
                    chunks: 'all',
                    reuseExistingChunk: true,
                },
            },
        },
    },
};
```

- webpack.dev.js
開発環境
```js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    // ソースマップを速く生成する設定で、デバッグ時に元のソースが見やすくなる
    devtool: 'eval-source-map',
    devServer: {
        static: path.resolve(__dirname, 'dist'),    // サーバが配信する静的ファイルのルートパス
        historyApiFallback: true,  // SPAでのルーティングを考慮し、404時にindex.htmlにフォールバック
        hot: true,   // ホットリロード
        port: 3001,  // ローカル開発サーバーのポート番号
        open: true,  // サーバ起動時にブラウザを自動で開く
    },
    output: {
        // 開発用ビルドの出力先ディレクトリ
        path: path.resolve(__dirname, '../dist/dev'),  
        // キャッシュ対策のためハッシュ付きファイル名に
        filename: 'bundle.[contenthash].js',     
        // ビルド前に出力先ディレクトリをクリーンにする
        clean: true,     
    }
});
```

- webpack.stg.js
ステージング環境
```js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'production',
    devtool: 'source-map', // デバッグ用ソースマップあり
    optimization: {
        // コード圧縮を無効化、可読性を保ちつつデバッグを容易に
        minimize: false,
    },
    output: {
        // ステージング用の出力ディレクトリを指定
        path: path.resolve(__dirname, '../dist/stg'),
        // キャッシュ対策としてcontenthash付きのファイル名を使用
        filename: 'bundle.[contenthash].js',
        // ビルド前に出力先ディレクトリをクリーンにする
        clean: true,
    }
});
```

- webpack.prod.js　
本番環境
```js
const { merge } = require('webpack-merge');
const path = require('path');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

module.exports = merge(common, {
    mode: 'production',
    devtool: false, // ソースマップなし
    optimization: {
        // コード圧縮を有効化してバンドルサイズを最小化
        minimize: true,
        // コード分割設定
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    // react と react-dom のみを vendor チャンクに分離
                    test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
                    name: 'vendor',
                    chunks: 'all',
                },
            },
        },
    },
    output: {
        // 本番ビルドの出力先ディレクトリを指定
        path: path.resolve(__dirname, '../dist/prod'),
        // contenthash を付けてキャッシュ破棄を自動化
        filename: 'bundle.[contenthash].js',
        // ビルド前に出力ディレクトリをクリーンにする
        clean: true,
    },
    plugins: [
        // CSS を別ファイルに抽出し、本番用に分割
        new MiniCssExtractPlugin({
            filename: 'style.[contenthash].css',
        }),
        // バンドル解析プラグイン
        new BundleAnalyzerPlugin({
            analyzerMode: 'disabled', // ← これでサーバー起動を抑止
        }),
    ],
});
```

## package.json
```json
  "scripts": {
    "start": "cross-env NODE_ENV=development webpack serve --config webpack.config.js",
    "build:stg": "cross-env NODE_ENV=staging webpack --config webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
    "test": "jest --config jest.config.ts"
  }
```

## 最後
今回の説明ではWebpack設定を中心に紹介しました。その他のソースコードや詳細な実装は、GitHubのリポジトリをご参照ください。

https://github.com/uniqueNan1018/react-webpack-app