Webpackがモジュールを2回読み込まないためにした3つのこと
Photo by Jørgen Håland on Unsplash
こんにちは、Wantedly のDX Squadでエンジニアをしている原です。 (DXはDeveloper Experienceの略で、開発者が心地よくプロダクトを作れる環境を作ることを目標に頑張る部門です)
本稿は、WANTEDLY TECH BOOK 10 から「Webpackがモジュールを2回読み込まないためにした3つのこと」という章を抜粋し加筆修正を加えたものです。ウォンテッドリーでは WANTEDLY TECH BOOK のうち最新版を除いた電子版を無料で配布しています。ぜひ読んでみてください。
以下、本文です。
Wantedlyのフロントエンドでは styled-components というライブラリを使ってReactコンポーネントにCSSを適用しています。styled-components の詳細は本稿の主旨ではないため省きますが、 styled-components はその機能を実現するために、 動的にCSSを生成して <style>
要素をページの <head>
に挿入します。この <style>
要素間の干渉を避けるためか、 styled-components は自身の2回以上の読み込みを禁止しています。実際、開発環境で styled-components 2回以上読み込んでしまうと、 hot reloading との相性問題でうまく動かない場合があります。
この問題を解決するために次の3つのことを行いました。
lockfileの確認
NpmやYarnには、同じ依存グラフの中に同じパッケージの複数バージョンが共存できるという特徴があります。これは、バージョン制約を守りつつ、依存地獄に陥らないという点ではよくできた仕組みですが、モジュールの重複読み込みを避けたい状況下では問題になります。
これを確認するには、 package-lock.json
や yarn.lock
を開いて、 styled-componentsが複数バージョン存在していないかどうかを調べればOKです。Wantedlyの場合はこの問題には該当していませんでした。
runtimeChunkの設定の確認
Webpackが生成するバンドルの本体は「モジュールID」から「モジュールのソースコード」への辞書オブジェクトのようなもので、実質的にはソースコードのzipアーカイブのようなものとしてとらえてしまって問題ありません。ということは、上に挙げたようなモジュールの重複さえなければ、styled-componentsは1度きりだけ収録されるはずで、問題は起きないような気がします。
ところが、実際には問題が起きるケースがあります。それはWebpackランタイムが複数ある場合です。
Webpackのバンドルにはモジュールのソースコードが書かれているのみなので、それを eval
(または apply)
して「モジュールの実体」を作る作業が必要です。そして、一度モジュールの実体を作ったら、次回移行の import
/ require
ではそのオブジェクトを再利用します。この処理と状態管理を行うのがWebpackランタイムです。
通常Webpackのランタイムは1個だけ存在しますが、チャンク分割をしている場合は話が違います。実は、Webpackはデフォルトで、全てのチャンクにランタイムを同梱します。実際にランタイムに意味があるのはエントリポイントのあるチャンクだけなので、読み込まれたエントリポイントの数だけ別々にランタイムが作られてしまいます。これらのランタイムはそれぞれがモジュールの読み込み済み状態を管理しているため、styled-componentsもまた複数回読み込まれてしまうという次第です。
これを回避するには runtimeChunks
というオプションを変える必要があります。ランタイムを1個にするには以下の2つの方法が考えられます。
- ランタイム専用のチャンクを作成する。このためのショートカット設定として runtimeChunk: 'single' というオプションが用意されていて、これにより runtime という名前のチャンクが作られます。
- 特定のチャンクに同梱させる。たとえば runtimeChunk: { name: 'vendor' } のように書くと、 'vendor' というチャンクにランタイムが生成され、常にそのランタイムが使われるようになります。
Wantedlyではチャンク分割の設定で、よく使うライブラリを vendor
チャンクにまとめていました。これらは必ず読み込むようにしていたので、とりあえずはランタイムも同じチャンクに入れることで問題を解決することができました。
webpack.config.jsの統合
最後にして本命だったのが「webpack.config.jsの統合」です。
実は、Wantedly Visitのメインサーバーには2つの異なるWebpack設定が混在していました。つまり、 webpack コマンドを二回実行していて、ページごとにどちらかの出力が使われていました。詳細は長くなるので、同じTech Book内の別記事を参照してください。
まとめ
JavaScriptのモジュールは副作用のないように作るのが原則ですが、設計上やむをえず重複を禁止しているモジュールもあります。このような場合、複数バージョンが混在している他にも「Webpackそのものが2つある」「Webpackのランタイムが複数回作られている」などの可能性があるため、これらを地道に潰していく必要があります。
このようなプロダクトに直接影響のない問題にじっくり取り組めたのは DX (Developer Experience) Squad という立場のおかげだと思っています。DX Squad では、これからもプロダクトを作るエンジニアを後方支援していくためにあらゆる策を尽くしていきたいと思っています。