WebSocketではないTurbo Streamsのsourceを作って遊ぶ
2020年の年末にBasecampからHotwireが発表されました。
そのなかのTurbo Streamsですが、sourceはWebSocketである必要はなく、自分でsourceを作ることもできます。以下では簡単なsourceを作って遊んでみます。
以下で使っているバージョンは@hotwired/turboの7.0.0-beta.3です。
Turbo Streamsが要求していることは何なのか
特にドキュメントで明示されているわけではないですが、ActionCableをsourceとして利用しているturobo-railsのTurboCableStreamSourceElementが参考になります。
sourceは@hotwired/turboのconnectStreamSourceで利用することができますが、きちんと型の情報も定義されています。
connectStreamSource(source: StreamSource): void;
でStreamSourceはといういと、
type StreamSource = {
addEventListener(type: "message", listener: (event: MessageEvent) => void, options?: boolean | AddEventListenerOptions): void
removeEventListener(type: "message", listener: (event: MessageEvent) => void, options?: boolean | EventListenerOptions): void
}
MessageEventをdispatchするEventTargetのようなものであることを要求しているようです。
MessageEventの中身としてはdataにturbo-streamを入れるとよさそうです。turbo-streamはほぼHTMLのようなものです。
時刻を更新するだけのsourceを作ってみる。
では実際に作っていきましょう。
div#timerを作って、それを毎秒更新するためのsourceを作ってみます。
EventTarget的なものを作ってもいいですが、turbo-railsを参考にcustom elementsを作ってサボります。
Turbo Streamsのactionにはappend, prepend, replace, removeがありますが、ここではreplaceを使って、ただ書き換えるだけにします。なのでMessageEventのdataとしては
<turbo-stream action="replace" target="timer">
<template>
<div id="timer">
${new Date().toISOString()}
</div>
</template>
</turbo-stream>
を吐き出します。
以上のことを素直に実装すると、
import { connectStreamSource, disconnectStreamSource } from "@hotwired/turbo"
class TickElement extends HTMLElement {
timerId?: number;
connectedCallback() {
connectStreamSource(this);
this.timerId = setInterval(this.dispatchMessageEvent.bind(this), 1000);
}
disconnectedCallback() {
disconnectStreamSource(this);
if (this.timerId) clearInterval(this.timerId);
}
dispatchMessageEvent() {
const data = `
<turbo-stream action="replace" target="timer">
<template>
<div id="timer">
${new Date().toISOString()}
</div>
</template>
</turbo-stream>
`
const event = new MessageEvent("message", { data });
this.dispatchEvent(event);
}
}
customElements.define("tick-source", TickElement);
といった感じになります。これをHTMLから
<div id="timer"></div>
<tick-source />
と利用すれば…

自分で作ったsourceで更新することができました!
最後に
一応成果物は にあります。
Hotwireの旨みはサーバサイドのテンプレートを使い回すことで楽ができることなので、今回作ったsourceは実用的とは言えません。しかしsourceを書きさえすれば、Server Sent EventsでもWeb Pushでも、ポーリングだろうとTurbo Streamsで利用することができるということです。意外と簡単に書けるので色々やりようがありそうですね。
Discussion