Webpack

ビルドツールJavaScriptモジュールバンドラーフロントエンド設定最適化

ビルドツール

Webpack

概要

Webpackは、JavaScriptアプリケーション用の静的モジュールバンドラーです。アプリケーションを処理するとき、内部的にプロジェクトが必要とするすべてのモジュールをマッピングし、1つ以上のバンドルを生成する依存関係グラフを構築します。2012年にTobias Koppers氏によって開発が開始され、現在はフロントエンド開発のデファクトスタンダードとして広く採用されています。React、Vue.js、Angularなどの主要フレームワークで標準的に使用され、複雑なアセット処理、コード分割、開発サーバー、ホットリロードなど豊富な機能を提供します。

詳細

主要機能

  • モジュールバンドリング: ES6 modules、CommonJS、AMDなど様々なモジュールシステムをサポート
  • ローダーシステム: TypeScript、Sass、CSSなど多様なファイル形式の変換処理
  • プラグインアーキテクチャ: HTMLの生成、ファイルの最適化、環境変数の注入など拡張可能
  • コード分割: 動的importやSplitChunksPluginによる効率的なバンドル分割
  • 開発サーバー: ホットモジュールリプレースメント(HMR)対応の高速開発環境
  • 最適化機能: Tree shaking、ファイル圧縮、画像最適化など本番用最適化

アーキテクチャ

Webpackは設定駆動型のビルドツールで、エントリーポイントから依存関係を辿り、ローダーとプラグインを通じてアセットを変換・最適化してバンドルを出力します。モジュールファイレーション、チャンクグラフ、依存関係解決エンジンが中核を担います。

エコシステム

webpack-dev-server、webpack-merge、webpack-bundle-analyzerなど豊富なツールチェーンを持ち、Create React App、Vue CLI、Angular CLIなどの主要開発ツールに統合されています。

メリット・デメリット

メリット

  • 豊富な機能: ローダー、プラグインによる高度なカスタマイズが可能
  • 成熟したエコシステム: 豊富なコミュニティプラグインと詳細なドキュメント
  • フレームワーク統合: React、Vue、Angularでの標準サポート
  • 柔軟な設定: 複雑な要件にも対応できる設定の柔軟性
  • コード分割: 効率的なバンドル分割とlazy loading
  • 開発体験: HMRによる高速な開発サイクル
  • 本番最適化: Tree shaking、圧縮、キャッシュ最適化

デメリット

  • 学習コスト: 設定が複雑で初心者には習得困難
  • ビルド速度: 大規模プロジェクトでは遅くなる傾向
  • 設定の複雑さ: 高度な設定には深い理解が必要
  • 新世代ツールとの競合: ViteやTurborepoなどより高速なツールの登場
  • デバッグの困難さ: バンドル後のエラー追跡が困難
  • メモリ使用量: 大規模プロジェクトで高いメモリ消費

参考ページ

書き方の例

インストールと基本セットアップ

# Webpackのインストール
npm install --save-dev webpack webpack-cli

# 開発サーバーのインストール
npm install --save-dev webpack-dev-server

# 基本的なローダーのインストール
npm install --save-dev babel-loader css-loader style-loader

# プロジェクト作成と初期化
npm init -y
npm install webpack webpack-cli --save-dev

# package.jsonにスクリプト追加
# "scripts": {
#   "build": "webpack --mode production",
#   "dev": "webpack --mode development",
#   "start": "webpack serve --mode development"
# }

基本的なwebpack.config.js設定

const path = require('path');

module.exports = {
  // エントリーポイント
  entry: './src/index.js',
  
  // 出力設定
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true  // ビルド前に出力ディレクトリをクリア
  },
  
  // モード設定
  mode: 'development',
  
  // 開発サーバー設定
  devServer: {
    static: './dist',
    hot: true,  // ホットリロード有効
    port: 3000,
    open: true  // ブラウザを自動で開く
  },
  
  // ローダー設定
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset/resource'
      }
    ]
  }
};

