webpackは、現代の前端開発で広く使われているモジュールバンドラーです。アプリケーションを処理する際、webpackは依存関係のグラフ(dependency graph)を再帰的に構築し、アプリが必要とするすべてのモジュールを把握した上で、それらを最小限のbundleにまとめて出力します。通常は1つのファイルとして生成され、ブラウザで読み込まれます。
このツールは高度にカスタマイズ可能であり、理解すべき4つの核心的概念があります:エントリポイント(entry)、出力設定(output)、ローダー(loader)、プラグイン(plugin)。
1. エントリポイント(Entry)
webpackはアプリケーションの全モジュール間の依存関係を解析し、その起点となる「エントリポイント」から開始して依存関係図を作成します。これは、アプリの起動元や初期モジュールと捉えることができます。
基本ルール:各ページごとに1つのエントリポイントを持つのが一般的です。単一ページアプリ(SPA)では1つ、マルチページアプリ(MPA)では複数になります。
シンプルな記法
// webpack.config.js
module.exports = {
entry: './src/index.js'
};
これは以下のような構文の簡略形です:
module.exports = {
entry: {
main: './src/index.js'
}
};
配列による複数エントリ
複数のファイルを一度に処理したい場合、配列形式で指定できます。これにより、複数の依存関係が1つの「chunk」に統合されます。
module.exports = {
entry: [
'./src/index.js',
'babel-polyfill'
]
};
オブジェクト形式
より柔軟な構成が必要な場合、オブジェクト形式を使います。各キーは出力されるバンドル名を表し、値は対応する入力ファイルです。
module.exports = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};
この形式は、複数の独立した依存関係グラフを生成できるため、多ページアプリケーションに適しています。ただし、近年のベストプラクティスでは、外部ライブラリ(vendor)は`entry`に直接含めるよりも、`DllPlugin`やコード分割によって動的に読み込む方が推奨されています。
2. 出力設定(Output)
バンドルされたコードをどこに保存するかを定義するための設定です。`output`は、webpackが出力するファイルのパスと名前を制御します。
最低限の設定
必須となるのは以下の2点:
filename:出力ファイル名path:絶対パスでの出力ディレクトリ
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
複数エントリ時の命名規則
複数のエントリがある場合、[name]というプレースホルダを使ってユニークなファイル名を生成できます。
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
};
結果:`dist/app.js` と `dist/search.js` が生成されます。
3. ローダー(Loader)
webpackはデフォルトでJavaScriptしか理解できません。そのため、他の形式のファイル(例:`.css`, `.scss`, `.jpg`, `.ts`など)をモジュールとして扱うには、ローダーを使用します。
ローダーは、ファイルが依存関係に追加される際に、その内容を変換する役割を持ちます。たとえば、CSSファイルをJSに変換したり、TypeScriptをJavaScriptにコンパイルしたりします。
基本構文
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
},
{
test: /\.ts$/,
use: 'ts-loader'
}
]
}
};
上記の構文では、testで対象ファイルを特定し、useで適用するローダーを指定しています。
ローダーのチェーン処理
複数のローダーを連続して適用できます。処理は後ろから順に実行され、最終的な出力は正しくパース可能なJavaScriptになる必要があります。
use: [
'style-loader',
{ loader: 'css-loader', options: { modules: true } },
'sass-loader'
]
ローダーの使用方法
- 構成ファイルでの定義(推奨):`module.rules`に記述。可読性と保守性が高く、デバッグも容易。
- インライン指定:`import`文に`!`でローダーを直書き。
例:`import styles from 'style-loader!css-loader?modules!./styles.css'` - CLIからの指定:
例:`webpack --module-bind css=style-loader!css-loader`
ローダーの特徴
- 複数のローダーをパイプラインのように連結可能
- 同期・非同期どちらでも動作可能
- Node.js環境で実行され、任意の処理が可能
- オプションパラメータ(クエリ文字列またはオブジェクト)を受け取る
- `package.json`に`loader`フィールドを定義することで独自ローダーを公開可能
- プラグインとの連携も可能
- 任意の追加ファイルを出力することも可能
4. プラグイン(Plugin)
ローダーは個々のファイルの変換に特化しているのに対し、プラグインはビルドプロセス全体の流れ(コンパイルやチャンクの管理など)に対して操作を行います。例えば、バンドルの圧縮、HTMLファイルの自動生成、環境ごとの最適化などが可能です。
基本構造
プラグインは`apply`メソッドを持つオブジェクトです。webpackのコンパイラがライフサイクルのタイミングでこのメソッドを呼び出します。
function BuildLoggerPlugin() {}
BuildLoggerPlugin.prototype.apply = function(compiler) {
compiler.hooks.run.tap('BuildLoggerPlugin', () => {
console.log('ビルド開始');
});
};
使用方法
プラグインは`new`でインスタンス化し、`plugins`配列に追加します。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new webpack.optimize.UglifyJsPlugin()
]
};
注意点
- 複数回同じプラグインを使う場合は、それぞれ別々のインスタンスを作成する
- Node APIでの直接適用(`compiler.apply()`)は非推奨。構成ファイルでの利用が標準的
まとめ
webpackのエントリ、出力、ローダー、プラグインは、ビルドシステムの基盤となる要素です。これらを理解し、適切に設定することで、効率的で柔軟な開発ワークフローが実現できます。実際のプロジェクトではさらに多くのオプションや組み合わせが存在するため、継続的な学習が求められます。