Astro islands で初回 JS をゼロに近づける
「非同期読み込み」を「必要な時だけハイドレート」まで分解し、3層の遅延読み込みがそれぞれ何を解決するのかを実コードで説明します。
「非同期で読み込む」という言い方は曖昧です。少なくとも3つの別のことを指し得ますし、得られる効果は桁違いに違います。
第1層:ルート単位のコード分割
もっとも基本の層です。バンドラがページごとにチャンクを切り、/ja/games/snake を開いたときにダウンロードされるのは蛇のコードだけで、2048 は付いてきません。
Vite なら import.meta.glob で足ります。
const loaders = {
...import.meta.glob('../games/*/App.vue'),
...import.meta.glob('../tools/*/App.vue'),
};
要点はこの glob が静的に解析できることです。パスを変数で組み立てるなど動的にすると、バンドラは分割を諦めて全部を1つにまとめてしまいます。
第2層:ハイドレーションの遅延
この層は Astro 固有で、ドキュメント系フレームワークに対する本当の強みです。
VitePress では defineAsyncComponent がコンポーネントの描画時に読み込みを始めます。一覧ページにカードを20枚置けば、20個のチャンクが同時に飛びます。Astro ではこう書けます。
<GameShell client:visible />
client:visible の意味は「ビューポートに入るまでコードすらダウンロードしない」。ほかに client:idle(アイドル時)、client:media(メディアクエリ一致時)、client:only(SSR をスキップ)があります。
下の小さな部品は実際にこの経路を通ります。まだハイドレートされておらず、スクロールして見えた時点で読み込まれます。
第3層:実行時のオンデマンド
最初の2層は「いつ落とすか」の話でした。この層は「そもそも落とすか」の話です。
ゲームエンジン、チャートライブラリ、Web Worker 内のデコーダ。これらは初回バンドルに入るべきではありません。JSON ツールの「差分」モードがまさにこの作りで、ボタンを実際に押したときだけ差分アルゴリズムを読み込みます。
const mod = await import('./logic/diff');
diffEntries.value = mod.diffValues(left.value, right.value);
代償
3層を合わせると、コンテンツページはほぼ JS ゼロ、アプリページはアプリ単位で分割、重い依存は操作単位で分割されます。
代わりにビルド設定と、3つの可動部からなるメンタルモデルが増えます。SSG・islands・クライアント側マウントを同時に理解していなければなりません。記事とゲームが同じサイトに同居するなら、この代償は払う価値があると考えています。

コメント
…