はじめに
Rails5系から、webpackerが導入され注目を集めるwebpackですが、名前聞いたことあるし、いっちょ試してみっかとHello, World!的に触って見た人は多いのではないでしょうか?
僕も流行りに乗っかって、gulp、Browserify、Reactifyで行っていたReactのjsxのコンパイルを、webpackとbabelを用いて行うように変更したのですが、まあ、設定ファイルを作成するのが難しいわけですよ。
実際は、entryとoutputと必要ならloaderを定義するだけである程度の事はできるので、特定の目的を達成するだけなら凄くシンプルなので分かりやすいんです。関連するプラグイン豊富ですし、やれることも多いですし。
なので、厳密に言うと、関連するプラグインや設定が豊富が故に自分たちのプロダクトに合った設定ファイルを作成するのが難しいんですよね。
その設定ファイルを作成するのが難しい理由の1つに、日本語で整備されているドキュメントが少ないのと、まとまった情報が見れる箇所が少ないことがあるのではないかなーと思ってまとめを作ってみました。
ベースは、公式のこちらのlist of pluginsでまとめられているプラグインが対象ですが、良く分からないものや、Angularに傾倒しているようなものは省略しています。
対象読者は以下の2から3へとステップアップしたい人が対象です。
※ちなみに僕のレベル感が2~3くらいです。良くわからないものもあったのでただ翻訳しただけみたいなものもあります。なので、説明が間違っていたり、より良い情報があれば指摘頂けると助かります。足りないものや、理解できたオプションは後々追記していきます。
- webpackのHello, World!的なのをやってみた
- webpack.config.jsを最低限自分のプロダクトで使えるレベルに整備した
- webpack.config.jsで色々なプラグインを導入しパフォーマンスの向上を計っている
- webpack.config.jsを本番環境、開発環境で書き分け、それぞれの環境で最適なパフォーマンスを発揮している
- webpackは俺に聞け
では早速。
項目は、大きく"設定関連・出力関連・最適化関連・その他"の複数項目でかき分けています。
設定関連
ContextReplacementPlugin
ContextReplacementPluginとは、デフォルトのリソース(ディレクトリ)がresourceRegExpと一致する場合、newContentResource、newContentRecursiveまたはnewContextRegExpで解析して生成されたリソースをデフォルトのリソースとして置き換えが行われます。
new webpack.ContextReplacementPlugin(
resourceRegExp,
[newContentResource],
[newContentRecursive],
[newContentRegExp])
使用例
日付を扱いローカライゼーション(他言語)にも対応しているmoment.jsを例にあげます。
ここではローカライズしたデータを扱いたいものとします。
moment.jsでは、ローカライズされたデータは、"moment/locale/hoge"のhogeに該当する部分に配置されています。
従来:
const moment = require("moment");
moment.locale("ja"); //これだとmoment全リソースをロードするのでファイル数が大きくなる
もしくは、
const moment_{hoge} = require("moment/locale/{hoge}"); //これは必要な言語の数だけロードする必要がある
ContextReplacementPluginを用いる事で:
//・・・
plugins: [
//必要な言語を指定する。 en, ja, ...
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en|ja|.../)
],
すると、ContextReplacementPluginの中で指定された言語だけロードしたものが、requireで呼び出されるデフォルトのmomentとして上書きされるので、無駄なファイルのロードや不要な記述を削減することができます。
const moment = require("moment"); // webpackの中で指定されたlocaleだけ定義されているmomentで上書き
moment.locale("en");
moment.locale("ja");
参照サイト
IgnorePlugin
IgnorePluginは、正規表現にマッチするモジュールを作成しないようにするためのプラグインです。
"requestRegExp"に作成したくないモジュールにマッチするような正規表現を指定します。
"contextRegExp"の箇所にはオプションで、対象とするディレクトリなどを指定する事ができます。
new webpack.IgnorePlugin(requestRegExp, [contextRegExp])
使用例
先ほど、例にあげたmoment.jsで、他言語対応を考慮せず日付だけ扱いたいものとします。(デフォルトのenだけ読み込まれるようにする)
localeファイルは全て不要になるのでwebpackの中ではIgnorePluginを用いて以下のように定義を行います。
//・・・
plugins: [
// momentのlocale以下のmoduleを作成しない
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
参照サイト
ResolverPlugin
ResolverPluginとは、プラグインやnpm、bowerといったパッケージマネージャ間で保持しているプラグインの依存解決を適用するためのプラグインです。
pluginsには、依存解決をしたいpluginを定義し、typesには解決を行いたいタイプを定義します。
typesには、normal、context、loaderがあり、デフォルトではnormalが指定されています。
new webpack.ResolverPlugin(plugins, [types])
使用例
例えばwebpack + bowerな環境で、bower.jsonで管理しているライブラリを呼び出したい時
const webpack = require("webpack");
const path = require("path");
module.exports = {
// resolveのrootに、"bower_components"へのパスを定義
resolve: {
root: [path.join(__dirname, "bower_components")]
},
plugins: [
new webpack.ResolverPlugin(
new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(".bower.json", ["main"])
)
]
}
このように、resolveのrootに、"bower_components"へのパスを定義し、ResolverPluginを用いる事で、bower.jsonで管理されていたライブラリを以下のように取得することが可能になります。
var $ = require("jquery");
参照サイト
EnvironmentPlugin
EnvironmentPluginとは、process.envを介して環境変数を参照することができるようにするためのプラグインです。
従来、環境変数を参照しようと思ったら、DefinePluginを用いて以下のように定義する必要がありました。
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
})
これが、EnvironmentPluginを用いる事で以下のようにかけるようになります。
new webpack.EnvironmentPlugin(['NODE_ENV', 'DEBUG'])
以下のように、process.envが定義されていなかった時用に初期値を定義しておく事もできます。
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
DEBUG: false
})
使用例
以下のようにwebpackの設定ファイルを定義します。
const webpack = require("webpack");
module.exports = {
entry: "./entry.js",
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
DEBUG: false
})
]
}
以下のようなテストデータを準備します。
if (process.env.NODE_ENV === 'production') {
console.log('Welcome to production');
}
if (process.env.DEBUG) {
console.log('Debugging output');
}
例) "NODE_ENV=production webpack"を実行した時
// 環境変数で、NODE_ENV = productionが定義されているので、ここは出力されます。
if (process.env.NODE_ENV === 'production') {
console.log('Welcome to production');
}
// 環境変数で、DEBUGには特に定義されていないですが、configの中でデフォルトでfalseが定義されているので出力されません。
if (process.env.DEBUG) {
console.log('Debugging output');
}
このようにして、本番環境、開発環境で実行するコードを変更したい時や、テストでデバッグコードを埋め込みたい時などに使用すると良いと思います。
参照サイト
DotenvWebpack
.envファイルの中に記述された環境変数をprocess.envを介して呼ぶために従来、npmでdotenvのライブラリをインストールした後、環境変数を読み込ませたいファイルの中で以下のような記述が必要でした。
require("dotenv").config()
DotenvWebpackはdotenvを包含しているので、このライブラリを使用する事で、ファイルの中で明示的に定義された変数のみoutputファイルに含まれるようになります。
new webpack.DotenvWebpack([options])
参照サイト
出力関連
BannerPlugin
BannerPluginとは、生成されたチャンクの先頭に文字列の情報を付加するためのプラグインです。
new webpack.BannerPlugin(banner, options)
- banner: 文字列の情報。ここで入力されたものが先頭に付加されます。
- options.raw: もし、trueの場合、コメント文としてでは無く、bannerをそのまま出力します。
- options.entryOnly: もし、trueの場合、入力チャンクにのみこの情報が付加されるようになります。
使用例
//・・・
plugins: [
new webpack.BannerPlugin(
"This is a comment.",
{
raw: false,
entryOnly: true
}
)
]
これを設定ファイルに記述する事で、作成されるファイルの先頭には以下のようなコメント文が出力されるようになります。
ライセンス情報などを記述する際に使用すると良いと思います。
/*! This is a comment. */
最適化関連
DedupePlugin
DedupePluginとは、ライブラリ間で依存するモジュールの重複した出力を排除することで、ファイルサイズを効果的に減らす事ができるプラグインです。
注意: 多くの時間を費やすので、watchモードでは使用せず、本番環境向けのbuildの時にのみ使用するようにしましょう。
使用例
//・・・
plugins: [
new webpack.DedupePlugin()
]
LimitChunkCountPlugin
requireなどで分岐しすぎた依存ファイルはコンパイルした後、チャンクが小さすぎてHTTPオーバーヘッドが大きくなることがありますが、LimitChunkCountPluginを用いる事で、マージするチャンクの上限数などを設定することができます。
new webpack.optimize.LimitChunkCountPlugin(options)
- options.maxChunks: チャンク(分岐数)の最大値を設定します。(number)
- options.chunkOverhead: バイト単位の各チャンクに対する追加のオーバーヘッドを設定します。(number, default: 10000, 要求遅延を反映します)
- options.entryChunkMultiplicator: エントリチャンクのための乗算器を設定します。(number, default: 10, エントリのチャンクは10倍より少ない可能性があります)
MinChunkSizePlugin
requireなどで分岐しすぎた依存ファイルはコンパイルした後、チャンクが小さすぎてHTTPオーバーヘッドが大きくなることがありますが、MinChunkSizePluginを用いる事で、オプションで定義される最小サイズよりも小さい、小さなチャンクをマージするように設定することができます。
new webpack.optimize.MinChunkSizePlugin(options)
- options.minChunkSize: この数値より小さいチャンクはマージするといった、ベースとなる値を設定します。(number)
使用例
//・・・
plugins: [
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})
]
OccurrenceOrderPlugin
OccurrenceOrderPluginとは、モジュールとチャンクのIDを使用(出現)頻度で割り当て、頻繁に使用されるIDに、より低い(より短い)IDを割り当てる事で、予測可能なIDの作成と、合計ファイルサイズを縮小することができるようになるプラグインです。
new webpack.optimize.OccurrenceOrderPlugin(preferEntry)
"preferEntry"にはbooleanが入ります。trueを入力すると、エントリチャンクの優先順位を高くします。 これにより、エントリのチャンクは小さくなりますが、全体のサイズが大きくなります。 (推奨, default: true)
使用例
//・・・
plugins: [
new webpack.optimize.OccurrenceOrderPlugin()
]
UglifyJsPlugin
UglifyJsPluginとは、不要なスペースやコメント、改行などを削除し、ファイルサイズを圧縮するためのプラグインです。
new webpack.optimize.UglifyJsPlugin([options])
UglifyJsPluginは、optionがとても豊富です。optionを一切つけないデフォルトのままでも、十分に圧縮してくれるのですが、ライセンス情報まで削除してしまったりすることがあるので必要に合わせてoptionを使い分けるようにしましょう。
optionに関してはここに書き切ることができないので、以下の公式ページを参照してください。
使用例
//・・・
plugins: [
new webpack.optimize.UglifyJsPlugin({
// warningsは圧縮しない
compress: {
warnings: false
}
})
]
CommonsChunkPlugin
CommonsChunkPluginは、複数のエントリポイント間で共有されるモジュールを別ファイルとして作成するオプトインなプラグインです。バンドルから共通モジュールを分離することで、結果として得られるチャンクファイルを最初に一度ロードし、後で使用できるようにキャッシュに格納することができます。これにより、新しいページにアクセスするたびに大きなバンドルをロードするのではなく、ブラウザのキャッシュから共有コードをすばやく配信できるので、ページスピードの最適化が可能になります。
既に日本語で解説してくださっているページがあったので説明は省略します。以下を参照ください。
webpackのCommonsChunkPluginの使い方、使い所
AggressiveMergingPlugin
AggressiveMergingPluginとは、既に十分に圧縮されている場合にも詳細にコードを分析し共通化できそうな箇所はまとめてより積極的にコードを圧縮するためのプラグインです。
new webpack.optimize.AggressiveMergingPlugin()
使用例
const webpack = require("webpack");
module.exports = {
//・・・
plugins: [
new webpack.optimize.AggressiveMergingPlugin()
]
}
その他(プラグインのインストールが必要)
HotModuleReplacementPlugin
HotModuleReplacementPluginとは、moduleの変更を検知してページをリロードする事無く、ブラウザに変更を反映する事を可能にするプラグインです。主にwebpack-dev-serverと一緒に開発環境で用いられます。
既に日本語で解説してくださっているページがあったので説明は省略します。以下を参照ください。
ExtendedAPIPlugin
ExtendedAPIPluginとは、バンドルを変数に代入できるようにするためのプラグインです。
参照サイト
NoErrorsPlugin
NoErrorsPluginとは、コンパイル中にエラーが発生した場合、その箇所をスキップしてコンパイルを実行し、エラーが発生した箇所のassetsを出力しないようにするためのプラグインです。
使用例
const webpack = require("webpack");
module.exports = {
//・・・
plugins: [
new webpack.NoErrorsPlugin()
]
}
ProgressPlugin
ProgressPluginとは、ビルドの進捗をフックする為のプラグインです。フックした情報を使用する為のfunctionには、percentageとmessageの引数が必要です。
使用例
const webpack = require("webpack");
module.exports = {
//・・・
plugins: [
new ProgressPlugin(function(percentage, msg) {
// consoleに進捗情報を出力する
console.log((percentage * 100) + '%', msg);
})
]
}
参照サイト
WatchIgnorePlugin
WatchIgnorePluginとは、webpackのwatchモード実行時に、watchの対象外とするファイルを設定するためのプラグインです。
使用例
const webpack = require("webpack");
module.exports = {
//・・・
plugins: [
new WatchIgnorePlugin([
// node_modulesの変更は、リビルドの対象としない
path.resolve(__dirname, './node_modules/'),
]),
]
}
参照サイト
HtmlWebpackPlugin
HtmlWebpackPluginとは、webpackバンドルに対応するHTMLファイルの作成が簡単になるプラグインです。
body内にscriptタグを用いてwebpackバンドルを出力し、HTML5ファイルを作成します。
使用例
以下のような設定ファイルを書くと
const webpack = require("webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: 'index.js',
output: {
path: 'dist',
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin()]
}
以下のようなシンプルなHTML5ファイルが作成されます。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script src="index_bundle.js"></script>
</body>
</html>
参照サイト
S3Plugin
S3Pluginとは、ファイルをS3にアップロードするためのプラグイン。
使用例
const webpack = require("webpack");
const S3Plugin = require('webpack-s3-plugin');
module.exports = {
//・・・
plugins: [
new S3Plugin({
// Exclude uploading of html
exclude: /.*\.html$/,
// s3Options are required
s3Options: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: 'us-west-1'
},
s3UploadOptions: {
Bucket: 'MyBucket'
}
})
]
}
参照サイト
BellOnBundlerErrorPlugin
BellOnBundlerErrorPluginとは、ビルドエラー時に通知を行う為のプラグインです。STDERR出力にベル文字が書かれます。
使用例
const webpack = require("webpack");
const BellOnBundlerErrorPlugin = require('bell-on-bundler-error-plugin');
module.exports = {
//・・・
plugins: [
new BellOnBundlerErrorPlugin()
]
}
参照サイト
WebpackBrowserPlugin
WebpackBrowserPluginとは、webpackのビルド完了時に自動的にブラウザを立ち上げるためのプラグインです。
使用例
const webpack = require("webpack");
const WebpackBrowserPlugin = require('webpack-browser-plugin');
module.exports = {
//・・・
plugins: [
new WebpackBrowserPlugin({
browser: 'Firefox',
port: 9000,
url: 'http://192.168.3.1'
}));
]
}
参照サイト
FaviconsWebpackPlugin
FaviconsWebpackPluginとは、faviconを設定するためのプラグインです。
使用例
const webpack = require("webpack");
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
module.exports = {
//・・・
plugins: [
new FaviconsWebpackPlugin('hogehoge.png')
]
}