[Astro] 静的サイトジェネレーター [インストール、ディレクトリ構成、Astro コンポーネント]

Astro

コンテンツにフォーカスした高速なWebサイトを構築するためのフレームワークです。

プロジェクトを作成

以下のコマンドを実行して、プロジェクトを作成します。

npm create astro@latest
Need to install the following packages:
  create-astro@1.2.4
Ok to proceed? (y) y
√ Where would you like to create your new project? ... // プロジェクト名
√ How would you like to setup your new project? » a few best practices (recommended)
✔ Template copied!
√ Would you like to install npm dependencies? (recommended) ... yes
✔ Packages installed!
√ Would you like to initialize a new git repository? (optional) ... yes
✔ Git repository created!
√ How would you like to setup TypeScript? » Relaxed
✔ TypeScript settings applied!

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./astro
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat
npm run dev

ディレクトリ構成

・src/ - プロジェクトソースコード (コンポーネント、スタイル、ページ)
・public/ - コード以外の処理不要のアセット (フォント、アイコン)
・package.json - プロジェクトマニュフェスト
・astro.config.mjs - Astroの設定ファイル

Astro コンポーネント

コンポーネント構造

---
// コンポーネントスクリプト (JavaScript)
---
<!\\ コンポーネントテンプレート (HTML + JS Expressions) -->

Button コンポーネントを使用して、ButtonGroup コンポーネントを作成すると次のようになります。

---
// ButtonGroup.astro
import Button from './Button.astro';
---
<div>
    <Button title="Button1" />
    <Button title="Button2" />
    <Button title="Button3" />
</div>

コンポーネントスクリプト

src/components/MyComponent.astro

---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import someData from '../data/sample.json';

// 渡されたコンポーネントの props にアクセスする
const { title } = Astro.props;
// 外部データを取得する
const data = await fetch('URL/users').then(v => v.json());
---
<!-- テンプレートはここに書きます -->

コンポーネントテンプレート

src/components/MyData.astro

---
// コンポーネントスクリプトはここに書きます
import DataComponent from '../components/DataComponent.jsx';
const myData = [* ... *];
---
<!-- HTMLコメントに対応しています -->
<h1>Hello, world!</h1>

<!-- props やコンポーネントスクリプトの変数を使用します -->
<p>タイトルは: { Astro.props.title }</p>

<!-- 'client:' ディレクティブの付いたコンポーネントはハイドレートされます -->
<DataComponent client:visible />

<!-- JSX と同じように、HTML と JavaScript の式を混ぜられます -->
<ul>
    { myData.map((data) => <li>{ data.name }</li>) }
</ul>

<!-- テンプレートディレクティブを使って、複数の文字列やオブジェクトからクラス名を作成できます -->
<p class:list={ ["add", "dynamic" , { className: true }] } />

JSX に似た式

変数

ローカル変数は、中括弧 {} で囲んで使うことで、HTML に追加できます。
src/components/Variables.astro

---
const name = "Astro";
---
<div>
    <h1>Hello { name }!</h1>
</div>

動的な属性

ローカル変数は、中括弧で囲んで、HTML 要素やコンポーネントに属性の値を渡せます。
src/components/DynamicAttributes.astro

---
const name = "Astro";
---
<h1 class={ name }>属性式がサポートされています</h1>

<MyComponent templateLiteralNameAttribute={ `MyNameIs${ name }` } />

動的な HTML

ローカル変数は JSX のような関数で使用でき、動的に生成された HTML 要素を生成できます。
src/components/DynamicHtml.astro

---
const items = ["dog", "cat", "rabbit"];
---
<ul>
    { items.map((item) => (
        <li>{ item }</li>
    ))}
</ul>

Astro は JSX の論理演算子や参考演算子を用いて、HTML を条件付きで表示することができます。
src/components/ConditionalHtml.astro

---
const visible = true;
---
{ visible && <p>OK!</p> }
{ visible ? <p>OK!</p> : <p>NG!</p> }

動的なタグ

HTML タグ名またはインポートしたコンポーネントに変数を設定することで動的なタグが利用できます。
src/components/DynamicTags.astro

---
import MyComponent from './MyComponent.astro';
const Element = 'div';
const Component = MyComponent;
---
<!-- レンダリング -->
<Element>Hello!</Element>
<MyComponent />

フラグメントと複数要素

src/conponents/RootElements.astro

