Webサイト作成を通して学ぶAstro

  •  
 
トビウオ に投稿
概要

MPA (複数の HTML ファイルを軸として構成する Web アプリケーション) 開発用フレームワークとして人気がある Astro 。その勉強のため、Bootstrap の公式サンプルを題材にしてみました。

プロジェクトを作成
-> % npm create astro@latest -- --template framework-react

 astro   Launch sequence initiated.

   dir   Where should we create your new project?
         ./pricing-page
      ◼  tmpl Using framework-react as project template

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict

  deps   Install dependencies?
         Yes

   git   Initialize a new git repository?
         Yes

      ✔  Project initialized!
         ■ Template copied
         ■ TypeScript customized
         ■ Dependencies installed
         ■ Git initialized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./pricing-page
         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

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! ?
╰─────╯

Astro 公式のテンプレ (npm create astro@latest) を利用して、プロジェクトをサクッと作成します。細かなコンポーネント作成は、Reactの力を借りることを想定しています。

Bootstrapが読み込めることを確認する

Bootstrap を読み込むには、<head>タグ内に<link href="(CDNのURL)/bootstrap.min.css" rel="stylesheet">と入れる手の他に、npm install bootstrapとしてからCSSを読み込む方法があります。

例えば、index.astroを次のように記述した場合、「import」部分で Bootstrap を読み込み、「---」以下に記述した HTML 部分でそれを取り込むことになります。

---
import "bootstrap/dist/css/bootstrap.min.css";
---
<html lang="ja" data-bs-theme="dark">
  <head>
    <meta charset="utf-8">
    <title>Pricing example</title>
  </head>
  <body>
    <div class="container py-3">
      <header>
        <div class="p-3 pb-md-4 mx-auto text-center">
          <h1 class="display-4 fw-normal text-body-emphasis">Pricing</h1>
        </div>
      </header>
    </div>
  </body>
</html>
コンポーネントでパーツを分割する

前述したように、Astro は MPA なので、1ページにベタッと情報を書いても普通に表示されます。

// src/pages/index.astro
---
import "bootstrap/dist/css/bootstrap.min.css";
---
<html lang="ja" data-bs-theme="dark">
  <head>
    <meta charset="utf-8">
    <title>Pricing example</title>
  </head>
  <body>
    <div class="container py-3">
      <header>
        <!-- ここをまとめたい -->
        <div class="p-3 pb-md-4 mx-auto text-center">
          <h1 class="display-4 fw-normal text-body-emphasis">Pricing</h1>
          <p class="fs-5 text-body-secondary">Quickly build an effective pricing table for your potential customers with this Bootstrap example. It’s built with default Bootstrap components and utilities with little customization.</p>
        </div>
      </header>
    </div>
  </body>
</html>

しかし、せっかくならコンポーネントを活用するべき……ということで、src/componentsディレクトリにPricingHeader.astroを作成し、パーツを切り出してそちらに置いておくことにします。

// src/components/PricingHeader.astro
---
---
<header>
  <div class="p-3 pb-md-4 mx-auto text-center">
    <h1 class="display-4 fw-normal text-body-emphasis">Pricing</h1>
    <p class="fs-5 text-body-secondary">
      Quickly build an effective pricing table for your potential customers with
      this Bootstrap example. It’s built with default Bootstrap components and
      utilities with little customization.
    </p>
  </div>
</header>
// src/pages/index.astro
---
import "bootstrap/dist/css/bootstrap.min.css";
import PricingHeader from "../components/PricingHeader.astro";
---
<html lang="ja" data-bs-theme="dark">
  <head>
    <meta charset="utf-8">
    <title>Pricing example</title>
  </head>
  <body>
    <div class="container py-3">
      <PricingHeader />
    </div>
  </body>
</html>

また、変数を使用してコンポーネントに値を渡せますので、次のように記述を変更できます。React (などが使う JSX) とは、ところどころ文法が違いますので、混同しないように注意して下さい。

// src/components/PricingHeader.astro
---
interface Props {
  title: string;
  message: string;
}

const { title, message } = Astro.props;

---
<header>
  <div class="p-3 pb-md-4 mx-auto text-center">
    <h1 class="display-4 fw-normal text-body-emphasis">{title}</h1>
    <p class="fs-5 text-body-secondary">{message}</p>
  </div>
</header>
レイアウトでテンプレート部分を共通化する

pagesディレクトリ以下に置く.astroファイルは、今まで見てきたように<html>から始まるのが基本です。しかしそれだと、pagesディレクトリ以下に複数の.astroファイルを置くと、Webサイト全体で共通になるような要素 (<head>タグ内の記述など) を愚直にコピペすることになってしまいます。

そこで、src/layoutsディレクトリにDarkLayout.astroを作成し、共通部分を切り出してそちらに置いておくことにします。

// src/layouts/DarkLayout.astro
---
import "bootstrap/dist/css/bootstrap.min.css";

interface Props {
    title: string;
}

const { title } = Astro.props;
---
<html lang="ja" data-bs-theme="dark">
  <head>
    <meta charset="utf-8">
    <title>{title}</title>
  </head>
  <body>
    <slot />
  </body>
</html>

すると、Layout 部分を他ファイルから参照できますので、index.astroが次のようにコンパクトになります。

