htmxでSSEを簡単操作! hx-sse属性の仕組みとサンプルコード


hx-sse 属性は、Server-Sent Events (SSE) と直接やり取りするための機能を提供します。SSE は、サーバーからクライアントへリアルタイムにデータをプッシュする技術です。これにより、Web ページを更新することなく、最新の情報をユーザーに提供することができます。

属性の構文

hx-sse 属性の値は、スペースで区切られた1つ以上のキーワードで構成されます。それぞれのキーワードは以下の役割を果たします。

  • swap:<eventName>: SSE メッセージの内容を、イベント名が一致するDOM ノードに差し替えます。
  • connect:<url>: SSE イベントストリームのURLを指定します。

例:基本的な使用

<div hx-sse="connect:/event_stream swap:eventName">
  ...
</div>

上記の例では、以下の処理が行われます。

  1. /event_stream のURLにあるSSE イベントストリームに接続します。
  2. イベント名が eventName であるメッセージを受信すると、そのメッセージの内容を <div> 要素内に差し替えます。

例:複数のイベントを処理

<div hx-sse="connect:/event_stream">
  <div hx-sse="swap:eventName1">
    ...
  </div>
  <div hx-sse="swap:eventName2">
    ...
  </div>
</div>
  1. /event_stream のURLにあるSSE イベントストリームに接続します。
  2. イベント名が eventName1 であるメッセージを受信すると、そのメッセージの内容を eventName1 要素内に差し替えます。
  3. イベント名が eventName2 であるメッセージを受信すると、そのメッセージの内容を eventName2 要素内に差し替えます。
<div hx-sse="connect:/event_stream">
  <button hx-trigger="sse:eventName">更新</button>
</div>
  1. /event_stream のURLにあるSSE イベントストリームに接続します。
  2. イベント名が eventName であるメッセージを受信すると、button 要素をクリックします。


サーバーからランダムな数値をプッシュし、それをDOMに表示する

HTML

<div hx-sse="connect:/random-numbers swap:number">
  <span id="number">0</span>
</div>

JavaScript (サーバー側)

const events = new EventSource('/random-numbers');

events.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  const number = data.number;
  document.getElementById('number').textContent = number;
});

説明

  • JavaScript コードでは、EventSource オブジェクトを使用して SSE イベントストリームに接続し、message イベントが発生したときに、受信したメッセージを解析して #number 要素内の数値を更新します。
  • HTML コードでは、hx-sse 属性を使用して /random-numbers の SSE イベントストリームに接続し、イベント名が number であるメッセージを受信すると、そのメッセージの内容を #number 要素内に差し替えます。

ボタンをクリックしてサーバーにイベントを送信し、サーバーからメッセージを受信してDOMに表示する

この例では、ボタンをクリックするとサーバーにイベントを送信し、サーバーから受信したメッセージを #message 要素内に表示します。

HTML

<div>
  <button hx-trigger="sse:send-message">送信</button>
  <span id="message"></span>
</div>

JavaScript (サーバー側)

const events = new EventSource('/events');

events.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  const message = data.message;
  document.getElementById('message').textContent = message;
});

addEventListener('message', (event) => {
  if (event.data === 'send-message') {
    const message = 'Hello from server!';
    const data = JSON.stringify({ message });
    events.dispatchEvent(new MessageEvent('message', { data }));
  }
});

説明

  • また、message イベントリスナーを追加して、send-message イベントを受信したときに、Hello from server! というメッセージをサーバーから送信します。
  • JavaScript コードでは、EventSource オブジェクトを使用して SSE イベントストリームに接続し、message イベントが発生したときに、受信したメッセージを解析して #message 要素内のメッセージを更新します。
  • HTML コードでは、hx-trigger 属性を使用して button 要素をクリックしたときに sse:send-message イベントを送信し、#message 要素に hx-sse 属性を追加して、イベント名が message であるメッセージを受信すると、そのメッセージの内容を #message 要素内に差し替えます。

