🚀

VueやJSXは好きだけどSPAは要らない、そんなあなたに Astro

2022/09/11に公開

「EJS, Nunjucks, PugなどでWeb制作をやってたけど、もっとモダンな開発がしたい・・・」

「ReactやVueに手を出してみたけど、別にアプリケーションが作りたいんじゃなくて静的なWebページが作りたいんだ・・・」

「Next, Gatsby, Nuxtみたいな静的サイトビルダーを使ったけど、結局SPAの挙動するやん・・・」
「SPAは別にいらんねん・・・」

いずれかに当てはまる方、いませんか?(全部私が思ってたこと)

そんなあなたに Astro です。

https://astro.build/

Astro ではどんな書き方ができるの?

Astroにはいろんな特徴がありますが、この記事では「どんな書き方ができるのか」を中心に紹介します。
Astroでは .astro という独自のファイル形式を使用します。
早速ですが、Astroでどんな書き方ができて、どんな出力が得られるのか見てみましょう。

こういう書き方↓ができて・・・

src/components/Component.astro
---
// フロントマッター内にJS/TSが書ける!(ビルド時に実行される)
const list = ['aaa', 'bbb', 'ccc'];
---

<!-- Reactのように(ほぼ)JSXで書ける! -->
<ul>
  {list.map((item) => <li>{item}</li>)}
</ul>

<style lang="scss">
  // VueのようなScoped CSSが書ける!
  li {
    // Sassなどのメタ言語も使える!
    &:first-child {
      color: red;
    }
  }
</style>

<script>
  // ブラウザで実行されるJS/TSも書ける!
  console.log('in browser');
</script>
src/pages/index.astro
---
// コンポーネントをimportして使う
import Component from '../components/Component.astro';
---

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Sample</title>
  </head>
  <body>
    <Component />
  </body>
</html>

ビルドすると、こういう出力↓が得られる・・・!

dist/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Sample</title>
    <link rel="stylesheet" href="/assets/index.1a99575d.css" />
    <script type="module">
      console.log('in browser');
    </script>
  </head>
  <body>
    <ul class="astro-QIHFUJBN">
      <li class="astro-QIHFUJBN">aaa</li>
      <li class="astro-QIHFUJBN">bbb</li>
      <li class="astro-QIHFUJBN">ccc</li>
    </ul>
  </body>
</html>
dist/assets/index.1a99575d.css
li:where(.astro-QIHFUJBN):first-child {
    color: red
}

※見やすさのために整形しています。

記法はReactやVueのいいとこ取り! ✨
出力後はSPAではなく完全に静的なHTML ✨
最高 ✨ 天才 ✨ 超イケメン ✨

もうちょい詳しく

それでは、もう少し詳しく .astro コンポーネントの機能を見ていきましょう。

テンプレートエンジンとして

HTML中心のテンプレートを、JS (TS) を使用して出力することができます。EJSにも近いイメージです。
フロントマッター内に書かれたスクリプトは、ビルド時に(つまりNodeで)のみ実行されます。
そして、フロントマッター内で宣言した変数はHTMLテンプレート内で使用できます。

AstroのHTMLテンプレートは「JSXに似た式 (JSX-link Expressions)」と説明されている通り、ほぼJSXだけどJSXじゃない形式です。
たとえばJSXでは className を使用しますが、Astroでは普通に class を使用します。
よりHTMLに近くていいですね。

Sample.astro
---
const name = "test";
console.log(name); // このconsole.logはターミナルに表示される
---

<div>変数の文字列を表示することが可能:{name}</div>
<div class={name}>属性の値に入れることも可能</div>

↓出力

dist/index.html
<div>変数の文字列を表示することが可能:test</div>
<div class="test">属性の値に入れることも可能</div>

出力後は、完全に静的なHTMLになります。
変数部分がブラウザ上で動的に変わることはありません。

画像などのimport

画像などの静的アセットファイルをimportすると、そのファイルに対するパスが文字列として取得できます。
importされたファイルはビルド時に出力(コピー)されます。
デフォルトではハッシュがついたファイル名に変更されます。