// src/pages/index.astro
---
import PricingHeader from "../components/PricingHeader.astro";
import DarkLayout from "../layouts/DarkLayout.astro";

const pageTitle = 'Pricing example';
const headerTitle = 'Pricing';
const headerMessage = `Quickly build an effective pricing table for your \
potential customers with this Bootstrap example. It’s built with default \
Bootstrap components and utilities with little customization.`;
---
<DarkLayout title={pageTitle}>
  <div class="container py-3">
    <PricingHeader title={headerTitle} message={headerMessage} />
  </div>
</DarkLayout>
変数を利用してコンポーネントを一括で作成する

「コンポーネントでパーツを分割する」段落の応用編です。.astroファイルを使う場合はこんな感じ。

// src/components/PricingCard.astro
---
interface Props {
  title: string;
  price: number;
  features: string[];
  button: string;
}
const { title, price, features, button }: Props = Astro.props;
---
<div class="col">
  <div class="card mb-4 rounded-3 shadow-sm">
    <div class="card-header py-3">
      <h4 class="my-0 fw-normal">{title}</h4>
    </div>
    <div class="card-body">
      <h1 class="card-title pricing-card-title">
        ${price}<small class="text-body-secondary fw-light">/mo</small>
      </h1>
      <ul class="list-unstyled mt-3 mb-4">
        {
          features.map((feature) => (
            <li>{feature}</li>
          ))
        }
      </ul>
      <button type="button" class="w-100 btn btn-lg btn-outline-primary"
        >{button}</button
      >
    </div>
  </div>
</div>
// src/pages/index.astro
---
import PricingCard from "../components/PricingCard.astro";
import PricingHeader from "../components/PricingHeader.astro";
import DarkLayout from "../layouts/DarkLayout.astro";

const pageTitle = 'Pricing example';
const headerTitle = 'Pricing';
const headerMessage = `Quickly build an effective pricing table for your \
potential customers with this Bootstrap example. It’s built with default \
Bootstrap components and utilities with little customization.`;

const cards = [
  // 中略
];
---
<DarkLayout title={pageTitle}>
  <div class="container py-3">
    <PricingHeader title={headerTitle} message={headerMessage} />
    <main>
      <div class="row row-cols-1 row-cols-md-3 mb-3 text-center">
        {
          cards.map((card) => (
            <PricingCard {...card} />
          ))
        }
      </div>
    </main>
  </div>
</DarkLayout>

一方、React を使って.tsxで書く場合は、次のようになります。書き方はだいぶ似ていますが、class指定の書き方など、ところどころ異なることに気をつけましょう。

// src/components/PricingCard.tsx
interface Props {
  title: string;
  price: number;
  features: string[];
  button: string;
  outlined: boolean;
}

const PricingCard = ({ title, price, features, button, outlined }: Props) => (
  <div className="col">
    <div className="card mb-4 rounded-3 shadow-sm">
      <div className="card-header py-3">
        <h4 className="my-0 fw-normal">{title}</h4>
      </div>
      <div className="card-body">
        <h1 className="card-title pricing-card-title">
          ${price}<small className="text-body-secondary fw-light">/mo</small>
        </h1>
        <ul className="list-unstyled mt-3 mb-4">
          {
            features.map((feature, index) => (
              <li key={index}>{feature}</li>
            ))
          }
        </ul>
        <button type="button" className={
          `w-100 btn btn-lg ${
            outlined ? 'btn-outline-primary' : 'btn-primary'
          }`
      }
          >{button}</button
        >
      </div>
    </div>
  </div>
);

export default PricingCard;
// src/pages/index.astro
---
import PricingCard from "../components/PricingCard2.tsx";
import PricingHeader from "../components/PricingHeader.astro";
import DarkLayout from "../layouts/DarkLayout.astro";

const pageTitle = 'Pricing example';
const headerTitle = 'Pricing';
const headerMessage = `Quickly build an effective pricing table for your \
potential customers with this Bootstrap example. It’s built with default \
Bootstrap components and utilities with little customization.`;

const cards = [
  // 中略
];
---
<DarkLayout title={pageTitle}>
  <div class="container py-3">
    <PricingHeader title={headerTitle} message={headerMessage} />
    <main>
      <div class="row row-cols-1 row-cols-md-3 mb-3 text-center">
        {
          cards.map((card) => (
            <PricingCard {...card} />
          ))
        }
      </div>
    </main>
  </div>
</DarkLayout>
まとめ

Bootstrap の公式サンプルを題材にして、Astro が持つ便利機能 (コンポーネント、レイアウト、変数など) を使ってみました。上記の説明だけだと「React 要らないのでは?」となってしまいますが、static なページ作りが得意な Astro の中に、dynamic なコンポーネント作りが得意な React とをシームレスに連携できる……というのが重要だったりします。

そのため次回は、React 要素を使って、Astro で作られた Web サイト内の一部分を動的に書き換える例を紹介してみます。

コメントを追加

プレーンテキスト

  • HTMLタグは利用できません。
  • 行と段落は自動的に折り返されます。
  • ウェブページのアドレスとメールアドレスは自動的にリンクに変換されます。
CAPTCHA
この質問はあなたが人間の訪問者であるかどうかをテストし、自動化されたスパム送信を防ぐためのものです。