Hotwireとは何なのか?
はじめに
HotwireはBasecampが発表した、モダンなWebアプリケーションを作るための新しいアプローチです。名前もHTML OVER THE WIREから来ているように、HotwireではHTMLをサーバーから送ります。「それ普通のWebアプリケーションでは?」と思う方もいるかもしれませんが、SPA + APIサーバでJSONが使われるのに対し、SPAと同様の体験をHTMLを中心に置いて作るアプローチであることを示す表現です。
僕個人は、最初は「ふ〜ん」という感じだったんですが
と、ある程度触ってみて良さが理解できてきたので、Hotwireを使うと何が嬉しいのか、Hotwireの各要素の紹介を記事としてまとめてみることにしました。
Hotwireを使うと何が嬉しいのか
僕が触ってみて感じた一番のメリットは、
ユーザにとっても、開発の進め方もProgressive Enhancementにできること
です。
最近、全てをJavaScriptに!全てをクライアントに!という流れの揺り戻しとして、SPA的な体験と開発効率向上の両立を目指す、サーバ側に重きを置いた技術がいくつもでてきています。Hotwireの前にも
- Phoenix LiveView
- Livewire
- StimulusReflex
- React Server Components
といったものがあります(以前記事にしたのでよかったらどうぞ)。この中でもHotwireが特徴的なのはステートレスかつ、Webの標準に寄り添ったアプローチになっているところです。
Webの標準に寄り添っているからこそ、
- サーバサイドがどんな言語であるかに依らずに使える
- Web標準のもとで既にされている決断を開発者が無駄にしなくてよくなっている。Webの他の要素からの類推で使える
- リッチなクライアントでなくても、普通の体験を享受できる(ユーザにとってのProgressive Enhancement)
といったメリットがあります。
また、Hotwireの構成要素は、Webの標準に近いものの上に、標準から離れるけれども複雑なことが実現できるもの、と段階的に積み重なるようにできています。これに沿って作っていくことで、開発の進め方もProgressive Enhancementにすることができます。
Hotwireで可能になるProgressive Enhancementな開発
Hotwireを使うと、いきなり全ての部分で最高を目指すのではなく、全体に対する最低ラインの品質を迅速に確保して、重要な部分のみを追加の機能を使って良くしてくことができるようになっています。これが、ユーザにとってProgressive Enhancementになるのと同じように進んでいくので、僕は開発の進め方がProgressive Enhancementであると表現しています。
まずHotwireの登場人物を見てみましょう。
- Turbo
- Turbo Drive
- Turbo Frames
- Trubo Streams
- Turbo Native
- Stimulus
- Strada
これを見ての、個人的な第一印象は、「なんか、概念が多くて無駄に複雑に見えるなぁ」でした。しかし、実際に触ってみてわかったのですが、最初から全てを使う必要もなければ、理解している必要もありません。力を注ぐべき場所がわかってから、その改善に必要な要素を使っていけばいいのです。
例えば、Hotwireを使って新規にアプリケーションを開発する場合、以下のように進めていくことができます。
- Web
- JavaScriptなしでも使えるような素のWebアプリケーションを書く
- Turbo Driveを入れるだけで、ページ遷移が少し最適化される(ほぼ導入コストゼロ)
- 必要な部分にStimulusでちょっとした振る舞いをつける
- リリース
- 良く使われる重要な部分をTurbo Framesを使って改善する
- 良く使われる重要な部分をTurbo Streamsを使って更に改善する
- 良く使われる重要な部分かつクライアント側に複雑な状態を持つ必要がある部分はJavaScriptをバリバリ書く
- Native
- Turbo Nativeを使って最低限のアプリを作る(素のWebアプリケーションがあればコストは低いです)
- リリース
- (たぶんここにStrada)
- 良く使われる重要な部分をSwift, Kotlinで書き直してネイティブの要素に置き換えていく
このように、Turbo DriveやTurbo Nativeで迅速に最低ラインの品質まで持っていって、力を注ぐべき場所がわかってから、その改善に必要な次の要素を使っていけばいいのです。
既にあるアプリケーションに採用するにしても、同様の段階的な改善をしてくことができます。
Webの場合、SPAと比べたときに、ユーザ体験と開発コストの関係はイメージとしては以下のような感じです。
どちらが嬉しいのかは状況によると思います。
例えば以下のような感じでしょうか。
Hotwireを選ぶとき
- ビジネスになるのかが不明で、なるべく早くリリースして試行錯誤したいとき
- スモールチーム
- 既にあるHTMLを吐き出すWebアプリケーションを、限られた開発リソースで改善していきたい
SPAを選ぶとき
- クライアント側に複雑な状態を持つ必要があるアプリケーションのとき
- 操作から反応までの許容レイテンシーが厳しい部分が多い
- ビジネスになるのかの検証が終わっていて、高水準な体験が求められるとき
- 成功しているアプリケーションの書きかえ
- 一部の受託開発
Hotwire概観
以下ではHotwireの各要素が実際にどういうものなのかをみていきます。まずは大きく3つあります。
Turbo
クライアントとサーバ間のHTMLのやりとり、DOMの更新を担うJavaScriptライブラリです。
詳しくは後述します。
Stimulus
HTMLを主役としてちょっとした振る舞いの追加のみをするためのJavaScriptライブラリです。
ReactやVueではJavaScriptで状態を持ち状態→DOMとするのに対して、HTMLのdata attributeに状態を持たせてJavaScript側をなるべくステートレスにする作りになっています。MutationObserverを使っているので「なんか知らんけど壊れるからTurbolinksはオフ!」みたいな悲劇が起きづらくなっています。
シンプルで全体像を把握するのには数時間もいらないぐらいだと思うので、Handbookを読んでみるのがオススメです。
この記事ではこれ以上扱いません。
Strada
ネイティブ用のStimulusのようになりそうな雰囲気のことが書いてありますが、まだ公開されておらず不明です。
この記事ではこれ以上扱いません。
Turboについて
Turboは「クライアントとサーバ間のHTMLのやりとり、DOMの更新を担うJavaScriptライブラリ」と説明しました。
Turboは4つの要素からなります。
- Turbo Drive
- Turbo Frames
- Trubo Streams
- Turbo Native
Webなら基礎的なものから
- Turbo Drive
- Turbo Frames
- Turbo Streams
となっています。
Native用には、Turbo Nativeがあり、WebViewを使って、Turbo Driveでの遷移をNativeのナビゲーションにすり替えてくれます。
以下ではWebで使う3つについて見ていきます。
1. Turbo Drive
Turbo Driveは、かつてTurbolinksであったものです。
リンクやフォーム送信というWeb標準で遷移が発生するところにフックして
- ブラウザのデフォルトの遷移ではなくJavaScriptでfetch
- レスポンスのHTMLからhead内の必要なものを処理
- bodyを新しいものにすり替え
- History APIでlocationを更新
します。
仮想DOMのような賢い差分更新はしませんが、毎回assets類をすべて読み込み直すことがなくなるのでページ遷移が早くなります。Turbo Driveが使えない場合は普通のページ遷移になるだけです。
それほど重要ではない大多数のページでは、これで事足りると思います。
Turbo Driveの限界
ページ内の一部のみを更新ができない。
2. Turbo Frames
Turbo Framesを使うことで
- Turbo Driveではできなかった部分的な更新
- ページ内の一部分を遅延読み込み
が出来るようになります。
turbo-frame
というcustom elementが主役で、これはcontextが分離されていないiframe
として作られています。
部分的な更新
リンクやフォーム送信を拾って、fetchするところまではTurbo Driveと同じですが、
リンク、フォームに紐づくtargetが指し示す turbo-frame
のみがすり替えられます。
fetchしたURLに直接遷移しても普通に動くように作ることができながら(Progressive Enhancement)、一部分を部品として使えるようになります。
turbo-frame
内にあるリンクやフォームは、targetがデフォルトでそのturbo-frame
のidになります。
targetはdata-turbo-target
attributeで指定することもできて、特別な値として_top
を指定すると、Turbo Driveと同様の遷移になります。
iframe
内のa[target="_top"]
は親フレームで遷移を発生させますから、iframe
と非常に類似した概念を使っていますね。
使い道としては
- デモ動画のように、コンテンツをその場で編集したいときに使ったり
- モーダルの中身をすりかえたり
など、かなり幅広い場面で使えると思います。
遅延読み込み
またsrc
attributeを設定すると遅延読み込みする要素としても使えます。遅延読み込みのturbo-frame
を使うと以下のようなことができます。
- 認証が必要な要素を遅延読み込みするようにして、それ以外の部分をHTTPでキャッシュできるようになる
- 更新頻度の違うデータを含む部分を遅延読み込みするように分割することで、更新頻度が高いデータに引きずられることなくキャッシュできてキャッシュヒット率が上がる。
またloading="lazy"
だとさらにview port内に入ってから読み込むようにもできます。ここもiframe
に合わせてますね。
Turbo Framesの限界
- リンク、フォーム送信起因でしかDOM操作をできない
- 複数フレームの同時更新をすることはできない
3. Turbo Streams
Turbo Framesに対して、DOM操作が発生するタイミング、DOM操作自体をより柔軟に行えるようにするものがTurbo Streamsです。
制限されたDOMへの操作を、turbo-stream
を含むHTMLフラグメントとして表現し、それを使ってDOM操作をします。turb-stream
はaction
で操作を、target
で対象要素のidを、template
タグでコンテンツを持ちます。
<turbo-stream action="append" target="messages">
<template>
<div id="message-1">#message-1が#messagesに加えられる例</div>
</template>
</turbo-stream>
action
としては、今のところ
- append
- prepend
- replace
- remove
があります。
Turbo StreamsによってDOM操作が発生するのは主に2パターンあります。
- フォーム送信などTurboが扱ったHTTPリクエストのレスポンスMIME typeがTurbo streams用のものだった場合
- Turbo Streamsに接続されたsourceがメッセージを受け取ったとき
- 例えばWebSocket, Sever Sent Events, Web Pushを使ったsourceなど
Turbo Streamsを使えば、リアルタイム性が求められるところでも、サーバーからのプッシュで更新をすることができます。
Railsでいうと、ajaxのレスポンスにJavaScriptを入れていたようなもの(RJS)に対して以下の点が良くなっています。
- 可能なことを制限して秩序をつくる(REST的)
- それ以上のことをはStimulusでやるようにするための制約
- CSPのポリシーを厳しくできる
最後に
以上です。
Hotwireはユーザにとっても開発もProgressive Enhancementになり、条件にあえば、かなりいい選択肢だと思います。Stradaにも期待してしまいます。
現状Turboはbetaで、今変わっていっている部分もあるので、コードを読んだりIssueを追えないと使い始めるにはまだ少し早いかもしれません。
ただ、ドキュメントもなかなか面白いので是非是非読んでみてください。
Discussion
「Hotwireで可能になるProgressive Enhancementな開発」の節ですが、おそらく https://fullstackradio.com/151 でDHH氏が述べたことが出典となっていると思いますので、もしそうであればその旨を明記した方が良いと思いました!(今のままだと読者は @en30 さんの主張なのかDHH氏が意図していることなのか区別がつかないため)
なるほど。確かに https://fullstackradio.com/151 も大いに参考にしてます。ご指摘の節で参考にしている部分はHotwireの性質の話なので、僕の主張ととられるとは思いませんでした。そのPodcastも含め記事を書く上で参考にしたものをこのコメントの最後にまとめておきますね。
基本的にはHotwireの性質の説明をしているつもりで、僕の主張があるとしたら
ぐらいですかね。
「開発が段階的に進められる」というのは https://fullstackradio.com/151 でも強く言われていたと思いますし、「ユーザにとってや、Hotwireをデザインする上での哲学がProgressive Enhancementである」というのも開発者のSam Stephensonが明言しています。
ただ、 https://hotwire.dev/ では
というマーケティング的な意図なのかProgressive Enhancementに関してはあまり前面に出ていない印象でした。僕個人としては触ってみて良いと感じたのは哲学の部分だったので、この記事ではそれを強調しています。
参考
漏れがあるかもしれませんが、他に参考にした(or 昔聞いて影響を受けてそうな)ものは以下です。