Astroコンポーネント

Astroコンポーネントは、あらゆるAstroプロジェクトの基本的な構成要素です。クライアントサイドのランタイムを持たない、HTMLのみのテンプレートコンポーネントです。

HTMLをご存知の方なら、すでに最初のAstroコンポーネントを書くのに十分な知識を持っています。

Astroコンポーネントの構文は、HTMLのスーパーセットです。この構文は、HTMLやJSXを書いたことのある人なら誰でも親しみやすいように設計されています。また、コンポーネントとJavaScript式を含むためのサポートも追加されています。Astroコンポーネントは、ファイル拡張子が.astroなので、すぐ見分けられます。

Astroコンポーネントは非常に柔軟です。多くの場合、Astroコンポーネントは、ヘッダーやプロフィールカードのような、ページ上で再利用可能なUIを含むことになります。また、Astroコンポーネントには、SEO対策を容易にする一般的な<meta>タグのコレクションのような、小さなHTMLのスニペットが含まれることもあります。Astroコンポーネントは、ページ全体のレイアウトを含められます。

Astroコンポーネントについて知っておくべきもっとも重要なことは、ビルド中にHTMLへ変換されることです。コンポーネントの内部でJavaScriptコードを実行しても、すべて事前に実行され、ユーザーに送られる最終ページからは取り除かれます。その結果、デフォルトでは、追加されるJavaScriptの痕跡のない、より高速なサイトが実現します。

Astroコンポーネントは、コンポーネントスクリプトコンポーネントテンプレートという2つの主要な部分で構成されています。それぞれのパーツは異なる仕事を行いますが、この2つを組み合わせることで、使いやすさと、どんなものにも対応できる表現力を兼ね備えたフレームワークを提供することを目指しています。

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

コンポーネントを他のコンポーネントの内部で使用し、より高度なUIを構築できます。たとえば、Buttonコンポーネントを使用して、ButtonGroupコンポーネントを作成すると、次のようになります。

src/components/ButtonGroup.astro
---
// 例: ButtonGroup.astro
import Button from './Button.astro';
---
<div>
  <Button title="Button 1" />
  <Button title="Button 2" />
  <Button title="Button 3" />
</div>

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

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

Astroでは、Astroコンポーネント内のコンポーネントスクリプトを識別するためにコードフェンス(---)を使用します。Markdownを書いたことがある方なら、すでにfront-matterという同様の概念に馴染みがあるかもしれません。Astroのコンポーネントスクリプトの考え方は、この概念から直接着想を得ています。

コンポーネントスクリプトを使用して、テンプレートをレンダリングするために必要なあらゆるJavaScriptコードを記述できます。

  • 他のAstroコンポーネントのインポート
  • 他のフレームワークコンポーネント(Reactなど)のインポート
  • データ(JSONファイルなど)のインポート
  • APIやデータベースからコンテンツを取得するコード
  • テンプレートで参照する変数の作成
src/components/MyComponent.astro
---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';

// 渡されたコンポーネントのprops(`<X title="Hello, World" />`など)にアクセスする。
const {title} = Astro.props;
// 外部データを取得する(プライベートAPIやデータベースからでも可)
const data = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
---
<!-- テンプレートはここに書きます -->

コードフェンスは、その中に書かれたJavaScriptが「守られる」ことを保証するために設計されています。フロントエンドのアプリケーションに漏れたり、ユーザーの手に渡ったりしません。高コストなコードや機密性の高いコード(プライベートなデータベースの呼び出しなど)が、ユーザーのブラウザに届くことを心配せずに、安全にコードを書けます。

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

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

コンポーネントスクリプトの下には、コンポーネントテンプレートがあります。コンポーネントテンプレートは、コンポーネントの出力するHTMLを決定します。

ここにプレーンなHTMLを書けば、そのコンポーネントはAstroのページでインポートされて使用される際にそのHTMLをレンダリングします。

ただし、Astroのコンポーネントテンプレート構文は、JavaScript式インポートしたコンポーネント特別なAstroディレクティブ (EN)もサポートしています。コンポーネントスクリプトで(ページ構築時に)定義されたデータと値は、コンポーネントテンプレートで使用され、動的に作成されたHTMLを生成できます。

src/components/MyFavoritePokemon.astro
---
// コンポーネントスクリプトはここに書きます
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
---
<!-- HTMLコメントに対応しています -->

<h1>Hello, world!</h1>

<!-- propsやコンポーネントスクリプトの変数を使用します。 -->
<p>好きなポケモンは: {Astro.props.title}</p>

<!-- `client:`ディレクティブで他のコンポーネントをハイドレートに含める -->
<ReactPokemonComponent client:visible />

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

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

