take4s5i DEV

16 Jul 2022

Astro ざっくりまとめ

ブログを Astro で作り直したので 備忘録がてらまとめていこうかなと思います。

Astro とは

Astro は静的サイトジェネレータです。

Next.js のSSG, Hugo, Gatsby と同じようなツールと言えます。

以下のような特徴があります。

  • コンポーネント思考によるモダンなWeb開発
  • React, Preact, Svelte, Vue 等の複数のフレームワークに対応
  • Islands Archtecture に基づいた高速なWebサイトの生成。

また、ブログ用途限定かもしれませんが何もセットアップしなくても markdown が読み込めて シンタックスハイライトまで効いてくれます。

Gatsby との比較

会社のドキュメントで Gatsby を使ってるので比較してみようと思います。 公式での比較ドキュメント があるので主に使用感について。

Astro の良いところ

  • Starter 探したり, Plugin 頑張って設定しなくても markdown 等がそこそこいい感じで使える
  • build, devモードの起動が半端なく早い
    • Vite 使ってるようなので Vite のおかげかも

Gatsby のよいところ

  • コーディングのサポート
    • jsx なのでエディタ等のサポートが充実してます
    • astro は Vim だとシンタックスハイライトすら効かない・・・(2022年7月現在)
  • GraphQL でデータを取ってこれる柔軟性
  • プラグインによる拡張性

従来の SSR の問題

Islands Archtecture について話す前に従来の SSR の問題点について軽くおさらいします。

通常 SPA は<div id="app"> のようなタグ(=マウントポイント)を1つだけ body の中に設置し、 その下はフレームワークによってレンダリングされます。

SSR 時、React 等のフレームワークはマウントポイントに静的に HTML をレンダリングしてクライアントに返します。 このときクライアント側では ハイドレーション という処理が行われます。

ハイドレーション では以下のような処理が実行されます。

  • コンポーネント等のjsロード, 実行
  • コンポーネントのローカルステートの設定
  • イベントハンドラの設定

SSR によって Core Web Vitals の LCP (Largest Content Paint) は改善しますが、 ハイドレーションの処理があるため FID (First Input Delay) は改善しません。

Islands Archtecture とは

Islands Archtecture は Web ページを複数のパーツ (= Island) の組み合わせとして考えます。 Island には2つのタイプがあって、

  • SSR + ハイドレーション or CSR する部分
  • 静的な HTML の部分

js 不要な部分に関しては単なるHTML + CSS で返してしまおうということですね。 こうすることで、頭から全部ハイドレーションするより処理を軽くすることが出来ます。

また、Astro では Partial Hydration といって Island 毎に独立してハイドレーションします。 ハイドレーションの方法も複数サポートしていて、ここらへんの細かい制御ができるのもAstroの強みになっています。

Astro Component と Framework Component

Astro では Component が複数種類あります。

Astro Component

  • *.astro
  • Islands Archtecture の静的な HTML の部分を担当
  • CSR は出来ない。
  • JSX ぽいシンタックスで書ける
  • Astro Comopnent 内では Astro Component, Framework Component の両方が使える

詳しくはこちら

Framework Component

  • React, Preact, Svelte, Vue 等の各種フレームワークでレンダリングされるコンポーネント
  • *.tsx, *.jsx など(フレームワークによる)
  • Framework Comopnent 内では Astro Component は使えない。

また、ビルトインで markdown がサポートされており、何も設定しなくても勝手にシンタックスハイライトが効きます。 (便利ですね)

markdownでは jsx のようなシンタックスもサポートされていて、 MDX のように使えます。 markdownの中で, Astro Component や Framework Component を使うことも可能です。

Astro のプロジェクト構成

  • src/components: 再利用可能なコンポーネント群
    • Astro Component, Framework Component両方
  • src/pages: Page Component
    • HTML全体をレンダリングする root となるコンポーネント
    • Astro Component のみ
  • src/layouts: markdown や Page Component で利用するレイアウト用コンポーネント
    • Astro Component のみ
  • src/styles
    • css や sass 等のスタイル置き場
  • public
    • robots.txt 等の そのまま公開されるファイルを置く場所

ここらへんに詳しく書いてあります。

Astro サンプル実装

サンプルを使って説明します。 (ソース

Icon.astro [src]

---
// Icon.astro
export interface Props {
  type: 'success' | 'danger'
}

const { type } = Astro.props
---
<div class={['icon', `icon-${type}`].join(' ')}>
  <slot />
</div>
<style>
.icon {
  min-width: 120px;
  text-align: center;
  font-weight: bold;
  border-style: solid;
  border-radius: 4px;
  padding: 2px 4px;
}

.icon.icon-success {
  background-color: royalblue;
  border-color: royalblue;
}

.icon.icon-danger {
  background-color: tomato;
  border-color: tomato;
}

</style>

TodoItem.astro [src]

---
// TodoItem.astro
export interface Props {
  title: string;
  completed: boolean;
}

import Icon from './Icon.astro'

const { title, completed } = Astro.props

---
<div class="card">
  <div class="title">{title}</div>
  <div class="icon-wrap">
  {completed
    ? (<Icon type="success">COMPLETE</Icon>)
    : (<Icon type="danger">IN PROGRESS</ICON>)
  }
  </div>
</div>
<style>
.card {
  border: 2px solid dimgray;
  border-radius: 4px;
  padding: 16px;
  margin: 4px;
  display: flex;
  justify-content: space-between;
}

.title {
  font-size: 1.5rem;
}

.icon-wrap {
  display: flex;
  align-items: center;
}
</style>

TodoList.astro [src]

---
// TodoList.astro
import TodoItem from './TodoItem.astro'

const limit = 5
const url = 'https://jsonplaceholder.typicode.com/todos/'
const todos = await fetch(url).then((res) => res.json())
---
<ul class="list">
  {todos.slice(0, limit).map((todo) => (
    <li class="item">
      <TodoItem title={todo.title} completed={todo.completed} />
    </li>
  ))}
</ul>
<style>
.list {
  display: flex;
  flex-direction: column;
  list-style-type: none;
  padding: 4px;
}

.item {
}
</style>

Astro Component は Component Script と Component Template の2つのパートからなります。

--- で区切られた最初の部分(frontmatter) は Component Scriptです。 Component Script で Astro Component やFramework Component を import したり、 Astro.props で Props を受け取る事もできます。

Component Script は SSR 時、またはビルド時に実行されます。 このブログはビルドした HTML をデプロイしているため、 TodoList.asrto はビルド時に fetch したデータで HTML をレンダリングします。

--- の後ろの部分は Component Template です。 JSX ライクな記法でHTMLを記述することが出来ます。 Component Script で import したコンポーネントを使ったり、定義した変数を参照出来ます。

また、 <style> タグでスタイルを定義することも出来ます。 スタイルは Asrto によって自動的に CSS Module 化されるので、クラス名の重複を気にする必要はありません。

<TodoList /> をレンダリングすると以下のようになります。

  • delectus aut autem
    IN PROGRESS
  • quis ut nam facilis et officia qui
    IN PROGRESS
  • fugiat veniam minus
    IN PROGRESS
  • et porro tempora
    COMPLETE
  • laboriosam mollitia et enim quasi adipisci quia provident illum
    IN PROGRESS

おわりに

書き方がちょっと違うのと、 CSR はできないという点を除けば コンポーネントでの開発に慣れている方はそんなに違和感なく使えるのかなと思いました。

先程の TodoList も記事のソースの markdown 中に直接コンポーネントとして呼び出してレンダリングしています。 MDX をセットアップしなくても markdown 中でコンポーネントを使えるのは非常に便利だと思いました。