Webpackで使うJavaScriptを全列挙するのが面倒になって、一括して読み込む方法をとっていたのですが、それに伴う代償もあることに気づいてきました。
これまでの経過
実は、以前にもこの「一括読み込み」をテーマにした記事を2本ほど書いています。
-
Webpackでフォルダ内の全ファイルを一気にrequireする -
require.contextを用いた手法 -
Webpackでの一括読み込み(その2) -
import-glob-loaderを使った手法
自分自身、一括読み込みを便利に使っていたのですが、いくつか問題が発生してきました。
requireという足かせ
もともとJavaScriptにモジュール化の仕組みはなく、Node.jsとともにCommonJSが普及していたのですが、ES6ではネイティブのモジュール(import/export)が導入されました。そして次第にJavaScriptの周辺ツールも、RollupなどES Moduleに対してネイティブに動くものが増えていっています。
WebpackでもES Moduleを直接処理できるのですが、個人的に衝撃だったのがModuleConcatenationPluginでした(過去記事)。可能な限りモジュールをフラットに開いてTree Shakingなどが行えるということで、モジュール化が文法要素であるがゆえの強力さを体感したわけです。
一方で、require()は(JavaScriptの文法で言えば)単なる関数であって、どこにでも書けます。そのため、ModuleConcatenationPluginもrequire()で呼んだ先についてはモジュールを切り出す、という形で対応することとなります。結果、require.contextで呼び出したものは、Tree Shakingなどが無効となってしまうのです。
ということで、require.contextを使い続けるのはやめて、指定されたフォルダ以下についてimportを生成するようなローダーを手で書いて対応していたりしました。
リロードで払わされる負債
ところが、今度は「よく吟味せずに、全部を一括読込できる」という発想そのものがデメリットとなる事態に見舞われました。
Webpackの開発サーバには「Hot Module Replacement」といって、JavaScriptが更新された場合に、可能であれば一部だけコードを入れ替えて、リロードなしに反映させる仕組みがあります。これを実行するには、入れ替え可能なモジュールでmodule.hot.accept()のように宣言する必要があります。
あるモジュールAでmodule.hot.accept()を適用していて、モジュールBがモジュールAからしか使われていない場合、モジュールBを更新した影響はモジュールAの入れ替えで終わる、ということになります。
…が、全部一気に読み込んでいると、モジュールBの更新は「全部読み込んだバンドル」の更新に相当することになり、結果ブラウザのフルリロードに至ってしまいます。もちろん「マウントなど、acceptするところだけを一括読み込みの対象にする」など工夫すれば対応は可能なのですが、何も考えずに全部読み込むというサボりのツケを一気に払わされた、そんな印象です。