この例では、シンプルなチャットアプリケーションを作成します。ユーザーはメッセージを入力して送信ボタンをクリックすると、そのメッセージがサーバーに送信され、他のユーザーにブロードキャストされます。

HTML

<div>
  <input type="text" id="message-input">
  <button hx-trigger="sse:send-message">送信</button>
  <ul id="messages"></ul>
</div>

JavaScript (サーバー側)

const events = new EventSource('/chat');

events.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  const message = data.message;
  const username = data.username;
  const li = document.createElement('li');
  li.textContent = `${username}: ${message}`;
  document.getElementById('messages').appendChild(li);
});

addEventListener('message', (event) => {
  if (event.data === 'send-message') {
    const username = prompt('名前を入力してください:');
    if (!username) return;
    const message = document.getElementById('message-input').value;
    const data = JSON.stringify({ username, message });
    events.dispatchEvent(new MessageEvent('message', { data }));
  }
});
  • HTML コードでは、input 要素と button 要素を使用して、ユーザーがメッセージを入力して送信できるようにします。また、ul 要素を使用して、受信したメッセージを表示します


Fetch API

Fetch API は、JavaScript で非同期 HTTP リクエストを処理するための標準 API です。SSE を使用するには、fetch() 関数を使用して SSE イベントストリームに接続し、addEventListener('message', ...) を使用してイベントを処理する必要があります。

長所

  • コードが柔軟で、より複雑な処理が可能になります。
  • 標準 API であり、幅広くサポートされています。

短所

  • htmx の他の機能との統合が難しくなる場合があります。
  • hx-sse よりも記述量が多くなります。

const url = '/events';

fetch(url)
  .then((response) => response.json())
  .then((data) => {
    const message = data.message;
    const username = data.username;
    const li = document.createElement('li');
    li.textContent = `${username}: ${message}`;
    document.getElementById('messages').appendChild(li);
  });

WebSockets

WebSockets は、クライアントとサーバー間で双方向のリアルタイム通信を可能にする API です。SSE よりも低遅延で、双方向通信が可能という利点があります。

長所

  • より複雑なアプリケーションに適している
  • 低遅延で双方向のリアルタイム通信が可能

短所

  • htmx との統合が難しくなる場合があります
  • SSE よりも複雑で、ブラウザのサポート状況も限られている

const ws = new WebSocket('/events');

ws.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  const message = data.message;
  const username = data.username;
  const li = document.createElement('li');
  li.textContent = `${username}: ${message}`;
  document.getElementById('messages').appendChild(li);
});

サードパーティ製ライブラリ

SSE を処理するためのサードパーティ製ライブラリがいくつかあります。これらのライブラリは、Fetch API や WebSockets をより簡単に使用できるようにするラッパーを提供したり、追加機能を提供したりすることがあります。

長所

  • 追加機能を提供するものがある
  • Fetch API や WebSockets をより簡単に使用できるようにする

短所

  • htmx との統合が難しくなる場合があります
  • ライブラリによって機能や使い勝手が異なる

ポーリング

ポーリングは、一定間隔でサーバーにリクエストを送信して、新しいデータがないかどうかを確認する手法です。SSE や WebSockets ほど効率的ではありませんが、シンプルなアプリケーションで使用できます。

長所

  • コードがシンプルで、実装が簡単

短所

  • リアルタイム性が低い
  • 非効率で、サーバーへの負荷が高くなる

const url = '/events';

setInterval(() => {
  fetch(url)
    .then((response) => response.json())
    .then((data) => {
      const message = data.message;
      const username = data.username;
      const li = document.createElement('li');
      li.textContent = `${username}: ${message}`;
      document.getElementById('messages').appendChild(li);
    });
}, 1000);

hx-sse の代替方法はいくつかありますが、それぞれに長所と短所があります。最適な方法は、アプリケーションの要件によって異なります。

  • htmx との統合
  • ブラウザのサポート状況
  • 必要なリアルタイム性
  • アプリケーションの複雑性