Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Railsの技: "プログレッシブエンハンスメント"でHotwire的思考を身につける(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Turboについては以下の記事もどうぞ。

参考: 猫でもわかるHotwire入門 Turbo編

Railsの技: "プログレッシブエンハンスメント"でHotwire的思考を身につける(翻訳)

Hotwireを始める方法や個別のパーツの利用法を学べるチュートリアルはたくさんあります。しかし私は「Hotwireで考える」コツを会得するのにしばらく時間がかかりました。

Hotwireそのものは「HTML-over-the-wire」という包括的なコンセプトに過ぎず、以下のような部品をいつどのように使い分けるかを知っておくことが肝心です。

  1. Turbo Drive
  2. Turbo Frames
  3. Turbo Streams
  4. Stimulus
  5. Turbo Native
  6. Strada

Hotwireはツールの集合体なので、問題を複数の方法で解決できます。Turbo Framesで構築できる機能の中にはTurbo Streamsでも構築できるものもあります。また、「より低レイヤ」のツールを投入して何かを動かすことはいつでも可能です。

では各ツールに手を伸ばすタイミングは、どのように判断すればよいのでしょうか?

私見では、Web開発の黎明期に誕生した「プログレッシブエンハンスメント」という手法こそベストだと思っています。

1: Hotwire抜きでscaffoldしてCRUDアプリを作成してみる

最初にまったく素の状態、つまりHotwire登場以前の伝統的なRailsを出発点として考えてみることにしましょう。

最初はscaffoldでリソースベースのコントローラを作成するでしょう。

記事にコメントを追加するには、comments_controllerを作成し、newアクションを作成してフォームをレンダリングします。

新しいコメントはcomments#createに送信します。Commentモデルはコメントを保存し、続いてpost#showルーティングにリダイレクトで戻ります。

この一連の動作は何も新しくないおなじみのものですが、これはRESTfulなWebアプリケーションを構築するうえで基礎となるものであり、確実に機能します。

2: Turbo Driveを追加する

それでは、このRESTfulなWebアプリケーションのユーザーエクスペリエンスをほんの少しだけ改良したい場合はどうすればよいでしょうか?そんなときはHotwireの第1レイヤであるTurbo Driveを使えばよいのです。新しいバージョンのRailsでは、あなたの知らないうちにTurbo Driveがデフォルトで使えるようになっています。

Turbo Driveがあるおかげで、たとえば+ Addをクリックしたときにcomments#newパスを再読み込みする代わりに、Turbo Driveを用いてページをAjax経由で取得してから<body>のコンテンツを差し替えるようになります。この振る舞い(以前はTurbolinksと呼ばれていました)は、いわゆるSPA(Single Page Application)のレスポンシブな操作性を真似たものですが、Railsはこれまで通りあらゆるものをサーバーサイドでレンダリングします。

Turbo Driveは、コメントのフォームを送信するときにも同じことを行います。ページ全体を再読み込みするのではなく、<body>のコンテンツを差し替えます(これはredirectの場合にも行われます)。Turbo Driveに含まれるJavaScriptパーツとRailsパーツが連携して、こうした動作がシームレスに行われます(もちろんRailsの規約に正しく従っていることが前提です)。

これ以上何もしなくても、技術的には既にHotwireを使っていることになります。Turbo DriveはRails開発者からほとんど見えないようになっています。インタラクティブな機能がこれで十分なら、この先を深掘りする必要はありません。

3: Turbo Framesを追加する

Turbo DriveはTurbolinksと起源が同じですが、Turbo FramesはHotwireの新しいコンセプトのひとつです。私は以前Turbo Framesを「思い通りに動かせるiFrames」と表現したことがあります。HotwireにおけるTurbo Framesは、ページの部分更新に使われます。

1個のFrameは、Turbo Driveと同じように動作します。ナビゲーション操作やフォーム送信はインターセプトされてAjaxを経由し、レスポンスはページ上で差し替えられます。しかしTurbo Framesでは<body>タグ全体を差し替えるのではなく、一致するTurbo Frameコンテンツだけを差し替える点が異なります。

つまり、idcomment_123であるTurbo Frameの中にリンクがあるとすると、Hotwireは、スワップするレスポンス内に<turbo-frame id='comment_123'>タグと一致する要素があるかどうかを探索します。

Turbo Framesの典型的なユースケースは、インライン編集です。ページ上にコメントのリストがあり、各コメントがフレームで囲まれているとしましょう。編集ボタンをクリックするとcomments#editレスポンスがレンダリングされますが、別のページ全体を読み込む代わりに、そのフレームを編集フォームに差し替えます。

4: Turbo Streamsを追加する

Turbo Driveが置き換えるのは<body>タグ全体で、Turbo Framesが置き換えるのはフレームです。Turbo Streamsは、これらよりもさらに細かく操作するときに利用します。

Turbo Streamsはページ上のHTMLを扱う標準的な操作のセットを提供し、コンテンツを追加・削除・置換できます。

先ほどのコメントの例で言うと、コメントを削除したいときは、Turbo Streamsでリクエストに応答することでコメントをページから削除できます。コメントを作成したい場合は、ページ上のリストの末尾にコメントを追加できます。

Turbo Streamsは、Railsの古典的なテクニックであるSJR(Server-generated JavaScript Response)のHotwire版です。従来はページ内で実行されるJavaScriptのスニペットをブラウザに返す方法でしたが、これはCSP(Content Security Policy)の問題を引き起こし、やや寛容すぎました。

Turbo Streamsは、従来ならSJRで行っていたような処理のほとんどを扱えるCRUD風の抽象化を提供します。

5: Turbo StreamsにAction Cableを組み合わせる

Turbo Streamsのレスポンスは、Action Cableで帯域外にブロードキャストすることも可能です。間違えやすいのですが、Turbo Streamsは必ずしもAction CableやWebSocketで送信する必要はなく、通常のHTTPリクエスト/レスポンスサイクルの中でTurbo Streamsを利用できます。

しかし、たとえば「自分以外のユーザーがコメントを追加したら、ページを更新しなくてもビューにそのコメントが追加される」リアルタイム更新機能が欲しくなるときがあります。

6: Stimulusを追加する

Hotwireは、アプリケーションで可能な限りサーバーサイドでレンダリングするRubyコードを書けるようにしてくれます。しかし、クライアント側にちょっぴりJavaScript機能を追加したくなることもあります。

StimulusはHotwireで推奨されるJavaScriptフレームワークで、HTMLマークアップに機能をアタッチしてブラウザのシンプルなイベントに応答できるようになります。

Stimulusで特筆すべき点は、クライアント側でのレンダリングサポートを一切提供しないことです。StimulusにはテンプレートもJSXもありません。もし開発者がStimulusコントローラで大量のHTMLを生成していることに気づいたら、頭を冷やして作業内容を再点検する必要があります。

Stimulusコントローラは多くの場合汎用性が高く、コードが50行を越えることもめったにありません。

たとえばコメントの折りたたみ機能をサポートしたい場合は、コメントを表示・展開するシンプルなStimulusコントローラを書けばよいのです(Hotwireの熱狂的なファンなら、こういうときに<detail>などのネイティブHTML要素に手を出したくなるかもしれませんが、これ以上は脱線するので...)。

7: カスタムコンポーネント・React・クライアントサイドJavaScriptを追加する

高度なインタラクティブコンポーネントについては、Reactのようなツールを使う方が適切な場合があります。Stimulusでは手に余るような高機能なクライアントサイド構築には、ReactやVueやWebComponentsのようなツールが最も適していることもあります。

そのときに備えて、ページには小規模な独立コンポーネントを追加するよう心がけておくとよいでしょう。Railsにおける例としては、リッチテキストエディタTrixがあります。これは標準の<trix-editor> WebComponentです。

先のコメントの例で言うと、絵文字ピッカーやドラッグアンドドロップ方式のファイルアップローダーを追加したくなることもあるでしょう。これらのリッチなJavaScriptライブラリをStimulusコントローラにラップして、Turboのライフサイクルイベントに乗せて管理しやすくしてもよいでしょう。

Hotwire愛好家たちは、「JavaScriptコンポーネントは決して使ってはならぬ」などと堅苦しい原理を押し付けたりしません。最初のうちは別のシンプルなツールにしておいて、必要が生じたら高度なツールを使えばよいと考えます。

8: Turbo Nativeを追加する

Webアプリケーションで機能の構築を終えたら、Turbo NativeのiOSアダプタやAndroidアダプタを使えば、WebViewの内部に同じアプリのコンテンツを表示するモバイルアプリケーションを作成できます。

これは「Webアプリのモバイル最適化」ではなく、Railsアプリのページをレンダリングする本物のSwiftアプリやKotlinアプリです。

Turbo Nativeには、ネイティブのアクションバーやボタンを作成するのに便利な機能がいくつかあります。

Turbo Nativeの中心となるアイデアは、Railsのビューをできるだけ再利用し、プラットフォームネイティブな言語を用いていくつかの画面を取り除きながら書き込む仕組みを提供するというものです。ここにもプログレッシブエンハンスメントという考え方が取り入れられています。

たとえば、Turbo Nativeの代表的なアプリであるHeyのメールクライアントには完全にネイティブの受信トレイ画面がありますが、アカウント設定画面などそれ以外の画面はRailsビューのラッパーになっています。

Turbo Nativeの機能はまだベータリリースとみなすべきでしょう。Hotwireアプリケーションを書いている開発者の多くはネイティブ機能を使っていませんが、それでも、このスタイルによる開発の地固めを進めている開発者たちがいることは知っておくとよいでしょう。

9: Stradaを追加する

Hotwireの最後のピースは、2022年夏の時点でまだリリースされていないStradaライブラリです。StradaはTurbo Native機能の拡張であり、アプリケーション内で「SwiftやKotlinのパーツ」と「HTML/Railsのパーツ」同士が通信する必要が生じたときに両者のギャップを埋めてくれます。

StradaライブラリはTurbo Nativeを使いやすく拡張するものであり、何か根本的に新しいものを追加するわけではありません。

Stradaについては、実際にリリースされるまで気にしなくてもよいでしょう。

まとめ

Hotwireはプログレッシブエンハンスメントという考え方に基づいています。欲しい結果を得るには、使うツールをできるだけ少なくしておく必要があります。 Hotwireは下のレイヤになるほど機能が強力になりますが、トレードオフとして複雑さが増してしまいます。

「使うツールを少なめに抑える」アプローチのよい点は、機能の構築やフィードバックに基づいたテストの繰り返しを短期間で行えることです。必要が生じたら、より高度なリアルタイのインタラクティブ機能を上に乗せていけばよいのです。

プログレッシブエンハンスメントはHTMLと実によく調和します。Hotwireは、ブラウザのネイティブ要素でできることを追求するよう促進し、レイヤにツールを追加するのはプラットフォームを補完またはアップグレードするときだけにしておくことを推奨しています。

Hotwireというブランドは、さまざまな異なるツールを覆う傘のようなものです。本記事で解説したHotwireの概要によって、皆さんが個別のツールを上手に選択できるようになり、見事にまとまった技術スタックを構築する方法を学ぶうえで助けになることを願っています。


本記事が役に立つと思った方は、ぜひフォームでニュースレターの登録をお願いします。余分な文章を削ぎ落とした読みやすく価値ある情報だけをニュースレターで配信いたします。

関連記事

Railsの技: StimulusJSコントローラからRailsの環境変数にアクセスする(翻訳)

Better Stimulusガイド:アーキテクチャ1: アプリケーションコントローラ(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。