Webpack
ビルドツール
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/
}
};