Hotwire
- Turboを中心としたウェブアプリケーションのアーキテクチャの要素技術やコンセプトをPRするための名称
- Hotwireというライブラリがあるわけではない
- 役割としてはMicro FrontendsとかReactのlearn once, write anywhereなどに似ている
- アプリケーション実装言語非依存だけど現状Railsアプリケーションしか実用できる基盤がない
Hotwireの思想
- アプリケーション開発者の生産性を上げることを目的にしていること
- サーバーサイド言語でフロントエンドを実装したいアレではなかった
- プログレッシブ(段階的に利用可能)であること
- 必要な技術だけを使い無駄なことをしないことで効率化する
- Hotwireが列挙する技術は1つづつ有効にできる
- クライアントサイドでViewを差分更新する現在の主流のシングルページアプリケーション開発のオルタナティブなこと
- 既存の方法は効率が悪いという作者たちの考えが反映されている
- とくにビルドツール依存の開発が目の敵にされている
- 逆にSPAが必要ない人やSPAのクライアントサイド開発者には刺さらなそう
Hotwireの仕組み
- TurboというJavaScriptライブラリがある
- これがウェブブラウザ上で動き続けることでアプリケーションに動的な機能を提供する
- 通常すべてのドキュメントリクエストを乗っ取り、XHRで取得したコンテンツ(HTML)部分だけをブラウザではなくTurboが置き換える
- turbolinksと呼ばれていたRailsの機能のことで、JavaScriptイベントの挙動に副作用が生じてよく無効化されていた。これがTurbo Driveという概念になった
Turbo Frame
- Turbo Driveが書き換えるコンテンツを
<turbo-frame>
タグ以下で制御する仕組み <turbo-frame>
内にある<a>
タグや<form>
タグでイベントが発生した時にXHRのレスポンスの内容を現在のViewに差分更新する- コンテンツ自体はサーバーサイドで出力された結果になる
<turbo-frame>
外のコンテンツを書き換えるオプションが用意されており画面の共通部分の更新などはここを経由する
Newボタンをクリックする。このボタンのマークアップがaだったりformだったりする。
Turboによって https://app.hey.com/folders/new
へリクエストが送信されると <turbo-frame>
でダイアログの中身が返される
iframeのようにこの中身を直接開ける
Turbo Stream
- ユーザー側のイベントではなくサーバー側から更新をPushしていて更新する仕組み
- それぞれのサイバーサイドフレームワークが各自の機能を使ってブラウザからWebSocketやSSEでメッセージを待ち受け、決った仕様のHTMLをDOMのAPIに渡すとあとはTurboが画面更新をする
- それを turbo-stream のタグに定義してアプリケーション開発者がテンプレートに埋め込めるようにする
- 制約上、サイバーサイドフレームワークへの依存が強い。
turbo-rails
ではAction Cableを使って実現されている
Stimulus
- Turboがサーバーサイドで生成したHTMLをいかに画面に適応するかという役割を「のみ」を担っている
- 更新されたHTMLに対してボタンが押された時にCSSクラス名を変えたいのだとか、クライアントサイドだけで完結すればいい処理を書く場所が必要でそれがStimulus
- 必須ではないがTurboはアプリケーションで実行されるJavaScriptのロジック面倒を見てくれないので何かしろクライアントサイドのコードを管理する仕組みが必要
- 特定のHTMLのブロックに対して割り当てるふるまいをJavaScriptのclassとして記述していく
- その仕組みがHTMLをブロック単位で配信するTurboと相性が良い
Turbo Native
- 動作環境がウェブブラウザではなくiOS/AndroidアプリのWebView上になる
- https://hey.com/apps/ で利用されてる
- WebView上のアプリケーションが効率よく動作するためにクライアントサイドSDKが配布されていて、アプリケーション開発者はこの基盤の上で利用する
Strada
- Turbo NativeからiOS/AndroidアプリのネイティブAPIをブリッジして呼び出して開発できるようにする仕組み。未リリース
- ウェブアプリケーションをブラウザとiOS/Androidアプリに共通でホストするという点でアプローチがIonicに近い
- ただしStradaはリモートのウェブサイト経由でネイティブAPIを実行しようとしているところがIonic(Cordva/Capacitor)と設計が異なる
- 通常この方法はAppStoreのガイドラインに接触し問題になるソフトウェアが過去にいくつかあった
2.5.2 Appはバンドル内で完結している必要があります。他のAppを含め、指定されたコンテナエリア外に対するデータの読み書き、またはAppの特徴や機能を導入したり変更したりするコードをエリア外からダウンロード、インストール、実行することは許可されません。 https://developer.apple.com/jp/app-store/review/guidelines/#performance
- ただしCodePushのように「既にバンドルされているネイティブ実行コードではないアセットファイルを外部から更新する」というスキームで回避できている例もある(しかし結局レビュワーガチャによってリジェクトされている)
https://github.com/microsoft/react-native-code-push#store-guideline-compliance
勘違いしていたこと
- Phoenix.LiveView インスパイヤなのでWebSocketやロングポーリング必須だろう
- Turbo Streamを動かす場合に必要なだけだった。WebSocketサーバーがなくても他の機能は利用可能
ReactやVueを使った開発と比較して期待できそうな点
- サーバーサイドレンダリングがデフォルト
- 実装するレイヤーが減ってシンプルになる
- APIエンドポイントとクライアントがいなくなり、DBからデータを取得してViewまで落とし込むまでの距離が短かくなった
- React Server Components の解説でもこの利点が書いてあった
不安な点
- ReactやVueを前提とした便利なUIライブラリの存在
- JavaScriptビルドパイプラインは回避することができるけど謎のJS/CSSをインストールするだけのgemを管理し続ける問題は残ったまま……(Railsの場合)
- Turbo自体の学習コスト。テンプレートを細かく分けて記述するようになって、どこで何が起きているのか把握しずらかった
- ReactやRxやMVVMにも複雑さを感じていたが慣れたので、いずれ気にならなくなるかも……
- React以降にUIをデータと関数でプログラミングできるようになって前進した手応えがあったが、Stimulusで生DOM API+手続きコードのBackbone.js時代に戻されてしまったかのような不安
- 実際 hey.com コンソールを開くと大量の contoller.js クラスが……
- TypeScriptは諦めてるような雰囲気(ブラウザで動かないものだから)
- 一旦型に保護されたプログラミングに慣らされると、取り上げられた時に不安になる
実際のプロジェクトでの利用
- 今の主流のシングルページアプリケーションのスタックについてはモバイルアプリのAPI開発との共通化が影響していると思っているのでHotwireを全面的に使うか? という判断はWebView製のモバイルアプリを許容できるか。というところにかかっていると思う
- WebView製のモバイルアプリがどんなものかというのは実際のHeyのiOS/Androidアプリで試すことができる
- WebView製のモバイルアプリが不要、もしくは許容できるとしてもプロジェクトに充分なフロントエンドエンジニアやモバイルアプリエンジニアが居たとしたら技術スタックごとに分担した方が効率がいいという意見の人は多いと思う
- なので「フロントエンドエンジニア+React Native+サーバーレスアーキテクチャで最小構成ではじめる」例のように、「RailsエンジニアがDBとAPIとSPAを同時に作らなくていい方法」として局所的な活用方法に最初はなるのではないか
- 最小のRailsアプリケーションを開発して、少しづつシングルページアプリケーション化を取り入れていくという時の用途にも使える
- 今までの考え方からすると、ある時えいやでSPA化するということをみんなしていた