はじめに

Astro と Vite を使った開発で、ビルド時に生成されるファイルについて「なぜこんなにたくさんの JS ファイルが作られるの?」「ファイル名にくっついてるハッシュ値って何?」と疑問に思ったことはありませんか?

この記事では、そんな疑問を一緒に解決していきましょう!

以下について詳しく見ていきます。

  • Astro が内部で使っている Vite とは何か
  • ビルド時にどんなファイルが生成されるのか
  • JS ファイルが複数作られる理由とその仕組み
  • ファイル名のハッシュ値が果たしている役割

公式ドキュメントをベースに、分かりやすく解説していきますので、ぜひ最後まで読んでみてください。

目次

概要

Astro のビルドシステムってどんなもの?

まず、Astro がどうやってファイルをビルドしているのかを見てみましょう。Astro は内部で Vite というツールを使用していて、以下のような特徴があります。

  • SSG with selective hydration(二段階ビルド): 静的コンテンツの生成と JavaScript バンドルの最適化
  • コード分割: 自動的なコード分割による効率的な読み込み
  • アセット最適化: 画像、CSS、JavaScript の最適化
  • ハッシュベースキャッシュ: ファイル名にハッシュ値を付与したキャッシュ戦略

ビルド出力の構造

実際にビルドを実行すると、どのような構造でファイルが生成されるのでしょうか?以下は典型的なビルド出力の例です。

※ 実際のプロジェクトでは、設定やページ構成によって構造が異なります。また、ハッシュ値(a1b2c3d4のような部分)は実際異なります。

ファイル/ディレクトリ 種別 説明
dist/index.html HTML メインページ
dist/about/index.html HTML 静的ページ
dist/_astro/client.a1b2c3d4.js JavaScript クライアントサイド JS(ハッシュ付き)
dist/_astro/page.e5f6g7h8.js JavaScript ページ固有 JS
dist/_astro/styles.i9j0k1l2.css CSS 最適化された CSS
dist/assets/hero.webp 画像 最適化された画像

コード分割の仕組み

自動でコード分割してくれる仕組み

Astro の優れた機能の一つが、コードを自動的に分割してくれることです。

どういうことかというと、各ページ(.astroファイル)ごとに独立したチャンクを作成します。そして、React や Vue などのフレームワークライブラリは別のチャンクに分離してくれます。

さらに、import()を使った動的読み込みの部分は、独立したチャンクとして分割されるので、必要なときにだけ読み込まれます。

---
 // 動的インポートによる遅延読み込み
 const { Chart } = await import('./Chart.jsx');
---

チャンク生成例

ファイル名 種別 サイズ 説明
client.a1b2c3d4.js メインコード 15KB クライアントサイドの基本機能
react-vendor.e5f6g7h8.js ベンダー 120KB React 関連ライブラリ
chart.i9j0k1l2.js 動的インポート 45KB チャート機能(必要時に読み込み)
page-about.m3n4o5p6.js ページ固有 8KB about ページ専用コード

ハッシュ値を使ったキャッシュ戦略について

なぜハッシュ値が生成されるの?

ファイル名についているa1b2c3d4のような文字列、これがハッシュ値です。これはブラウザのキャッシュを効率的に活用するための仕組みです。ファイルの内容が変わったときだけハッシュ値が変わるので、変更されていないファイルはキャッシュから読み込まれ、変更されたファイルだけ新しく取得されます。

内部的には以下のような処理が行われています。

// Viteの内部的なハッシュ生成
function generateHash(content) {
  const hash = crypto
    .createHash("sha256")
    .update(content)
    .digest("hex")
    .substring(0, 8);
  return hash;
}

// ファイル内容が変更された場合のみハッシュが変更される
// 変更前: client.a1b2c3d4.js
// 変更後: client.x9y8z7w6.js

キャッシュ戦略の効果

ファイル種別 キャッシュ期間 効果
HTML 短期(数分〜数時間) 最新コンテンツの配信
JS/CSS(ハッシュ付き) 長期(1 年) 不要な再ダウンロード防止
画像・フォント 中期(1 週間〜1 ヶ月) バランスの取れたキャッシュ

実際のビルド例

仮に以下1.プロジェクト構成だった場合、ビルドされると2のようなファイルが生成されます。_astro/のファイルには、ハッシュ値が付与されているのも確認できるかと思います。

