Top

About

Hotwire for Frontend Developers

フロントエンドエンジニアのためのHotwire入門

Hotwireは優れたUXを小さいチームで作るための技術#

Hotwireおよびその前身の技術(TurboLinks, UJS)は、ウェブデザイン会社として創業した37signals社が、BasecampHeyなどのSaaS製品を開発するために作成されたものです。ユーザ向けのすべての画面で使われているだけではなく、iOS、Android用のモバイルアプリもHotwireで作られています。

一方で37signals社の各チームのサイズはデザイナーを含めてたったの3人です

このように、Hotwireは開発効率が非常に高く、優れたUI/UXを小さいチームで実現する技術です。

Next.jsに勝るとも劣らないUI/UX #

「Hotwireは実装が楽だけど、限界がある」 と考えている人がいます。React/Next.jsほどのUI/UXは作れないと思っている人がいます。「管理画面はHotwireでも良いけど、お客様向け画面はReactにしよう」という声も聞こえます。しかしこれは誤解です。お客様がお金を払ってでも使いたくなる製品がHotwireだけで作成できることを、37signals社が明確に実証ずみです。さらに本サイトでは細かい仕組みの議論をしながら、HotwireのUI/UXの方がむしろ優れている面もあることを紹介します。

実際、エンドユーザのUXが決定的に重要なCookpadでもHotwireを使用しています

もちろんすべてにおいてHotwireがReact/Next.jsに勝るわけではありません。ただしほとんどのケースでは、少なくとも対等であると考えて間違いありません。

Hotwire is capable of High fidelity UI/UX

Hotwireの構成 #

Hotwireは以下の3つのパーツから構成されています。

  • Turbo: Turboはサーバにレクエストを投げて、返ってきたHTMLをDOMに埋め込むためのライブラリです。いわゆる非同期通信(AJAX)を担当します。大きな特徴はJSONを介して通信をせず、敢えてHTMLのみを使う点です。HTMLのみの通信により、ブラウザ側の処理を大幅に簡略化しています。Turbo自身はさらにTurbo Drive, Turbo Frames, Turbo Streamsに分かれています。
    (Turbo以前のRailsではサーバからJavaScriptを返すことが一般的で、Turboよりむしろ遥かに柔軟性がありました。この柔軟さを捨てて、シンプルさを優先したのがHotwireと言えます)
  • Stimulus: StimulusはHTMLを新たにレンダリングするのではなく、すでにあるHTMLにJavaScriptを結びつけることに注力しています。再利用可能なControllerというまとまりを作ることで、イベントハンドラのスパゲッティを避けています
  • Native: Hotwire NativeはWebとiOS, Androidを繋ぎ合わせる役割を担う、モバイルアプリ作成のためのライブラリです。以前はStradaと呼ばれていましたが、大幅にパワーアップされ、名前も変更になりました。React Nativeなどと異なり、基本的にはウェブ版のHTML, CSSをそのまま使いますが、必要に応じてネイティブなUI要素を利用できます

上記の3つを組み合わせることにより、シンプルさを維持しつつ、モダンフロントエンドの要件を十分に満たすウェブサイト、さらにはネイティブアプリの作成が可能になります。

大雑把に言えば、守備範囲としてはHotwire == React + Next.js + React Nativeの関係となります。

Hotwireの構成
hotwire component structure image

Turboの構成 #

Turboはfetchを使い、ウェブページの部分置換をするライブラリです。Hotwireの中心的な技術です。置換する範囲と付随する機能に応じて、3種類の置換方法があります。

  • Turbo Drive:Turbo Drivebodyタグの中身を丸々置換する技術です。Next.jsのrouter、あるいはReact Routerに相当し、ページ全体をSPA的に置換・遷移する仕組みです。Linkタグのような特別なタグは使わず、aタグがすべて自動的にTurbo Driveを使うようになります。
  • Turbo Frames: Turbo Framesは部分置換を実現するものです。画面の一部を置換するだけではなく、画面を 「枠」 に分割する性質があり、デフォルトでは枠内の a タグおよび form タグも中に閉じ込めるように動きます。ルータとの連携Lazy Loadなども用意され、画面の部分置換だけでなく、関連するUXもパッケージされています。柔軟性も高く、これだけでほとんどの部分置換は可能です (Turbo Streamsをあまり使わなくても用が足りてしまう)。
    Next.js app routerのLayoutに近い性質もありますが、URLとの連動が必須ではないことや、1つのページに複数のTurbo Frameが配置できることなど、より小回りが効く柔軟な仕組みです。
  • Turbo Streams: Turbo Streamsは画面を細かく、柔軟に置換する技術です。IDで指定された要素を1つずつ追加・置換したり、削除したりできます。またWebSocketを介した置換も可能になっています。柔軟性は非常に高いのですが、それだけに置換ステップ数が増大する傾向があり、必要なところで慎重に使うのがポイントではないかと思います。jQueryprepend(), append(), remove(), html()をHTML属性から呼び出す感覚に近いとも言えます。Turbo StreamsとTurbo Framesの使い分けは明確ではなく、人よってどちらを多く使うかが異なります。私はTurbo Framesを多く使いますので、本サイトもこちらが中心になっています。Next.jsで言えば、Turbo StreamsはTurbo Framesとともに、Client ComponentやSSRしたページからuseEffect()を呼び、画面を部分更新するのに相当します。