Astroコンポーネントのfront-matterコンポーネント・スクリプト内で、ローカルJavaScript変数を定義できます。また、JSXに似た式を使用して、これらの変数をコンポーネントのHTMLテンプレートに挿入できます。

ローカル変数は、中括弧({})で囲んで使うことで、HTMLに追加できます。

src/components/Variables.astro
---
const name = "Astro";
---
<div>
  <h1>Hello {name}!</h1>  <!-- 出力: <h1>Hello Astro!</h1> -->
</div>

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

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

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

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

src/components/DynamicHtml.astro
---
const items = ["犬", "猫", "カモノハシ"];
---
<ul>
  {items.map((item) => (
    <li>{item}</li>
  ))}
</ul>

AstroはJSXの論理演算子や三項演算子を用いて、HTMLを条件付きで表示することができます。

src/components/ConditionalHtml.astro
---
const visible = true;
---
{visible && <p>私を見て!</p>}

{visible ? <p>私を見て!</p> : <p>または私を見て!</p>}

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

src/components/DynamicTags.astro
---
import MyComponent from "./MyComponent.astro";
const Element = 'div'
const Component = MyComponent;
---
<Element>こんにちは!</Element> <!-- <div>こんにちは!</div> としてレンダリングされます -->
<Component /> <!-- <MyComponent />としてレンダリングされます -->

動的なタグを使用する場合:

  • 変数名をキャピタライズする 例えば、elementではなくてElementを利用します。そうでなければ、Astroは変数名をリテラルなHTMLタグとしてレンダリングします。

  • ハイドレーションのディレクティブはサポートしません. client:* ハイドレーションディレクティブを利用する場合、Astroは本番用にバンドルするコンポーネントを知る必要がありますが、動的なタグパターンではこれが動作しません。

フラグメントと複数要素

Section titled フラグメントと複数要素

Astroコンポーネントテンプレートは、JavaScriptやJSXとは異なり、すべてを1つの <div><> で囲む必要がなく、複数の要素をレンダリングできます。

src/components/RootElements.astro
---
// 複数の要素を含むテンプレート
---
<p>要素を1つの要素で包む必要はありません。</p>
<p>Astroはテンプレート内の複数のルート要素をサポートします。</p>

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

src/components/FragmentWrapper.astro
---
const items = ["犬", "猫", "カモノハシ"];
---
<ul>
  {items.map((item) => (
    <>
      <li>赤い{item}</li>
      <li>青い{item}</li>
      <li>緑の{item}</li>
    </>
  ))}
</ul>

また、以下の例のように、set:* ディレクティブ (EN)を追加する際に、ラッパー要素を避けるためにフラグメントが役に立つことかもしれません。

src/components/SetHtml.astro
---
const htmlString = '<p>Raw HTML content</p>';
---
<Fragment set:html={htmlString} />

Astroコンポーネントの構文は、HTMLのスーパーセットです。HTMLやJSXの経験がある人なら誰でも親しみやすいように設計されていますが、.astroファイルとJSXの間には、いくつかの重要な違いがあります。

Astroでは、JSXで使用されているcamelCaseの代わりに、すべてのHTML属性に標準的なkebab-caseフォーマットを使用します。これは、Reactでサポートされていないclassに対しても有効です。

example.astro
<div className="box" dataValue="3" />
<div class="box" data-value="3" />

JSXではJavaScript形式のコメントを使用しますが、Astroでは標準的なHTMLのコメントを使用できます。

example.astro
<!-- .astroファイルではHTMLコメント構文が有効です -->

Astroコンポーネントは、propsを定義し、受け取れます。propsは、HTMLをレンダリングするためにコンポーネントテンプレートで利用できます。propsは、front-matterスクリプトのグローバルな Astro.props で利用できます。

以下は、greetingnameの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>グリーティングカード</h1>
<GreetingHeadline greeting="やぁ" name={name} />
<p>素敵な一日をお過ごしください!</p>

TypeScriptのProps型のインターフェイスでpropsを定義できます。Astroはfrontmatter内のPropsインターフェイスを自動的に検出し、プロジェクトに対して型の警告やエラーを出します。propsは、Astro.propsから再構成する際に、デフォルト値を与えることもできます。

src/components/GreetingHeadline.astro
---
interface Props {
  name: string;
  greeting?: string;
}

