スクリプトとイベントハンドリング

標準的なHTMLの<script>タグを使用すれば、React・Svelte・Vue等のUIフレームワークを利用せずにインタラクティブ性をAstroコンポーネントへ追加できます。これによってブラウザ上で実行されるJavaScriptを送信してAstroコンポーネントに機能を追加できるようになります。

.astroファイル内に単数(もしくは複数)の<script>タグを追加することによってクライアントサイドJavascriptを追加できます。

下の例では、<Hello />コンポーネントをページに追加することでブラウザコンソール上にログメッセージを出力します。

src/components/Hello.astro
<h1>Welcome, world!</h1>

<script>
  console.log('Welcome, browser console!');
</script>

スクリプトのバンドル

Section titled スクリプトのバンドル

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

  • すべてのインポートはバンドルされ、ローカルファイルやNodeモジュールをインポートできます。
  • 処理されたスクリプトは、type="module"としてページの<head>に挿入されます。
  • TypeScriptを対応しており、TypeScriptファイルもインポートできます
  • コンポーネントがページに複数回使われている場合、スクリプトは一度だけ含まれます。
src/components/Example.astro
<script>
  // 処理される! バンドルされる! TypeScriptサポート!
  // ローカルスクリプトやNodeモジュールのインポートも動作します。
</script>

スクリプトがバンドルされることを避けるために、is:inlineディレクティブを利用できます。

src/components/InlineScript.astro
<script is:inline>
  // 書かれたとおりにレンダリングされます。
  // ローカルインポートが解決されずに動作しません。
  // コンポーネント内の場合、コンポーネントが利用されている箇所全てに繰り返しレンダリングされます。
</script>

📚 <script> タグで利用可能なディレクティブについてもっと知りたい場合は、テンプレートディレクティブ (EN)を参照してください。

スクリプトを.js.tsファイルのように分離して書きたい場合や、他のサーバーから外部スクリプトを参照したい場合には<script>タグのsrc属性の参照を使えます。

ローカルスクリプトのインポート

Section titled ローカルスクリプトのインポート

これを使うケース: スクリプトがsrcに存在している場合。

Astroはスクリプトのバンドルのルールに従って、ビルド、最適化、そしてページ内にスクリプトを追加します。

src/components/LocalScripts.astro
<!-- `src/scripts/local.js`への相対パス -->
<script src="../scripts/local.js"></script>

<!-- このローカルのTypoeScriptファイルも動作します。 -->
<script src="./script-with-types.ts"></script>

外部スクリプトをロードする

Section titled 外部スクリプトをロードする

これを使うケース: JavaScriptファイルがpublicディレクトリに存在しているか、CDNの場合。

プロジェクトのsrcフォルダ以外のスクリプトをロードするには、is:inlineディレクティブを含めます。このアプローチでは、上記のようにスクリプトをインポートする際にAstroが提供しているJavaScriptの処理・バンドル・最適化はスキップされます

src/components/ExternalScripts.astro
<!-- `public/my-script.js`スクリプトへの絶対パス -->
<script is:inline src="/my-script.js"></script>

<!-- リモートサーバー上にあるスクリプトへの完全なURL -->
<script is:inline src="https://my-analytics.com/script.js"></script>

共通スクリプトパターン

Section titled 共通スクリプトパターン

onclickや他のイベントをハンドリングする

Section titled onclickや他のイベントをハンドリングする

いくつかのUIフレームワークではonclick={...}(React/Preact)や@click="..."(Vue)のように独自の構文でイベントを処理しています。Astroは標準的なHTMLにより近く、イベントのために独自の構文は利用しません。

その代わりに、ユーザーのインタラクションをハンドリングするために<script>タグ内にaddEventListenerを利用します。

src/components/AlertButton.astro
<button class="alert">Click me!</button>

