AstroでSvelteを使ってみた

## はじめに
最近はフロントエンド制作環境に **Astro** を採用する機会が増えてきました。

特にクライアントサイドでリアクティブなコンテンツを構築する場合、 **宣言型UIフレームワーク** を組み合わせることで効率的な開発が可能になります。

今回は Astro と組み合わせて使った **Svelte** について、その特徴や実装方法を具体例を交えながらご紹介します。

## Astro とは
Astro とは [GitHub](https://github.com/withastro/astro) でスター数約50kを獲得している人気の **静的サイトジェネレーター** です。
**コンポーネント分離** や **SSR（サーバーサイドレンダリング）** に強みがあり、大規模な静的サイトの制作に適しています。

React、Preact、Svelte、Vue、SolidJS、AlpineJS のような人気フレームワークをサポートしていることも魅力です。

https://docs.astro.build/ja/getting-started/


## Svelteとは
Svelte は **宣言的UI** と **リアクティブ** な実装を特徴とするフレームワークですが、React やVue とは異なるアプローチを採用しています。
- React や Vue は **仮想DOM** を用いてユーザーのブラウザ上で多くの処理を行います
- Svelte はビルド時に **コンパイル** を行い、最適化された **バニラJavaScript** を生成します

このため、ランタイムの負荷が軽減され、高速なパフォーマンスを実現できます。
似たような特徴の他のフレームワークとしては **SolidJS** などが挙げられます。

また、Svelte ではシンプルな構文でリアクティブな状態管理や DOM 操作が可能です。
さらに **slide** や **fade** といったトランジション効果がデフォルトで組み込まれており、アニメーションも簡単に実装できます。

https://svelte.jp/

## 実装フロー
今回は、クライアントサイドで動作するサイトを想定し、以下の2種類の「リンクリスト」を作成します。

1. 静的なリスト（固定データを使用）
2. 動的なリスト（ API から取得したデータを使用）

### 1. Svelte コンポーネントを作成する
以下は、動的なリストを表示するための Svelte コンポーネント例です。
```javascript:List.svelte
<script>
  import { onMount } from 'svelte';

  // リアクティブな変数を宣言
  let data;
  let isLoading = true;

  // コンポーネントがDOMにマウントされると実行（エラーハンドリングは省略）
  onMount(async () => {
    const response = await fetch('/list.json');
    data = await response.json();
    isLoading = false;
  });
</script>

{#if isLoading}
  <p>Loading...</p>
{:else}
  {#if data.length > 0}
    <ul>
      {#each data as item}
        <li><a href={item.link}>{item.label}</a></li>
      {/each}
    </ul>
  {:else}
    <p>データがありません。</p>
  {/if}
{/if}

<style lang="scss">
  /* 必要に応じてスタイルを記述 */
</style>
```
**ポイント解説**

・`let` のリアクティブ変数 : 
Svelte では `let` で宣言するだけでリアクティブな変数になります。
ただこちらは **バージョン5** において記述方法が変更されております。
まだ公式ドキュメントも 4 のままなので今回は 4 の記述方法ですすめます。

・`onMount` :
コンポーネントが初めてDOMにマウントされた際、一度だけ実行されるライフサイクル関数です。
API呼び出しや初期化処理に適しています。

・`{#each}` 構文 :
配列データを繰り返し処理してリスト表示を簡潔に実装できます。

:::note info
今回はコードを簡潔にするため TypeScript にはしておりませんでしたが `lang="ts"` で利用できます。
:::

:::note info
バージョン5については後述します。
:::

### 2. Astro で Svelte コンポーネントを読み込む
次に、上記で作成した Svelte コンポーネントを Astro で使用します。
```javascript:index.astro
---
import Layout from '../layouts/Layout.astro';
import List from '../components/List.svelte';

const linkLists = [
  {
    'link': 'https://example.com/',
    'label': 'サンプル1'
  },
  {
    'link': 'https://example.com/',
    'label': 'サンプル2'
  },
  {
    'link': 'https://example.com/',
    'label': 'サンプル3'
  },
]
---

<Layout>
  <ul>
    {linkLists.map((list) => (
      <li>
        <a href={list.link}>{list.label}</a>
      </li>
    ))}
  </ul>
  <List client:load />
</Layout>

<style lang="scss">
 /* 省略 */
</style>
```
**ポイント解説**

・**フロントマター** :
Astro では、コードの冒頭に記述する `---` （コードフェンス）内でコンポーネントやライブラリ、固定データのインポートを行います。

・**クライアントディレクティブ** :
`<List client:load />` のように指定することで、JavaScript が必要なクライアントサイドのコンポーネントを正しく動作させます。
**load** のほかにも **only** や **visible** などがありますが、詳細は下記ページをご参照ください（たいていは **load** でよいことが多そうです）。

https://docs.astro.build/ja/reference/directives-reference/#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96

## Svelte 5 について
2024年10月22日にリリースされた Svelte 5 は、プロジェクト史上最も重要なリリースとされています。完全な下位互換性を保ちつつ、根本から再設計されています。

今回ご紹介したコードを例に少しだけご説明すると、以下のように記述が変わりました。
```javascript
// Svelte 4
let isLoading = true;
onMount(async () => {
  // 処理
});

// Svelte 5
let isLoading = $state(true);
$effect(async () => {
  // 処理
});
```
https://svelte.dev/blog/svelte-5-is-alive

https://qiita.com/oekazuma/items/ab617096af10ad94356e

## おわりに
Astro は、静的サイト制作におけるコンポーネントベースの開発ハードルを下げ、多様なフレームワークとの組み合わせを可能にします。
今回ご紹介した Svelte 以外にも多くの選択肢があるため、プロジェクトに適した技術を選びながら理解を深めていきたいと思います。