---
// 複数の要素を持つテンプレート
---
<p>各要素の1つのコンテナ要素で囲む必要はありません</p>
<p>Astro はテンプレート内の複数のルート要素をサポートします</p>

式を使用して複数の要素を動的に作成する場合は、これらの要素をフラグメントで囲む必要があります。Astro では、<Fragment></Fragment> または省略形の <></> のいずれかを使用できます。
src/components/FragmentWrapper.astro

---
const items = ["dog", "cat", "rabbit"];
---
<ul>
    { items.map((item) => (
        <>
            <li>red { item }</li>
            <li>blue { item }</li>
            <li>green { item }</li>
        </>
    ))}
</ul>

コンポーネントの props

props は、フロントマタースクリプトのグローバルな Astro.props で利用できます。
src/components/GreetingHeadline.astro

---
// 使い方: <GreetingHeadline greeting="Howdy" name="Partner" />
const { greeting, name } = Astro.props;
---
<h2>{ greeting }, { name }!</h2>

このコンポーネントを利用して、他の Astro コンポーネント、レイアウト、ページでレンダリングする場合、属性としてこれらの props を渡せます。
src/components/GreetingCard.astro

---
import GreetingHeadline from './GreetingHeadline.astro';
const name = "Astro";
---
<h1>GreetingCard</h1>
<GreetingHeadline greeting="Hello" name={ name } />
<p>Have a nice day!</p>

スロット

<slot /> 要素は外部の HTML コンテンツのプレースホルダーで、他のファイルからコンポーネントテンプレートの子要素を注入できます。
src/components/Wrapper.astro

---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
    <Header />
    <Logo />
    <h1>{ title }</h1>
    <slot /> <!-- 子要素はここに入ります -->
    <Footer />
</div>

src/pages/fred.astro

---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
    <h2>About Fred</h2>
    <p>Let me introduce you to Fred.</p>
</Wrapper>

HTML コンテンツのページ全体を <Layout></Layout> タグで囲んでレイアウトコンポーネントに送り、共通のページ要素の中にレンダリングさせられます。

名前付きスロット

名前付きスロットを利用すると、対応するスロット名を持つ HTML 要素のみをスロットの場所に渡せます。
src/components/Wrapper.astro

---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
    <Header />
    <slot name="after-header" /> <!-- `slot="after-wrapper"` 属性を持つ子要素はここに入ります -->
    <Logo />
    <h1>{ title }</h1>
    <slot /> <!-- `slot` 属性を持たない子要素、`slot="default"` 属性を持つ子要素はここに入ります -->
    <Footer name="after-footer" /> <!-- `slot="after-footer"` 属性を持つ子要素はここに入ります -->
</div>

src/pages/fred.astro

---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
    <img src="https://my.photo/fred.jpg" slot="after-header">
    <h2>About Fred</h2>
    <p>Let me introduce you to Fred.</p>
    <p slot="after-footer">Copyright 2023</p>
</Wrapper>

スロットのフォールバックコンテンツ

スロットに渡される子要素がない場合、<slot /> 要素はそれ自身のプレースホルダーの子要素をレンダリングします。
src/components/Wrapper.astro

---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props
---
<div id="content-wrapper">
    <Header />
    <Logo />
    <h1>{ title }</h1>
    <slot>
        <p>これは、スロットに渡された子要素がない場合の代替コンテンツです</p>
    </slot>
    <Footer />
</div>

CSS スタイル

CSS の タグもコンポーネントテンプレートの内部でサポートされています。
src/components/StyleHeading.astro

---
// コンポーネントスクリプトはここに書きます
---
<style>
    /* コンポーネントでスコープが作られ、ページ上部の他の h1 要素には影響しません */
    h1 { color: red }
</style>

<h1>Hello, World!</h1>

クライアントサイドスクリプト

Astro コンポーネントでは、標準的な HTML の <script> タグを使用してクライアントサイドにインタラクティビティを追加することができます。
src/components/ConfettiButton.astro

<button data-confetti-button>To celebrate</button>

<script>
    // npm モジュールをインポートする
    import confetti from 'canvas-confetti';

    // ページ内のコンポーネント DOM を見つける
    const buttons = document.querySelectorAll('[data-confetti-button]');

    // ボタンがクリックされたときに紙吹雪 (confetti) を発生させるイベントリスナーを追加します
    buttons.forEach((button) => {
        button.addEventListener('click', () => confetti());
    });
</script>