<script>
  // ページ内から`alert`クラスを持つすべてのボタンを探す。
  const buttons = document.querySelectorAll('button.alert');

  // 各ボタンがクリックされたときのハンドリング
  buttons.forEach((button) => {
    button.addEventListener('click', () => {
      alert('Button was clicked!');
    });
  });
</script>

カスタム要素を持つWebコンポーネント

Section titled カスタム要素を持つWebコンポーネント

Webコンポーネント標準を使うことで独自の動作をするHTML要素を作成できます。.astroコンポーネントでカスタム要素を定義するとUIフレームワークライブラリを利用しなくてもインタラクティブなコンポーネントを作ることができます。

下の例では、ハートボタンがクリックされた回数を記録し、その最新のカウント数を<span>に更新する<astro-heart>HTML要素を新しく定義しています。

src/components/AstroHeart.astro
<!-- カスタム要素 "astro-heart" でコンポーネント要素を囲みます。 -->
<astro-heart>
  <button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>

<script>
  // 新しいタイプのHTML要素の動作を定義する
  class AstroHeart extends HTMLElement {
    constructor() {
      super();
      let count = 0;

      const heartButton = this.querySelector('button');
      const countSpan = this.querySelector('span');

      // ボタンがクリックされるごとにカウントを更新する。
      heartButton.addEventListener('click', () => {
        count++;
        countSpan.textContent = count;
      });
    }
  }

  // <astro-heart>要素としてAstroHeartクラスを利用することをブラウザに教える
  customElements.define('astro-heart', AstroHeart);
</script>

ここでカスタム要素を利用するメリットは2つあります。

  1. document.querySelector()を使って全ページを検索する変わりに、this.querySelector()を使えば検索範囲が現在のカスタム要素のインスタンスに限られます。これにより、一回で一つのコンポーネントだけを操作しやすくなります。
  2. <script>は一度だけしか実行されませんが、ブラウザはページ上の<astro-heart>を見つける度にカスタム要素のconstructor()メソッドを実行します。これにより、ページで複数回コンポーネントを使用する場合でも一つのコンポーネントを安全に記述できます。

📚 web.dev’s Reusable Web Components guideMDN’s introduction to custom elementsから多くのカスタム要素について学ぶことができます。

フロントマター変数をスクリプトに渡す

Section titled フロントマター変数をスクリプトに渡す

Astroコンポーネントでは、---フェンスの間にあるフロントマターのコードはサーバー上で実行され、ブラウザでは利用できません。サーバーからクライアントへ変数を渡すには、変数を保存しておきJavaScriptがブラウザで実行されたときに読み込む必要があります。

これを実現するための1つの手段はdata-* attributesを利用してHTML出力に変数の値を保存する方法です。カスタム要素を含むスクリプトはブラウザ上にHTMLがロードされると要素のdatasetを利用することで、この属性を読み込むことができます。

下の例では、message propをdata-message属性に保存されているので、カスタム要素がthis.dataset.messageを読み込みブラウザ上で値を取得しています。

src/components/AstroGreet.astro
---
const { message = 'Welcome, world!' } = Astro.props;
---

<!-- message propをdata属性に保存する -->
<astro-greet data-message={message}>
  <button>Say hi!</button>
</astro-greet>

<script>
  class AstroGreet extends HTMLElement {
    constructor() {
      super();

      // data属性からmessageを読み込む
      const message = this.dataset.message;
      const button = this.querySelector('button');
      button.addEventListener('click', () => {
        alert(message);
      });
    }
  }

  customElements.define('astro-greet', AstroGreet);
</script>

これでコンポーネントは何度でも利用でき、異なるmessageを表示できます

src/pages/example.astro
---
import AstroGreet from '../components/AstroGreet.astro';
---

<!-- messageの初期値「Welcome, world!」を利用する -->
<AstroGreet />

<!-- propsとしてカスタムmessageを渡す -->
<AstroGreet message="Lovely day to build components!" />
<AstroGreet message="Glad you made it! 👋" />