Hotwireとは何なのか?

2021/01/24に公開2

はじめに

HotwireBasecampが発表した、モダンな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と比べたときに、ユーザ体験と開発コストの関係はイメージとしては以下のような感じです。
comparison

どちらが嬉しいのかは状況によると思います。

例えば以下のような感じでしょうか。

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なら基礎的なものから

  1. Turbo Drive
  2. Turbo Frames
  3. Turbo Streams

となっています。

Native用には、Turbo Nativeがあり、WebViewを使って、Turbo Driveでの遷移をNativeのナビゲーションにすり替えてくれます。

以下ではWebで使う3つについて見ていきます。

1. Turbo Drive

Turbo Driveは、かつてTurbolinksであったものです。
リンクやフォーム送信というWeb標準で遷移が発生するところにフックして

  1. ブラウザのデフォルトの遷移ではなくJavaScriptでfetch
  2. レスポンスのHTMLからhead内の必要なものを処理
  3. bodyを新しいものにすり替え
  4. History APIでlocationを更新

します。
Turbo Drive
仮想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に合わせてますね。

https://twitter.com/_swanson/status/1348808477196615681

Turbo Framesの限界

  • リンク、フォーム送信起因でしかDOM操作をできない
  • 複数フレームの同時更新をすることはできない

3. Turbo Streams

Turbo Framesに対して、DOM操作が発生するタイミング、DOM操作自体をより柔軟に行えるようにするものがTurbo Streamsです。

制限されたDOMへの操作を、turbo-streamを含むHTMLフラグメントとして表現し、それを使ってDOM操作をします。turb-streamactionで操作を、targetで対象要素のidを、templateタグでコンテンツを持ちます。

 <turbo-stream action="append" target="messages">
   <template>
     <div id="message-1">#message-1が#messagesに加えられる例</div>
   </template>
 </turbo-stream>

Turbo Streams

actionとしては、今のところ

  • append
  • prepend
  • replace
  • remove

があります。

Turbo StreamsによってDOM操作が発生するのは主に2パターンあります。

  1. フォーム送信などTurboが扱ったHTTPリクエストのレスポンスMIME typeがTurbo streams用のものだった場合
  2. 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を追えないと使い始めるにはまだ少し早いかもしれません。
ただ、ドキュメントもなかなか面白いので是非是非読んでみてください。
https://hotwire.dev/

Discussion

yasaichiyasaichi

「Hotwireで可能になるProgressive Enhancementな開発」の節ですが、おそらく https://fullstackradio.com/151 でDHH氏が述べたことが出典となっていると思いますので、もしそうであればその旨を明記した方が良いと思いました!(今のままだと読者は @en30 さんの主張なのかDHH氏が意図していることなのか区別がつかないため)

en30en30

なるほど。確かに https://fullstackradio.com/151 も大いに参考にしてます。ご指摘の節で参考にしている部分はHotwireの性質の話なので、僕の主張ととられるとは思いませんでした。そのPodcastも含め記事を書く上で参考にしたものをこのコメントの最後にまとめておきますね。

基本的にはHotwireの性質の説明をしているつもりで、僕の主張があるとしたら

  • 触ってみてHotwireのよかったところは「ユーザにとっても、開発の進め方もProgressive Enhancementにできること」
    • MVPに合っている
    • SPAより合っている状況の人も結構いると思う
  • もっと知られるべき良いもののはずだし、知ってくれ

ぐらいですかね。

「開発が段階的に進められる」というのは https://fullstackradio.com/151 でも強く言われていたと思いますし、「ユーザにとってや、Hotwireをデザインする上での哲学がProgressive Enhancementである」というのも開発者のSam Stephensonが明言しています

ただ、 https://hotwire.dev/ では

  • SPAへのカウンターであるポジションを明らかにする
  • Turbolinksのリブランディング

というマーケティング的な意図なのかProgressive Enhancementに関してはあまり前面に出ていない印象でした。僕個人としては触ってみて良いと感じたのは哲学の部分だったので、この記事ではそれを強調しています。

参考

漏れがあるかもしれませんが、他に参考にした(or 昔聞いて影響を受けてそうな)ものは以下です。