HotwireとTurbolinks
はじめに
Hotwire aka NEW MAGIC is finally here: An alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire. This includes our brand-new Turbo framework and pairs with Stimulus 2.0 😍🎉🥂 https://t.co/Pa4EG8Av5E
— DHH (@dhh) December 22, 2020
DHH(Ruby on Railsの生みの親)が新作のHotwireについてツイートした。DHHはSPAのアンチ(ツイートからも察しがつく)であり、開発においてJavaScriptを多く導入することを極力避けている。このツイートはTwitter上で熱い議論を巻き起こしたので、ここで僕なりに少し整理してみる。
Hotwireの紹介については、公式の説明をそのまま引用する。
Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire
この説明には2つのポイントがある。
- JavaScriptをあまり使わない
- JSONではなくHTMLを直接送信する
この概念は実は最近現れたものではなく、何年も前からRuby on RailsはTurbolinksと呼ばれる類似の手法を採用していた。
Turbolinksとは何か?
TurbolinksはJavaScriptパッケージであり、通常はRuby on Railsと一緒に使われる(単体のライブラリとして使うことも可能だ)。主にHTMLをfetchして直接差し替える手法によって、通常のページ遷移に伴う再リクエストやCSSのコストを回避する。「JavaScriptを使う必要がない」と言うのは厳密には正しくない。JavaScript自体は存在しており、ライブラリ側ですでに処理してくれているため、開発時にJavaScriptを書かなくて済むというだけのことだ。
例えば、ページ上にこのようなタグがあるとする。
<a href="/articles/1" data-remote="true">link</a>
Ruby on RailsでTurbolinksの機能を有効にしている場合、ユーザーがリンクをクリックした時、実際にはリクエストを再送信するのではなく、Turbolinksが次のような処理を行う。
fetch('/index.html').then(res => res.html())
.then((html) => $page.html(html))
こうすることで、ユーザーがボタンをクリックした際、HTMLが最初から再レンダリングされるのではなく、まずそのページのHTMLファイルを(Ajax経由で)fetchし、JavaScript経由で直接レンダリングする。Ruby on Rails自体がTurbolinksと高度に統合されているため、時にはTurbolinksの存在をほとんど意識することなく、「あれ、なんだかページ遷移が速くなったぞ」と感じるだけになる。
HTMLが大きくなるにつれて効果は薄れていくだろうが、HTML自体が大きくない場合は、より良いユーザー体験を実現できる。
なぜこの手法が効果的なのか?
- ページ遷移でCSSやJavaScriptを再リクエストする必要がなく、ユーザー体験が向上する
- head部分はTurbolinksが自動で処理してくれる
- 追加のJavaScriptを書く必要がほとんどない
- バックエンドエンジニアが最小限の労力でより良い体験を提供できる
注意すべき点
loadのようなイベントは、Turbolinksの仕組み上ページ全体が再読み込みされないため、初回読み込み時しかトリガーされない。turbolinks:loadなどのイベントリスナーに切り替えることを覚えておく必要がある- JavaScriptのインタラクションが増えるにつれて、イベントリスナー同士が衝突しやすくなる
- ページ遷移時に状態が破棄されないため、JavaScriptの書き方が悪いとメモリリークを引き起こしやすい
JavaScriptのインタラクションが増えるにつれて、Turbolinksと組み合わせた際に多重実行によるエラーなど、奇妙な現象が発生することがある。
僕の考え
開発者として多かれ少なかれSPAを書いた経験があるため、実用に耐えうる使い勝手の良いSPAを作るのが容易ではないことは誰もが知っているはずだ。状態管理がうまくいかずにメモリを大量消費したり、状態が非同期になったり、エラー処理が不十分だったり、何かにつけて巨大なJavaScriptバンドルを読み込んだりしてしまう。それならいっそ純粋なSSRによるレンダリングにして、Turbolinksのような仕組みで体験を向上させる方が、むしろ良いアプローチかもしれないと僕は思う。
あとがき
It’s fucking hilarious that the tagline of this is “Hotwire is an alternative approach [...] by sending HTML instead of JSON over the wire”
— Surma (@DasSurma) December 22, 2020
HTML INSTEAD OF JSON? BLOODY MADNESS!
Then again, if this makes “sending HTML over the wire” popular, I’ll take it. https://t.co/7bLGdtJn7u
A lot of folks think serving JSON is more efficient than HTML, but once gzipped they're often the same. Sometimes HTML is smaller.
— Jake Archibald (@jaffathecake) November 15, 2017
Eg https://t.co/hgxyH0N3Ct:
JSON: 18k.
Generated HTML: 17k.
Also, it's much easier to stream HTML & get a progressive render.
JSONはHTMLよりもサイズが小さいため転送効率が良いと考える人もいるが、gzip圧縮後では実際両者は大差なく、時にはHTMLの方が小さいことすらある。だからHTMLを直接レンダリングすることは、決してそれほど許されない罪というわけではないと感じる。
ただ、現状Turbolinksとの統合が最も優れているのはおそらくRuby on Railsだけであり、Hotwireはまだ比較的新しいため、様子見をしている開発者もいるかもしれない。
関連記事
- Three.js で僕の部屋を表現する 僕が React Three Fiber を使って実際の自分の部屋をブラウザ上に再現し、実世界のオブジェクトを目録に見立て、空間の記憶を通してここ数年の生活と仕事について語った話。
- フロントエンドで画像を扱う際に注意すべきこと Jake Archibaldの記事を起点に、現代のレスポンシブ画像の書き方を整理する。なぜwidth/heightを付ける必要があるのか、CSSのaspect-ratioはいつ使うべきか、AVIFとWebPの選び方、そしてpicture/source/srcsetを使ったモバイル向け画像の切り替えについて。
- CSS field-sizing — たった1行のCSSでフォーム要素を自動リサイズする かつてtextareaの自動高さ調整は、JavaScriptでscrollHeightを監視するしかなかった。しかしCSSのfield-sizing: contentなら、わずか1行で代替でき、textarea、input、selectに対応している。本記事では従来のやり方のペインポイントと、field-sizingの使い方をまとめる。
- リンクの下線をもっと見栄え良くする:text-underline-offset デフォルトでは下線と文字が近すぎて、このスタイルを好まないデザイナーもいるし、僕自身もあまり綺麗ではないと感じていた。