高度な設定(プロダクション用)

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendor.js'  // ベンダーライブラリ用のエントリー
  },
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',  // キャッシュバスティング
    chunkFilename: '[name].[contenthash].chunk.js',
    clean: true
  },
  
  mode: 'production',
  
  // ソースマップ設定
  devtool: 'source-map',
  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', {
                targets: '> 0.25%, not dead',
                useBuiltIns: 'usage',
                corejs: 3
              }]
            ]
          }
        }
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,  // CSSを別ファイルに抽出
          'css-loader',
          'postcss-loader'  // PostCSS(autoprefixerなど)
        ]
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024  // 8KB以下はインライン
          }
        },
        generator: {
          filename: 'images/[name].[hash][ext]'
        }
      }
    ]
  },
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[name].[contenthash].chunk.css'
    })
  ],
  
  // 最適化設定
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true  // console.logを削除
          }
        }
      }),
      new CssMinimizerPlugin()
    ],
    
    // コード分割設定
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          enforce: true
        }
      }
    },
    
    // ランタイムチャンクの分離
    runtimeChunk: {
      name: 'runtime'
    }
  }
};

マルチ環境設定(webpack.config.js)

const path = require('path');
const { merge } = require('webpack-merge');

// 共通設定
const commonConfig = {
  entry: './src/index.js',
  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      }
    ]
  },
  
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

// 開発環境設定
const developmentConfig = {
  mode: 'development',
  devtool: 'eval-source-map',
  
  devServer: {
    static: './dist',
    hot: true,
    port: 3000
  },
  
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

// 本番環境設定
const productionConfig = {
  mode: 'production',
  devtool: 'source-map',
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
    clean: true
  },
  
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ],
  
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
};

// 環境に応じた設定をエクスポート
module.exports = (env, argv) => {
  if (argv.mode === 'development') {
    return merge(commonConfig, developmentConfig);
  }
  
  if (argv.mode === 'production') {
    return merge(commonConfig, productionConfig);
  }
  
  return commonConfig;
};

カスタムローダーとプラグインの使用

// webpack.config.js - 高度なカスタマイズ例
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: './src/index.js',
  
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  
  module: {
    rules: [
      // TypeScript設定
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      },
      
      // PostCSS + Tailwind CSS
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  require('tailwindcss'),
                  require('autoprefixer'),
                ]
              }
            }
          }
        ]
      },
      
      // Vue.js単一ファイルコンポーネント
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      
      // 多言語化ファイル
      {
        test: /\.(yml|yaml)$/,
        type: 'json',
        use: 'yaml-loader'
      }
    ]
  },
  
  plugins: [
    // 環境変数の注入
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.API_URL': JSON.stringify(process.env.API_URL)
    }),
    
    // バンドル分析
    new (require('webpack-bundle-analyzer')).BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false
    })
  ],
  
  resolve: {
    extensions: ['.tsx', '.ts', '.js', '.vue'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'components': path.resolve(__dirname, 'src/components')
    }
  }
};

パフォーマンス最適化の設定

// パフォーマンス最適化に特化した設定
module.exports = {
  // ... 他の設定
  
  optimization: {
    // Tree shakingの有効化
    usedExports: true,
    sideEffects: false,
    
    // 詳細なコード分割設定
    splitChunks: {
      chunks: 'all',
      minSize: 20000,
      maxSize: 250000,
      cacheGroups: {
        // React関連ライブラリ
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          chunks: 'all',
          priority: 20
        },
        
        // ベンダーライブラリ
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10
        },
        
        // 共通モジュール
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  },
  
  // キャッシュ設定
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },
  
  // パフォーマンスヒント
  performance: {
    hints: 'warning',
    maxEntrypointSize: 250000,
    maxAssetSize: 250000
  }
};

開発効率化のための設定

// 開発時の効率化設定
module.exports = {
  // ... 他の設定
  
  devServer: {
    // ホットリロード設定
    hot: true,
    liveReload: false,
    
    // プロキシ設定(APIサーバー)
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: { '^/api': '' },
        changeOrigin: true
      }
    },
    
    // HTTPS設定
    https: true,
    
    // 詳細なエラー表示
    client: {
      overlay: {
        errors: true,
        warnings: false
      }
    },
    
    // ヘッダー設定
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },
  
  // 開発時のソースマップ
  devtool: 'eval-source-map',
  
  // ファイル監視設定
  watchOptions: {
    aggregateTimeout: 300,
    poll: 1000,
    ignored: /node_modules/
  }
};