前提
この記事はWhy webpackを読みながら重要な箇所を翻訳しつつ、見やすく箇条書き形式に変えたり補足を入れたりした記事です。
このドキュメントは主に2018年に記載された様なので、現在とは状況が変わっている可能性があります。
その点は注意して下さい。
バンドラー等のツールが登場する以前の開発はどの様なものだったのか
なぜwebpackを使うべきかを理解するために、バンドラーが存在する以前でどの様にJavaScriptをwebで使っていたか総括しましょう。
ブラウザでJSを実行するには以下の2つのやり方があります。
- 機能毎にJSファイルを読み込む
- 大量のスクリプトを読み込むため、ネットワークがボトルネックになりスケールしない
- 全てを含んだ巨大なJSファイルを読み込む
- スコープやサイズ、読みやすさやメンテナンス性が犠牲になる
IIFEsという手法の登場
IIFE (Immediately Invoked Function Expression)という手法を使うことによって、各機能毎のスコープを安全に分離し、スコープの衝突を心配することなくそれぞれのファイルをつなぎ合わせる事が出来る様になりました。
(補足)
IIFEを応用する事で開発時には複数のJSファイルに分けて、リリース時にはUglifyJS等のツールを使って一つのファイルにする、と言う流れができたのだと思われます(この頃の事は筆者はあまり良く知りません)。
また、それ以外にもminify(最小化)する事でソース内のスペースやコメント等のコードと関係ない文字を取り除かれ、ファイルサイズが小さくなり、読み込みが速くなる効果もあります。
(補足ここまで)
IIFEの使用により、Make、Gulp、Grunt、Broccoli、Brunch等のツールが誕生しました。これらはタスクランナーと呼ばれ、プロジェクト内の全てのファイルをつなぎ合わせました。
(補足)GulpやGrunt自身にはJSファイルをつなぎ合わせる機能はないと思いますが、各種プラグインと組み合わせて使う事でJSファイルをつなぎ合わせたり最小化したりするタスクを作る事ができます。
しかし、一つでもファイルを修正した場合、全てをリビルドしなくてはいけません。全てのファイルをつなぎ合わせた結果、スクリプトを再利用する事は簡単になりました。
しかし、ビルド時の最適化は難しくなりました。コードが実際に使われているかどうかをどう判別すればよいか分かりません。
もしソースがlodashの関数の一つだけしか使っていなくても、ライブラリの全てを追加してつなぎ合わせなければなりません。この状態でどう依存関係をツリーシェイクすればよいのでしょうか。
遅延ローディングはスケールしにくいですし、開発者に沢山の作業を必要とします。
JavaScriptモジュールの誕生 - Node.jsに感謝
Node.jsはサーバーサイドで動作するため、scriptタグを埋め込むhtmlファイルはありません。そのため CommonJS というシステムを採用しました。
これによって require
で他のモジュールを現在のファイルに読み込んで使用する事ができる様になり、スコープの問題もそれぞれ必要なモジュールをインポートする事で解決しました。
npm + Node.js + modules – 大量流通
しかし、ブラウザーがCommonJSをサポートする事はありませんでした。
それは主に以下の様な理由からです。
- no live bindings
- CommonJSはモジュールを変数として受け取り、ES6 modulesはlive bindingsとして受け取ります
- 詳しくは What do ES6 modules export? を見て下さい
- 循環参照の解決
- 以前はNode.jsでは循環参照があるとエラーとなった様ですが、これは既に解決しているようです
- 16.3.6.1 Cyclic dependencies in CommonJS
- 同期的なモジュールの解決による読み込みの遅さ
そのため、Browserify、RequireJS、SystemJSの様なCommonJSモジュールをブラウザで動作させるバンドラーやツールが作られました。
(補足)
BrowserifyはCommonJSのモジュールの依存関係を解決しつつ、最終的には一つにファイルにまとめた形式で出力されるためバンドラーと呼び、RequireJSやSystemJSは各モジュールを読み込んで扱える様なJSライブラリなのでツールと呼んでいる様です。
Browserifyは全ての依存をバンドルする事でブラウザでrequireできる様にします、とHPに書いてあるのでバンドラーでよさそうです。
RequireJSはHP内でJSファイルとモジュールローダーと定義されています。
SystemJSはGitHubのaboutにESモジュールローダーと定義しています。
バージョンアップでCommonJSだけでなくESモジュールにも対応したのでしょう。
(補足ここまで)
ESM - ECMAScript Modules
良いニュースとしては、モジュールはECMAScriptの標準になりました。
しかし、まだブラウザのサポートは不完全かつバンドルの方がより速いので現時点では初期段階のモジュールの実装よりもバンドルが推奨されています。
(補足)
現在のJavaScriptモジュールのサポート状況です。
記事を書いている時点ではIEは当然ながらサポートされていませんが、他の主要なブラウザは以下の様な状況です。
- import
- 一部ブラウザはworkerでの動作がサポートされていない
- export
- SafariやiOSのSafari、AndroidのWebViewで一部サポートされていない
ES modulesの読み込みが遅いという点に関してはこちらのES modules基礎知識で課題感が理解できました。
要するにnpmのライブラリは小さく別れており、場合によっては数十、数百と依存しているため、これら全ての細かいJSファイルを読み込むのはHTTP/2環境でも容易ではない、という点でした。
しかし、現在では両者のいいとこ取りをする様な開発スタイルも生まれてきている様です。
ここまで見るとバンドラーが有利の様に見えますが、当然欠点もあります。ビルドに時間がかかる事です。
依存性を解決したりBabelで変換したりバンドルしたりといった処理をしていれば当然時間がかかります。
これは少し修正してローカルで動作確認し、また修正して…といった場合の体験が悪くなります。
もちろんWebPackもなるべく早くサイクルを回せる様にwatchして差分をビルドする機能がありますが、大規模なプロジェクトではメモリやCPUを圧迫すると言った問題があるようです。
なのでローカルで開発する際には外部ライブラリは予めコンパイルしておき、ES modulesとしてimportしてブラウザ側で解決(大量のファイルもローカルで読み込むので大きなネックにならない)してもらい、バンドルはしない。
プロダクションにデプロイする時はビルドする事で、両者の恩恵を受ける事ができます。
また、今後プロダクションでもES modulesを使う時が来たとしても、読み込むファイル数を減らす事は必須なため、事前に複数のファイルを纏めるバンドルは必要であると考えられており、現にその様な仕様のプロポーザルも出されている様です。
参考にした記事:
Native ESM 時代のフロントエンドビルドツールの動向
Native ESM時代とはなにか
(補足ここまで)
Automatic Dependency Collection
以前のタスクランナーやGoogle Closure Compilerでさえ開発者に手動で全ての依存性を宣言する必要がありました。
webpackの様なバンドラーは自動でimportやexportから依存性グラフを割り出して作る事ができます。
これは他のプラグインやローダーと連動して素晴らしい開発体験を提供します。
この様なツールがあるのはよい事ではないでしょうか
我々にモジュールを書ける様にしただけではなく、様々な種類のモジュールの形式がサポートされていて(少なくとも私達がESMに到達するまで)、リソースだけでなくアセットも同時に取り扱ってくれるツールがある事はよいではないでしょうか?
それがwebpackが存在する理由です。
このツールはJavaScriptアプリケーション(ESMとCommonJSどちらもサポート)、そしてその他のアセット、例えば画像やフォントやスタイルシート等もサポートを拡張できます。
webpackはパフォーマンスやロード時間にも気を付けています。改善や新しい機能の追加、例えば非同期のチャンク読み込みやプリフェッチなどをあなたのプロジェクトとユーザーの為に可能な限り最高の体験のために届けています。
終わりに
上記の内容を読んで理解した事をまとめると以下の様になりました。
- なぜタスクランナーやバンドラーを使うのか
- スコープを壊す事なく事前に一つまたは複数のファイルにビルドできる
- CommonJS、ESMの依存関係を解決し、ブラウザ/Node.jsで動作する様にしてくれる
- それ以外のタスク(BabelやTypeScriptのコンパイル等)も自動化できる
- webpackの強み
- 単体でバンドラーとして動作する
- gulp等はあくまでもBrowserifyやプラグイン等と組み合わせて使う
- 自動で依存関係グラフを作れる
- ツリーシェイクができる
- 画像などのJSファイル以外のアセットもバンドルできる
- 継続して改善や機能追加されている
- 単体でバンドラーとして動作する
webpackが人気なのは単体でバンドラーとして動作し、いくつかの強みがあるからと理解しました。
しかし、これからES modulesが本格的にプロダクションでも使用される様になると、バンドラーの位置付けもまた変わってきそうだな、と感じました。
想像していた以上に様々な周辺知識が拾えてよかったと思います。
その他に参考にしたサイト
https://stackoverflow.com/questions/22544649/why-is-it-recommended-to-use-concat-then-uglify-when-the-latter-can-do-both/27452514
https://medium.com/webpack/the-state-of-javascript-modules-4636d1774358
https://2ality.com/2015/07/es6-module-exports.html