moment.js を利用する際、
const moment = require('moment');
と書いて webpack でビルドすると、**生成される bundle ファイルのサイズが非常に大きくなってしまいます。**これは、moment.js が持つすべてのロケールファイルが bundle ファイルに含まれてしまうためです。ファイルサイズにシビアなフロントエンドではかなり大きな問題です。
(上図は source-map-explorer を使って生成しました)
解決策
webpack の IgnorePlugin か ContextReplacementPlugin を使えば、この問題を回避することが出来ます。
IgnorePlugin を使う場合
「デフォルトの en 以外にロケールファイルは必要ない」という場合は、IgnorePlugin が使えます。
const webpack = require('webpack');
module.exports = {
//...
plugins: [
// Ignore all locale files of moment.js
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
};
これですべてのロケールファイルが除去されます。
また「特定のロケールだけ必要」という場合も、コードの中で require('moment/locale/xx') と書けば bundle ファイルに含めることができます。
const moment = require('moment');
require('moment/locale/ja');
moment.locale('ja');
...
この手法は create-react-app でも使われています。
ContextReplacementPlugin を使う場合
「en 以外にいくつかのロケールファイルを使いたい」というケースでは ContextReplacementPlugin が使えます。
const webpack = require('webpack');
module.exports = {
//...
plugins: [
// load `moment/locale/ja.js` and `moment/locale/it.js`
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /ja|it/),
],
};
この場合、コードの中で require('moment/locale/xx') と書く必要はありません。
const moment = require('moment');
moment.locale('ja');
...
どれぐらいサイズを減らせるか
手元で検証してみたところ、以下のような結果になりました。
| File size | Gzipped | |
|---|---|---|
| Default | 199 kB | 58.6 kB |
| IgnorePlugin | 51 kB | 17.1 kB |
| ContextReplacementPlugin | 51 kB | 17.1 kB |
どちらの方法も、かなり効果があることがわかりました。
まとめ
やや hacky ですが、現状ではこれが現実的な解決策です。
どちらのプラグインでも結果はほとんど同じですが、個人的には webpack.config.js でロケールを指定する ContentReplacementPlugin よりも、コードの中で明示的に require('moment/locale/xx') と使うロケールを指定する IgnorePlugin のほうがベターではないかと思います。