const { greeting = "Hello", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>

コンポーネントのpropsは、何も提供されない場合に使用するデフォルト値を指定できます。

src/components/GreetingHeadline.astro
---
 const { greeting = "こんにちは", name = "宇宙飛行士" } = Astro.props;
---
<h2>{greeting}{name}</h2>

<slot /> 要素は外部HTMLコンテンツのプレースホルダーで、他のファイルからコンポーネントテンプレートに子要素を注入(はめ込む=スロット)できます。

デフォルトでは、コンポーネントに渡されたすべての子要素は、その <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 />  <!-- 子要素はここに入ります -->
  <Footer />
</div>
src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
  <h2>フレッドについて</h2>
  <p>ここでは、フレッドについて紹介します。</p>
</Wrapper>

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

Astroコンポーネントは、名前付きスロットも使えます。これを利用すると、対応するスロット名を持つ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-header"` 属性を持つ子要素はここに入ります。 -->
  <Logo />
  <h1>{title}</h1>
  <slot />  <!--  `slot`属性をもたない子要素、`slot="default"`属性を持つ子要素はここに入ります。 -->
  <Footer />
  <slot name="after-footer"/>  <!--  `slot="after-footer"` 属性を持つ子要素はここに入ります。 -->
</div>
src/pages/fred.astro
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="フレッドのページ">
  <img src="https://my.photo/fred.jpg" slot="after-header">
  <h2>フレッドについて</h2>
  <p>ここでは、フレッドについて紹介します。</p>
  <p slot="after-footer">Copyright 2022</p>
</Wrapper>

子要素の slot="my-slot" 属性を使用して、コンポーネント内の <slot name="my-slot" /> にマッチするプレースホルダに渡します。

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

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

スロットは、フォールバックコンテンツをレンダリングすることもできます。スロットに渡される子要素がない場合、 <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の <style> タグも、コンポーネントテンプレートの内部でサポートされています。

これらのタグはコンポーネントのスタイル設定に使えます。すべてのスタイルルールはそのコンポーネントに自動的にスコープが作られ、大規模なアプリでのCSSのコンフリクトを防げます。

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

<h1>Hello, world!</h1>

📚 スタイルの適用に関する詳細は、スタイリングガイドを参照してください。

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

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

フレームワークコンポーネント (React, Svelte, Vue, Preact, SolidJS, AlpineJS, Lit) や Astroインテグレーション (astro-XElement 等) を使わずにブラウザに JavaScript を送信するには、Astro コンポーネントのテンプレートで <script> タグを使ってグローバルスコープ内で実行されるJavaScriptをブラウザに送信してください。

デフォルトでは、<script> タグはAstroによって処理されます。

  • インポートされたものはバンドルされ、ローカルファイルやNodeモジュールのインポートができます。
  • 処理されたスクリプトは、ページの <head>type="module" と共に挿入されます。
  • TypeScriptを完全にサポートし、TypeScriptファイルのインポートを含みます。
  • コンポーネントがページ内で何度も使用される場合、scriptタグは一度だけ含まれます。
<script>
  // 処理、バンドルされます。TypeScript対応。npmパッケージに対してもESMのインポートできます。
</script>

スクリプトをバンドルしないようにするには、 is:inline 属性を使用します。

<script is:inline>
  // 書かれたとおりにHTMLにレンダリングされます!
  // ESM import はファイルからの相対パスで解決されません。
</script>

上記の方法を組み合わせることで、同じ .astro ファイルに複数の <script> タグを使用できます。

📚 <script> タグで使用できるディレクティブの詳細については、ディレクティブリファレンス (EN)を参照してください。

外部スクリプトの読み込み

Section titled 外部スクリプトの読み込み

使用するタイミング: JavaScriptファイルが public/ 内にある場合。

この方法では、以下に説明する import メソッドを使用したときに、Astroが提供するJavaScriptの処理、バンドル、最適化をスキップすることに注意してください。

// 絶対URLパス
<script is:inline src="/some-external-script.js"></script>

src/に配置されたスクリプトを使用する

Section titled src/に配置されたスクリプトを使用する

使用するタイミング: 外部スクリプトが src/ 内にあり、かつ、ESMモジュールタイプをサポートしている場合。

Astroは、これらのJavaScriptクライアントサイドインポートを検出し、自動的にJSをビルド・最適化してページに追加します。

// ESM import
<script>
  import './some-external-script.js';
</script>

Astroは.htmlファイルをコンポーネントとしてインポートして使用したり、これらのファイルをページとしてsrc/pagesのサブディレクトリに設置することをサポートしています。フレームワークなしで構築された既存のサイトからコードを再利用したい場合や、コンポーネントに動的な機能がないことを確認したい場合はHTMLコンポーネントを利用するといいでしょう。

HTMLコンポーネントは有効なHTMLしか含むことができず、そのためAstroコンポーネントの主要機能が制限されます

  • frontmatterやサーバーサイドのインポート、動的な記法をサポートしません
  • すべての<script>タグはバンドルされずに残され、is:inlineを持つ場合と同じように扱われます。
  • public/フォルダにあるアセット (EN)のみを参照できます。

📚 Astroの組み込みコンポーネント (EN)を学びます。

📚 AstroプロジェクトでのJavaScriptフレームワークコンポーネントの使用方法について学びます。