Sample.astro
---
import imageSample from "./sample.jpg";
---

<img src={imageSample} alt="サンプル画像" />

↓出力

dist/index.html
<img src="/assets/sample.fda7c236.png" alt="">

もしファイル名を間違えていたらビルド時にimportしようとした段階でエラーが発生するので、リンク切れの心配不要になって幸せになれます。

コンポーネントProps

コンポーネントにPropsを渡すことができます。

Component.astro
---
const { name } = Astro.props;
---

<p>{name}</p>
Parent.astro
---
import Component from "./Component.astro";
---

<Component name="なまえ" />

TypeScriptを使用する場合、型 Props をexportすることで型を定義できます。
Astro.props の型は、Props の情報を元に解釈されます。
(これまでは as Props が必要でしたが、最近のアップデートで自動で解釈されるようになっています。)

Component.astro
---
export type Props = {
    name: string;
    disabled?: boolean;
};
const { name, disabled = false } = Astro.props;
---

スロット (Slots)

コンポーネントの中に入れる要素を渡すことができます。
Reactでいうとchildren、Vueのslotにあたる機能です。
Vueのslotと同じく、複数持つことができます。

Wrapper.astro
<div class="wrapper">
    <main>
        <slot />
    </main>
    <footer>
        <slot name="footer" />
    </footer>
</div>
Another.astro
---
import Wrapper from '../components/Wrapper.astro';
---

<Wrapper>
    <p>メインコンテンツ</p>
    <nav slot="footer">フッター内に入る</nav>
</Wrapper>

スコープ付きCSS (Scoped CSS)

Vueの <style scoped> のような機能です。
ランダムなクラス名が追加されることで、そのコンポーネント内のみにスタイルが当てられます。

Component.astro
<p>テキスト</p>

<style>
  p {
    color: red;
  }
</style>

↓出力

dist/index.html
<p class="astro-QIHFUJBN">テキスト</p>
dist/assets/index.[hash].css
p:where(.astro-QIHFUJBN) {
    color: red
}

ブラウザで実行するスクリプト

scriptタグに書いたスクリプトは、ブラウザ上で実行されるscriptタグとして出力されます。
TypeScriptもサポートされています。
(以前はscriptタグ内にTypeScript記述ができなかったのですが、できるようになってました!)

Sample.astro
<button data-trigger>Click!</button>

<script>
  document.querySelector<HTMLButtonElement>('[data-trigger]')?.addEventListener('click', () => {
    console.log('Clicked!');
  });
</script>

宣言的UIはできないの?

.astro コンポーネントではできない。
なんたってHTMLテンプレートの出力はビルド時のみに実行され、完全に静的なHTMLになるから。
.astro コンポーネントはテンプレートエンジンと思ったほうがイメージとしては合ってます。

ですが、 .astro ファイルでは ReactやVue, Svelte, Solid, ... といった他のフレームワークを使用できます。
宣言的UIをしたい場合は、そういった他のフレームワークと組み合わせるといいでしょう。

Sample.astro
---
import ReactComponent from './ReactComponent.jsx';
---

<ReactComponent client:load />

外部フレームワークのブラウザ上でのスクリプトは、client:* ディレクティブを設定することで実行されます。

逆に、ReactやVueのUIフレームワークを使いつつ、ブラウザ上のスクリプト実行を消し去りたいなんて場合は client:* ディレクティブを付けなければOKです。

さいごに

その他詳しくは公式ドキュメントの Astroコンポーネント のページを参照ください。
(日本語です)

https://docs.astro.build/ja/core-concepts/astro-components/

それでは、よきAstroライフを🚀

Astroは…いいぞ。

余談

コードブロックを記述する際、

```astro
```

ではZennでシンタックスハイライトがつかなかったため、 jsxhtml に設定しました。
そのため、シンタックスハイライトが完全ではないです。

GitHubで編集を提案

Discussion