styles/global.cssにCSSスタイルを集約している場合は、以下2のような生成結果になりますが、各 .astro ファイル内に scoped でスタイルを記載している場合や Vite の設定で CSS が分割設定されている場合は、複数の .css ファイルが生成されることもあります。

また、.jsファイルに関しては、基本的に標準ではバンドルすることはできず、エントリファイル(src/でJavaScriptを使用している箇所)や Astro プロジェクト内で使用している React・Svelte などの他フレームワークに応じて対応した .js ファイルが生成される仕組みです。

1. プロジェクト構成

src/
├── pages/
│   ├── index.astro           # トップページ
│   ├── about.astro           # About ページ
│   └── blog/
│       └── [slug].astro      # 動的ルーティング
├── components/
│   ├── Header.astro
│   └── Chart.jsx             # React コンポーネント
└── styles/
    └── global.css

2. ビルド実行とファイル生成

$ npm run build

# ビルド過程の出力(簡略化)
14:32:18 [build] Building for production...
14:32:19 [build] ✓ Completed in 847ms.

dist/
├── index.html                    # 2.3 KB
├── about/index.html              # 1.8 KB
├── blog/
│   ├── post-1/index.html         # 3.1 KB
│   └── post-2/index.html         # 2.9 KB
└── _astro/
    ├── client.DwN2Y8tU.js        # 12.4 KB (gzipped: 4.2 KB)
    ├── react.BxM8K3Ls.js         # 42.1 KB (gzipped: 13.8 KB)
    ├── chart.A7sF9dG2.js         # 15.6 KB (gzipped: 5.1 KB)
    ├── header.C4nR7wQ9.js        # 3.2 KB (gzipped: 1.1 KB)
    └── styles.E8hY2nK7.css       # 4.7 KB (gzipped: 1.8 KB)

ビルドプロセスの詳細を深掘り

Tree Shaking という概念

ツリーシェイキング(tree shaking)とは、ビルド時に使われていないコード(未参照のエクスポートや副作用のない処理など)を自動で削除して、バンドルを小さくする最適化フローのことです。ビルド時に作動します。

// calculate.js
export const add = (a, b) => a + b;     // ← 使う
export const mul = (a, b) => a * b;     // ← 使わないので削除対象
export const sub = (a, b) => a - b;     // ← 使わないので削除対象 

// index.js
console.log(add(1, 2));

astro.config.mjs の設定項目

これまでのハッシュ値やコード分割など、バンドル・ビルドの設定は、astro.config.mjs で行えます。以下見ていただければ分かる通り、Vite 内部では、さまざまなツールが組み合わさって、最適なバンドル・ビルドを提供しています。その為、各ツールの設定インターフェースが提供されています。

export default defineConfig({
  vite: {
    // esbuild設定の例
    esbuild: {
      drop: ["console", "debugger"], // console.logを削除
      legalComments: "none", // コメント削除
    },
    build: {
      cssCodeSplit: false,
      treeShaking: false,
      // Rollup設定の例
      rollupOptions: {
        external: ["some-large-lib"], // 外部化
      },
    },
  },
});

ビルドエラーの基本的な対処

モジュール解決エラー

# エラー例: "Cannot resolve module"
Error: Cannot resolve module 'some-package'

解決法

  • package.jsonの依存関係を確認
  • npm installまたはnpm ciを実行
  • TypeScript の場合はtsconfig.jsonmoduleResolution設定を確認

おわりに

いかがでしたか?Astro と Vite のビルドシステムについて、少し理解が深まったでしょうか。

最初は「なぜこんなにファイルがたくさん作られるの?」という疑問から始まったかもしれませんが、実はそれぞれにちゃんと意味があることが分かったのではないでしょうか。

二段階ビルドやコード分割、ハッシュベースキャッシュなど、これらの仕組みを理解することで、より効率的な Web アプリケーション開発ができるようになります。

開発時は高速な HMR でサクサク作業でき、本番では最適化されたファイルで高速な配信ができる。そんな理想的な開発環境を、Astro と Vite が実現してくれています。HMR 様様です。大感謝。。。

読んでいただきありがとうございました。本記事が少しでも参考になれば幸いです。

参考記事