Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
HotwireからDHHが考えるこれからのRailsとJSの付き合い方を知る
Search
Shinichi Maeshima
February 17, 2021
Technology
14
13k
HotwireからDHHが考えるこれからのRailsとJSの付き合い方を知る
「iCARE Dev Meetup #18」で発表した資料です
https://icare.connpass.com/event/201662/
Shinichi Maeshima
February 17, 2021
Tweet
Share
More Decks by Shinichi Maeshima
See All by Shinichi Maeshima
Sidekiq vs Solid Queue
willnet
13
9.6k
どうしてこうなった?から理解するActive Recordの関連の裏側
willnet
5
1.1k
Exceptional Rails
willnet
6
6.9k
Breaking the Flaky Test Cycle
willnet
2
2k
mrskで広がるインフラの選択肢
willnet
1
1k
アプリケーションを長期にわたって無理なく運用するためのたったひとつの方法
willnet
2
2.1k
Rails6.1で新しく入る機能について
willnet
12
15k
Concerns about Concerns
willnet
11
34k
Clean Test Code Revised
willnet
34
18k
Other Decks in Technology
See All in Technology
C++26 エラー性動作
faithandbrave
2
640
GitHub Copilot のテクニック集/GitHub Copilot Techniques
rayuron
23
11k
OpenShift Virtualizationのネットワーク構成を真剣に考えてみた/OpenShift Virtualization's Network Configuration
tnk4on
0
130
LINEスキマニにおけるフロントエンド開発
lycorptech_jp
PRO
0
330
5分でわかるDuckDB
chanyou0311
10
3.2k
watsonx.ai Dojo #5 ファインチューニングとInstructLAB
oniak3ibm
PRO
0
160
Oracle Cloudの生成AIサービスって実際どこまで使えるの? エンジニア目線で試してみた
minorun365
PRO
4
270
kargoの魅力について伝える
magisystem0408
0
200
私なりのAIのご紹介 [2024年版]
qt_luigi
1
120
Oracle Cloud Infrastructure:2024年12月度サービス・アップデート
oracle4engineer
PRO
0
160
継続的にアウトカムを生み出し ビジネスにつなげる、 戦略と運営に対するタイミーのQUEST(探求)
zigorou
0
500
KubeCon NA 2024 Recap / Running WebAssembly (Wasm) Workloads Side-by-Side with Container Workloads
z63d
1
240
Featured
See All Featured
For a Future-Friendly Web
brad_frost
175
9.4k
What’s in a name? Adding method to the madness
productmarketing
PRO
22
3.2k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
48k
Building Better People: How to give real-time feedback that sticks.
wjessup
365
19k
YesSQL, Process and Tooling at Scale
rocio
169
14k
Site-Speed That Sticks
csswizardry
2
190
The Cost Of JavaScript in 2023
addyosmani
45
7k
Raft: Consensus for Rubyists
vanstee
137
6.7k
Optimising Largest Contentful Paint
csswizardry
33
3k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
Optimizing for Happiness
mojombo
376
70k
Transcript
Hotwire から DHH が考えるこれからの Rails と JS との付き合い⽅を知る @willnet 1
いろんな会社で技術顧問をしつつ、空 いた時間で savanna.io などを開発して います 2
savanna.io は Hotwire を利⽤していま す 3
今⽇は Hotwire の話をします 4
Hotwire とは? Basecamp 社製js フレームワーク Rails の作者であるDHH がCTO をしている会社 hey.com(Basecamp
社製メールアプリケーション) を作るのに使われ ている 複数のフレームワークで構成されている 5
Hotwire とは? ( 意訳) モダンなweb アプリケーションを、JSON とたくさんの JavaScript を使うことなしに、HTML を送ることで実現するアプロー
チ Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire. “ “ 6
今どきの js フレームワークといえば JSON を使うのがふつう? サーバサイドはJSON を返す フロントエンドはJSON を受け取る そこからDOM
を構築してHTML をレンダリングする js ですべてを制御するのでUI を細かく作り込める 7
でもそれって⼤変ではないですか? ロジックがサーバサイドとクライアントサイド両⽅に必要になる クライアントは複数存在するのでそれぞれで実装を頑張らないと… ブラウザ iOS Android それぞれに専任のエンジニアが必要 8
Hotwire を使うとどうなるか サーバサイドにロジックを寄せることができる クライアントサイドのコードは最⼩限に抑えることができる それでいて、それなりにSPA ができる 結果として ひとつのチームですべてを担当できる( かも) 好きな⾔語を使える
Hotwire はRails に限らず使えるようになっている ※ブリッジになるようなものは必要(ex: turbo-rails gem) 9
とはいえ vue やreact と⽐べて細かいことはできない native アプリケーションも基本webview になるぞ hey.com (Basecamp 社製のメールサービス)
くらいのものであれば Hotwire で実現できるので、そのレベルで問題ないサービスならOK 「そのレベルで問題ないサービス」は結構な割合でありそう 10
Hotwire のアーキテクチャ Turbo Turbo Drive Turbo Frames Turbo Streams Turbo
Native Stimulus Strada( 現時点で未発表なので今回は話しません) iOS やAndroid のネイティブで必要な機能(ex: カメラ) とHTML と をつなげるためのライブラリらしい 11
前提 Hotwire はRails に限らず使えますが、Rails を前提にしたツール (turbo-rails) があるのでRails で使うのが便利です 今回はturbo-rails を利⽤している前提で話します
12
注意点 IE などの古いブラウザで動かすのは⼤変そう fetch API custom elements Intersection Observer API
など、モダンブラウザでしか動かないAPI を利⽤している polyfill を使えば動きそうだけど⼤変そう IOS も12 以上( 近いうちに13 以上) が必要 13
Turbo から話していきます 14
Turbo Drive Turbolinks を改善させたもの 15
Turbolinks とは Rails4.0 からRails 標準のライブラリとなっている すべてのリンクをajax で置き換える 受け取ったHTML のうち、body だけを現⾏のものと差し替える
するとjs やcss のダウンロード→ パースをスキップできるぶん⾼速化 できる 16
不遇の Turbolinks リリース当時( 約7 年前) はjQuery およびjQuery プラグイン全盛 Turbolinks はjQuery
プラグインと相性が悪いことがあった Turbolinks を使うためには⾊々わかっている必要があった 通常のページロードは最初の⼀回のみであること E2E テストで、次ページに遷移したかをちゃんと待つ必要がある form に関しては完全に対応していないこと ハマるひとが多く、デメリットが⼤きいと判断→ 基本消されるライ ブラリに 17
Turbolinks もきちんと理解して使えば 使いやすい いまはSPA なアプリケーションも増えたので、フロントエンドの知 識のある⼈も増えている( はず) 要点さえ押さえれば、少ない労⼒でSPA 化が可能なメリットは変わ っていない
18
( 余談 ) いま Turbolinks を使っているひと はどうやって Turbo に移⾏したらいいの エイヤで頑張ると移⾏できます
詳細はブログに書いたのでこちらを⾒てください Turbolinks からTurbo への移⾏ - おもしろweb サービス開発⽇記 19
Turbo Drive による改善 form に関してもデフォルトでturbo 仕様になった form_with ヘルパメソッドはこれまでTurbolinks を意識し、デフォ ルトで
data-remote="true" 属性がついていた Turbolinks を使っていない⼈からしたら混乱の元 Turbo がリリースされたことにより、Rails6.1 から data-remote="true" はデフォルトオフになった 20
Turbo Drive の form 送信時の挙動 form 送信時は302 などのレスポンスが返るとリダイレクト、4xx や 5xx
系が返るとレスポンスボディを差し替えて履歴はそのまま、と なった Turbolinks 時にここをちゃんとやろうとすると⼿動でSJR(Server generated javascript response) して対応する必要があった 21
SJR ってなに 例えばPost のcreate アクションでバリデーションエラーが起きたら、 次のようなposts/create.js.erb をレンダリングします。 (shared/_error.html.erb にエラーメッセージ⽤の部分テンプレートが ⼊っている想定)
document.querySelector('#error').innerHTML = "<%= j(render("shared/error", model: @post)) %>"; するとrails-ujs が↑ の内容をeval してくれるので <div id="error"></div> にエラーメッセージが表⽰されます。 22
SJR 、セキュリティどうなんですか さっき提⽰したコードのレベルなら⼤丈夫だけど、ユーザが⼊⼒し た⽂字列をカジュアルにSJR とすると⼤変危険ですね>< そもそもTurbo(links) でなにかするときに頻出する操作は限られて いる 特定のDOM にHTML
を挿⼊する(append, prepend) 特定のDOM を差し替える(replace, update) 特定のDOM を削除する これを提供するのがTurbo Streams 23
Turbo Streams Turbo のレスポンスとして、↓ のようなHTML ⽚を Content- Type=text/vnd.turbo-stream.html で返すとTurbo がいい感じに処理し
てくれる。 <turbo-stream action="append" target="messages"> <template> <div id="message_1"> id がmessages なDOM の内部に挿⼊(append) される </div> </template> </turbo-stream> 24
Turbo Streams で部分テンプレート活⽤ turbo-rails を使っていると次のように書ける turbo-stream タグやtemplate タグを⾃動で付与してレスポンスを返 す これで既存の部分テンプレートを活⽤できる!!
def create message = Message.create!(params.require(:message).permit(:content)) render turbo_stream: turbo_stream.append(:messages, # ここがturbo-rails 提供部分 partial: "messages/message", locals: { message: message }) end end 25
複数操作をする ビュー側で複数のturbo-stream タグをレンダリングして複数操作をす ることもできる <% # app/views/entries/entry.turbo_stream.erb %> <%= turbo_stream.remove
entry %> <%= turbo_stream.append "entries" do %> <%= render partial: "entries/entry", locals: { entry: entry } %> <% end %> 26
Turbo Streams で使える action action として使えるのは次の5 種類のみ append prepend replace
update remove レールを敷くことで判断を楽にできるのと、SJR でセキュリティに⽳ を開けるのを防ぐことができるのがメリット 27
Turbo Streams と websocket websocket(Action Cable) でも使える turbo-rails にはAction Cable
⽤のヘルパメソッドがあり、subscribe やbroadcast が簡単にできるようになっている 例えば新着メールが来たタイミングで⾃動的にメールが追加され る、というのが簡単にできる 詳細はturbo-rails ソースコードをみてください( 現時点でドキュメ ントにはほぼ記載がない) 28
ここまで⾒てわかる Turbo ができること 既存のHTML をそのまま使いつつユーザ体験を良くする(Turbo Drive) 同じテンプレートをいろんなところで使い回す(Turbo Stream) ex: ⼀通のメッセージを表す部分テンプレートをTurbo
Streams で も使う 29
もっと使いまわしたい いろんな環境で共通のテンプレートが使えるとうれしい スマホなどの⼩さい画⾯ PC の⼤きい画⾯ そこで Turbo Frames ですよ 30
Turbo Frames turbo-frame タグ中に書かれた要素はframe として区切られる frame 内のリンクはTurbo Frames ⽤のリンクとなる( 詳しくは次⾴)
<!-- messages/index.html.erb --> <body> <div id="navigation"> メッセージ⼀覧</div> <turbo-frame id="message_1"> <h1>Hotwire</h1> <p> いいですね</p> // ↓ をクリックすると… ? <a href="/messages/1/edit"> メッセージを編集する</a> </turbo-frame> </body> 31
Turbo Frames <!-- /messages/1/edit --> <body> <h1> メッセージの編集</h1> <!-- リンク元が
<turbo-frame id="message_1"> に含まれていれば ↓ の中⾝だけが差し替わる! それ以外のリンク元であれば ↑ のh1 タグ含めてbody 全体が差し替わる --> <turbo-frame id="message_1"> <form action="/messages/1"> <input name="message[name]" type="text" value=" メッセージタイトル"> <textarea name="message[name]"> メッセージの内容</textarea> <input type="submit"> </form> </turbo-frame> </body> 32
つまり Turbo Frames を使うとレスポンスのHTML の⼀部を使い、もとの HTML の⼀部を部分的に差し替えることができる 33
turbo-frame タグの中だけど普通のリン クにしたいときもありますよね <!-- messages/index.html.erb --> <body> <div id="navigation"> メッセージ⼀覧</div>
<!-- target="_top" とすると普通のTurbo のリンクとなる(body が全部書き換わる) --> <turbo-frame id="message_1" target="_top"> <h1>Hotwire</h1> <p> いいですね</p> <a href="/messages/1/edit"> メッセージを編集する</a> </turbo-frame> </body> 34
Turbo Frames のメリット target="_top" つけはずしすることで、⼀つのテンプレートを使いま わしできる ⼤きいディスプレイのときはインライン編集 スマホなどの⼩さいディスプレイでは専⽤のページに遷移させる 35
Turbo Frames のもう⼀つの活⽤⽅法 src 属性をつけると、値となるURL を⾮同期でfetch する ファーストビューの時間を短縮できる Turbo Frames
⽅式でレスポンスを差し替える <body> <h1> ともだち⼀覧</h1> <div id="friends"> ... </div> <h2> ともだちかも?</h2> <turbo-frame id="maybe_friends" src="/maybe_friends"> </turbo-frame> </body> 36
Turbo Frames でさらに遅延表⽰ loading="lazy" をつけると、その要素が画⾯に表⽰されるまでロー ドを遅延する <body> <h1> ともだち⼀覧</h1> <div
id="friends"> ... </div> <h2> ともだちかも?</h2> <turbo-frame id="maybe_friends" src="/maybe_friends" loading="lazy"> </turbo-frame> </body> 37
Turbo Native iOS やAndroid でTurbo を使うためのライブラリ webview でアプリケーションを利⽤する 主にTurbo Drive
での遷移による履歴をネイティブで管理するた めのもの 38
Turbo については⼀通り話した 39
Turbo を使うとサーバサイドにロジック が集中する つまりjs の量が減る が、ゼロになるわけではない Turbo を使うと、全ページで使うjs をひとまとめにして提供するよ うになる
js の整理がたいへん 「A というページでだけ発⽕させたいjs 」を管理するのがたいへん 40
Stimulus HTML とjs をいい感じに紐付け合い整理するライブラリ 学習コストが低い 41
42
特定のページだけで発⽕させたい js が 書きやすい // hello_controler.js import { Controller }
from "stimulus" export default class extends Controller { connect() { // ここにdata-controller="hello" が // 表⽰されたときに実⾏したい処理を書く } } 43
Stimulus のメリット controller ごとにファイルが⾃然と分かれて整理される HTML タグの属性名を⾒るとjs のどの部分に紐付いているかすぐわ かる 特定のページだけで発⽕させたいjs が書ける
44
まとめ 未公開のStrada を除き⼀通りHotwire について説明した Hotwire は、" 最⼩限の労⼒でユーザが求めているサービスを提供す ること" に特化しているライブラリ つまりそれはRails
と同じ⽅針をフロントエンドにも持ち込んだ、 と⾔える 少⼈数で開発するスタートアップ〜中規模なサービスに特に向い ているはず 便利なので使ってみてください! 45