この他、TurboにはMorphingがあります。これはReactの差分検出処理と似たものであり、ブラウザのDOMとサーバから送られてきたHTMLの差分を検出し、なるべくブラウザのステートを保持しつつ更新処理をかけるものです。

Turboは上記のたった3つのパーツしかないのですが、実際にやってみるとこれだけでほとんどのインタラクティブなUIが作れてしまいます。

Turboの構成
turbo component structure image

Hotwireの特徴 #

  • バックエンド技術非依存: Ruby on Railsに限らず、DjangoLaravelJavaNodeWordpressなど、バックエンド技術がなんであってもHotwireは使用できます。実際、本サイトのHotwireデモはすべてNext.js のAPI routesで動いています。
  • 大幅なUXを向上: ウェブサイトのUXを大幅に向上させます。体感レスポンスタイムの大幅短縮、画面の部分置換など、モダンフロントエンドのUX要件をカバーできます。 具体的には本サイトの各例をお確かめください。
  • 学習時間と工数の削減: Hotwireは昔ながらのサーバでのHTML生成をするアプローチです。JSON APIも使いません。ReactやNext.jsのように新しいコンセプトを学習し、JSON APIを作る複雑さがありませんので、学習時間と作業工数を大幅に削減できます。
  • コンポーネント化: Hotwireのコンポーネント化は各バックエンド技術のテンプレートエンジンに委ねられています。Ruby on Railsであればpartialview helper、もしくは最近話題のViewComponentPhlexなどのコンポーネント化技術があります。Laravel, Djangoなどもそれぞれのコンポーネント化技術があります。
  • 注目されている技術です: Elixir PhoenixのLiveview、PHP LaravelのLivewire、さらにHotwire同様にバックエンド非依存のHTMXなど、Hotwireと同様のアプローチが最近注目を集めています。HTMXはDjangoやGo、Spring MVCで話題になっており、さらにJavaScriptフレームワークの世界でもAstroはHTMXを意識したpage partialsという機能を導入しています。
  • セキュリティが高い: レンダリング済みのHTMLのみをブラウザに送信するので、誤ってプライベートな情報を漏洩する心配がありません。例えば秘密キーをブラウザに預ける必要がなく、またJSON APIに機密情報を流してしまうこともありません。詳しくはセキュリティの解説で議論しています。

Hotwireのステート管理 #

Reactの世界ではステート管理が非常に大きなトピックになっています(useStateからReduxまで、多数の仕組みが使われています)。ここではHotwireにおけるステートの一般的な考え方について紹介します。

  • Reactは単方向データフロー(one-way data flow): Reactでは単方向データフローが非常に重要な概念です。すべてはステートから始まり、親コンポーネントから子コンポーネントにステートを渡しながら画面がレンダリングされます。ステートは各コンポーネントがuseStateで保持したり、あるいはグローバルにuseContextやReduxなどを使って保持されます。ユーザの操作等によって画面の複数箇所が同時に更新され、操作をする箇所も多いような複雑なアプリにおいては、これは非常に有効なコンセプトです
  • Turboはサーバにステート管理を任せます: HotwireのTurboはサーバにステートを持たせ、同じものはブラウザでは持たないという考え方です。HTMLはサーバでレンダリングされますので、ブラウザステートは原則として考慮されません。またHTML formによってサーバにデータを送信する場合も、form自身がネイティブに保有するステート管理に任せます。このような仕組みにより、ブラウザが管理するステートは激減します。その一方で積極的にキャッシュを行い、ネットワーク通信による遅延を最小化する仕組みを各種用意しています。Reactも最近は同じような形でステート管理をサーバに任せることが多くなっています。
  • Stimulusはブラウザ内のステートを管理します: HotwireのStimulusはブラウザのみが保有し、サーバと共有されないステートを管理します。例えばアコーディオンやプルダウンメニューの開閉状態がこれに相当します。簡単なステートの場合は、敢えてステートを別個に保持せずに、DOMに加えた変更そのものをステートとします。例えば開閉状態を示すopened CSSクラス、あるいはaria="busy"などを使います。より複雑なステートを扱う場合はStimulusのValuesを使用します。これはHTML要素のdata-*属性に書き込まれて保持されます(Object型のデータも保持可能)。またchange callbackが用意されていますので、Stimulus Valuesに加えた変更が自動的にHTML要素に反映される仕組みが作れます。例えばChart.jsのグラフをStimulus Values経由で管理すると便利です

