- 巨大バンドルの原因は「ライブラリが重い」ではありません
- なぜSPAはバンドルが1つに集まるのか
- Tree Shakingとは何をしているのか
- Tree Shakingが効かないパターン
- ライブラリが巨大化する理由
- さらに大きな原因:共有依存
- コードスプリッティングの役割
- よくある誤解
- 実際に起きる問題
- 対策の優先順位
- まとめ
巨大バンドルの原因は「ライブラリが重い」ではありません
SPA(Single Page Application)でパフォーマンスが悪化すると、よく「このライブラリが重い」「Reactが重い」という話になります。
しかし実際の現場で計測すると、原因の多くは特定のライブラリではありません。
結論から言うと、SPAで巨大バンドルが生まれる本当の理由は
“アプリケーションの構造”がバンドルに反映されてしまうからです。
そして、その解決策としてよく語られるTree Shakingには明確な限界があります。
なぜSPAはバンドルが1つに集まるのか
従来のMPA(Multi Page Application)では、ページごとにJavaScriptが分かれていました。
- /login → login.js
- /products → products.js
- /admin → admin.js
一方SPAではルーティングがブラウザ内で行われます。
つまり最初のアクセス時に「全ルートの可能性」を読み込む設計になります。
ここで問題が発生します。
例えば、次のような構成です。
- 商品一覧ページ
- 管理画面
- グラフ表示
- エディタ機能
ユーザが最初に開いたのがログイン画面でも、ビルドされたJavaScriptには管理画面用コードも含まれます。
これが巨大バンドルの出発点です。
Tree Shakingとは何をしているのか
Tree Shakingは「使っていないコードを削除する最適化」です。
ES Modulesの静的解析により、importされていない関数を除去します。
import { add } from './math.js'
この場合、math.jsに100個関数があってもaddしか残りません。
ここまでは正しい理解です。
しかしここに大きな誤解があります。
Tree Shakingはファイル単位の最適化ではないという点です。
Tree Shakingが効かないパターン
副作用(side effect)
import './initAnalytics'
このようなコードは削除できません。
なぜなら、何が実行されるか解析できないためです。
ライブラリの多くは初期化処理を持っています。
その時点でTree Shakingは止まります。
CommonJS
const lib = require('library')
CommonJSは動的読み込みです。
ビルド時に使用箇所を特定できないため、丸ごと含まれます。
動的参照
obj[methodName]()
これも解析不能です。
バンドラは安全のため削除しません。
つまりTree Shakingは「静的に証明できる不要コード」しか消せません。
ライブラリが巨大化する理由
モダンフロントエンドでは、1つのライブラリが複数の機能を持ちます。
- UIコンポーネント
- 国際化
- 日付処理
- バリデーション
例えば日付ライブラリをimportすると、ロケール情報やパーサまで入ることがあります。
実際にはformatしか使っていなくてもです。
import dayjs from 'dayjs'
この1行が数十KBになる場合もあります。
さらに大きな原因:共有依存
SPAの巨大バンドルの最大要因は、実はアプリ側のコードです。
例:
- 共通レイアウトがグラフコンポーネントをimport
- グラフがchartライブラリをimport
- すべてのページがchartを読み込む
すると、ログイン画面でもグラフライブラリが含まれます。
つまり問題はライブラリではなく依存関係の方向です。
コードスプリッティングの役割
巨大バンドルの対策はTree Shakingではなくコードスプリッティングです。
const AdminPage = React.lazy(() => import('./AdminPage'))
これにより、管理画面のコードはアクセス時に初めて読み込まれます。
ここで初めて「ルート単位」の最適化が行われます。
Tree Shaking:不要な関数を削除
Code Splitting:不要な機能を読み込まない
役割が違います。
よくある誤解
「Webpackの設定を変えれば軽くなる」という考えは半分しか正しくありません。
バンドルサイズはビルドツールより、コンポーネント設計の影響の方が大きいです。
特に注意すべきは共通モジュールです。
- utilsに何でも入れる
- index.tsで全部export
- barrel export
この構成は全ページに依存を広げます。
export * from './components'
この書き方は便利ですが、Tree Shakingを妨げる場合があります。
実際に起きる問題
巨大バンドルは単に読み込みが遅くなるだけではありません。
- JSパース時間増加
- コンパイル時間増加
- メインスレッドブロック
- LCP悪化
回線速度より、ブラウザの実行時間がボトルネックになります。
特にモバイル端末では顕著です。
対策の優先順位
効果が高い順です。
- ルート単位のコードスプリッティング
- 管理画面の分離
- 重いライブラリの遅延ロード
- Tree Shaking最適化
Tree Shakingは最後です。
削減量は数KB〜数十KBに留まることが多いです。
まとめ
SPAのパフォーマンスは、ビルド設定やフレームワークよりも依存設計に左右されます。
巨大バンドルは「ツールの問題」ではなく「構造の問題」です。
Tree Shakingは重要な最適化ですが、万能ではありません。
使っていないコードを消すことはできますが、使う可能性のあるコードは消せません。
SPAを軽くする本質は、コードを削ることではなく必要なときまで読み込まない設計です。
どれだけ小さくするかより、どれだけ遅らせるかが体感速度を決めます。