このようにHotwireのステート管理の考え方は、サーバが持つべきものはTurbo経由でサーバに持たせ、ブラウザが持つべきものはStimulusで処理するというものです。Stimulusの担当範囲は"sprinkles of JavaScript"(「軽く撒いたJavaScript」)までです。リッチテキストエディタのような複雑なものは、通常はStimulusで作りません。

本サイトの構成 #

  • 本サイトは、Hotwireの部分も含めてすべてNext.jsで書かれており、コードはGitHubに公開しています。さくらのVPSにでデモをデプロイしています。一方でコードをGitHubで確認し、他方でUI/UXをデモで確認できるため、HotwireとNext.jsの強み・弱みが確認しやすいと思います。
  • HotwireはEJS: Hotwireはバックエンド技術非依存なので、HTMLが出力できればどこでも動きます。本プロジェクトでは Next.js pages routerAPI routesから HTMLをレスポンスとして返しています (/api/hotwire/配下)。 テンプレートエンジンはEJSを使っています。Hotwireの構成要素であるTurboStimulusはそれぞれbuild済みのものをダウンロードし、public/hotwire/javascriptに配置しています。 またCSSはTailwindを使用しています。
  • ReactはNext.js pages routerが中心: Hotwireと比較するためのReact側は、目まぐるしく変わるフロントエンド開発環境の中でも、比較的頻繁に見られる技術を選定しました。
    • フレームワークはNext.jsを使用しています。ただしapp routerはまだ使用しているプロジェクトが少ないと考え、pages routerを中心に作成しています。ただし一部app routerと比較したいケースの時はこれも使っています。
    • 内容が頻繁に更新されるウェブアプリ(例えば業務アプリや管理画面、注文予約アプリ、プロジェクト管理アプリなど)を想定しているため、古いデータが表示されたままになってしまうタイプのキャッシュは使用していません。特にapp routerを使っている時はRoute CacheFull Route CacheData Cacheオフにしています
  • Next.jsはBFFとして使われている想定: 一貫性を持たせるために、システムは下記の構成になっていると想定しています。
    • Hotwire側はHTMLを出力するサーバを想定しています。Ruby on RailsでERBを使ったり、LaravelでBladeを使ったりするタイプを考えています。したがってエンドポイントが直接データベースにアクセスし、データを取得する構成です。
    • Next.js側がBFF(Backend-for-Frontend)として使われることを想定しています。Next.jsから直接データベースにアクセスするのではなく、別途バックエンドのAPIサーバがあり、そこを介してデータを取得する構成です。したがってPages router、API routerのコンポーネントは直接データベースにアクセスせず、常にfetchを使ってデータを取得します。
  • データベースは使用しない: デモサイトの運用を考えて、データベースを使いません。システムのメモリやCookie等に持たせています。ただし内部的にはデータベースアクセスしているかのようなgetUsers()などの関数を用意しています。
  • 遅延: 高速なサイトだとどんなフロントエンド技術を使ってもサクサク動いてしまいます。何をやっても非常に快適になってしまい、技術の違いが見えなくなります。そこで本サイトではあえて数百msの遅延を入れています。ただし静的なファイルやNext.jsのSSGなど、一般にCDNの載せそうようなものについては遅延を入れていません。

Hotwireのリソース #

Hotwireの学習は運用の助けになるサイトを集めています。下記のリンクをご覧ください。