見出し画像

食べログフロントエンドのリプレース戦略

この記事は食べログ Advent Calendar 2020の10日目の記事です。

お久しぶりです。食べログフロントエンドチームの辻です。
このブログでも頻繁に紹介しておりますが、食べログフロントエンドはjQueryベースからReact/TypeScriptへのリプレースを進めています。
ページごとではなく、コンポーネントごとにリプレースを進めていきます。

店舗画面の右下に出ている「○人が見ています!」というバナーはリプレース済みですが、この赤枠の中のみ、Reactです。
店舗ページ自体はSPAではありませんし、他のコンポーネントはjQueryで動いています。

繁盛バナー

この記事では、コンポーネントごと、つまり部分導入という判断に至った経緯と、どのような方法で部分導入を実現しているのかを紹介します。

ページごと VS コンポーネントごと

ページごとにリリースし、nginxなどでリプレース前後でリソースを出し分けて、最終的には全てリプレース後の方に振り分けるような方法がセオリー通りかもしれません。
ですが、食べログではコンポーネントごとのリリースを選択しました。

nginxなどでリソースを出し分けてページごとにリリースすれば、リプレース前後でシステムが明確に分離されますし、ページ内に複数のランタイムが共存することによる考慮・実装も不要で、シンプルで一見より良い選択に見えるかもしれません。
ですが、食べログにおいては下記3つの事情から、コンポーネントごとリリースのほうが最終的にかかるコストは少ないと判断しました。

① 1ページに機能のりすぎ
食べログは長期に渡ってUIが作りこまれ、1ページに多種多様なコンポーネントがのっています。
例えば店舗検索の画面だと、これらの機能があります。

・検索文字列のサジェストが出るフォーム
・エリア絞り込めるプルダウン
・詳細な検索条件を設定できるモーダル
・予約可能日を表示するカレンダー
・予約日を選択出来るカレンダー
・レビュー投稿できるモーダル
・お店を保存できるモーダル
・ページの先頭までスクロールできるボタン
・広告
・その他細かいのたくさん!

優先的にリプレースしたいページは特に機能が多いです。
これら全て書き換えてからページ単位でのリリースとなると、1ページリリースするのに数ヶ月、もしかしたら1年以上かかるかもしれません。
並行して機能開発も進めているため、開発期間中は常に最新機能とのマージが必要になります。途中で完全に作り直しとなるコンポーネントが出てくることも予想されます。

② 仕様が複雑
繰り返しになりますが、食べログは長期に渡ってUIが作りこまれています。どんなブラウザ、どんな条件でも問題なく使って頂くため、ユーザー状態や状況に応じて細かな制御がなされています。
もちろんこれはUXを上げるためには良いことなのですが、一見シンプルなコンポーネントであってもユーザーの状態や画面サイズによって表示を分けていたり、再現の難しい条件で制御されていたり、設計・実装・テストに時間がかかるコンポーネントも多いです。
今後も必要な機能なのか見直しも並行して行っていますが、基本的には必要だから実装されているものなので、オミットできるものは少ないです。

③ 共通コンポーネント多すぎ
なんとか1ページ丸ごとリリースできたとして、他のページと共通でつかっている機能(ヘッダー、フッター、モーダルなど)はReact版とjQuery版の二重管理が始まります。さらにもう1ページリプレースすると、また新たな二重管理が発生します。最終的には収束するとはいえ、ものすごく大きな規模・長い期間の二重管理が発生することが予測できます。過渡期とはいえ、コスト増加やメンテナンス性の低下が大きな課題となります。

まとめると、食べログにおいては、現状の複雑さ、リプレース開発スピード、機能開発スピードのバランスから、コンポーネントごとのほうが二重管理や最新機能追従の個数が抑えられ、コスパが良いという判断です。
食べログの仕様にめっちゃ詳しいフロントエンドエンジニアが100人居れば話は別ですが、なかなか難しいですね。(積極採用中です!)

ちなみに、コンポーネントごとのリリースを選択したからといって、SPA化を諦めているわけではありません。必要なコンポーネントが揃ったページでかつ、SPA化が必要と判断したページはReactでルーティングから制御するようにしていく予定です。


部分導入におけるビルド機構

Ruby on RailsでレンダリングされたHTMLにjQueryでインタラクションを実装している既存ページに、どのようにしてReact/TypeScriptを部分導入しているのか、なぜその手段を選択したかについて紹介します。

Reactと他のフレームワークを共存させる方法はいくつかありますが、チームで候補となったのは大きく3つです。

・手段1:jQueryベースの既存エントリポイントからReactのコンポーネントとjQueryのコンポーネント両方をimportする
・手段2:Reactベースの新規エントリポイントからReactのコンポーネントとjQueryのコンポーネント両方をimportする
・手段3:jQueryベースの既存エントリポイントとReactベースの新規エントリポイントを共存させ、それぞれ既存エントリポイントからjQueryのコンポーネントをimport、新規エントリポイントからReactのコンポーネントをimportする

これらの中から、3つの観点で絞り込んでいきました。

・年単位の運用に耐えれるか
・リプレース開発にコストをかけすぎないか
・jQueryとReactのコンポーネントが疎結合になるか


手段1:jQueryベースの既存エントリポイントからReactとjQueryのモジュールをimportする

手段1

自前のプライベートパッケージを使用するような構成と捉えて頂ければわかりやすいと思います。
既存のjQueryのビルド機構にReactの設定を加えても実現は可能ですが、jQueryとReactで疎結合としたいので、2段階ビルドを想定しています。つまり、上図のjquery-entrypointは、バンドルされたReactアプリケーションのパッケージを読み込み、更にjquery-entrypointもwebpackでトランスパイルします。

年単位の運用に耐えれるか:✗
リプレース時に既存コードビルドシステム&コードへの影響が発生しないため、jQueryのページ/モジュールは今までと同じ方法で開発が進められる。
しかし、Reactの機能をjQueryに組み込んだ状態でおかしな表示や挙動を見つけた時に、デバッグがしづらい。1段階ビルドした後、つまり.jsファイルの状態で原因追求するのは困難なので致命的。

jQueryとReactのコンポーネントが疎結合になるか:○
jQueryのビルドとReactのビルドは分離されており、jQuery側のwebpackに変更があってもReact側には影響しない。逆も同じ。

リプレース開発にコストをかけすぎないか:○
ビルド機構2つになるものの、それぞれのビルドでは特殊なことはしないので大きな課題なく実装できる。
リプレース時に既存コードビルドシステム&コードへの影響が発生しないため、対象機能のテストのみの実施にとどめられる。


手段2:Reactベースの新規エントリポイントからReactとjQueryのモジュールをimportする

手段2

React公式ドキュメントの「DOM 操作プラグインとのインテグレーション」で紹介されている実装に近くなる想定です。公式ドキュメントでも紹介されていることから、こちらがリプレースの一般的な手段なのかもしれません。
https://ja.reactjs.org/docs/integrating-with-other-libraries.html

年単位の運用に耐えれるか:△
対象ページのwebpack依存は、webpack公式ページに記載されている標準的構成になる。
しかし、jQuery/React共存ページではリプレース対象でないjQueryモジュールまで運用方法が変わってしまう。
ReactベースとjQueryベース両方のページで使われている既存モジュールは、React/jQuery両方エントリポイントで動くことのテストが必要になる。

jQueryとReactのコンポーネントが疎結合になるか:✗
新規のReactのビルドにもjQueryとその関連モジュールの設定が必要になる

リプレース開発にコストをかけすぎないか:✗
リプレース対象でないjQueryコンポーネントもReactベースの新規エントリポイントからimportすることになり、ほぼ触ってないjQueryコンポーネントもテスト対象になってしまう。1回のリリースに対して影響範囲が広くなってしまうのは避けたい。


手段3:jQueryベースの既存エントリポイントとReactベースの新規エントリポイント(React)を共存させ、それぞれ既存エントリポイントからjQueryのモジュールをimport、新規エントリポイントからReactのモジュールをimportする

手段3.png.003

1ページにentrypointが2つある状態になります。

年単位の運用に耐えれるか:△
リプレース時に既存コードビルドシステム&コードへの影響が発生しないため、jQueryのページ/コンポーネントは今までと同じ方法で開発が進められる。
ページ内にランタイムが複数存在することとなり、jQuery/React双方が影響しあわないよう運用でカバーとなる部分がある。

jQueryとReactのコンポーネントが疎結合になるか:○
jQueryのビルドとReactのビルドは分離されており、jQuery側のwebpackに変更があってもReact側には影響しない。逆も同じ。

リプレース開発にコストをかけすぎないか:○
ビルド機構が2つになるものの、それぞれのビルドでは特殊なことはしないので大きな課題なく実装できる。
リプレース時に既存コードビルドシステム&コードへの影響が発生しないため、対象機能のテストのみの実施にとどめられる。


手段2がセオリー通りで良いのではとの意見もありましたが、食べログにおいてはデメリットが多く選択肢から外しました。
手段1のデバッグのしにくさは許容できず、手段3とすることを決めました。
手段3は1つのページランタイムが複数存在することになるが、それぞれ独立して動作するように運用が可能で、大きな問題にはならないと判断しました。


まとめ

ページごとのリリースは、1リリースにかかる開発期間が長くなり、最新機能の取り込みや2重管理の発生によるコストが大きくなるため、コンポーネントごとにリリースしていきます。

コンポーネントごとのリリースをするため、1ページ内でjQueryとReactの共存が必要です。
既存のjQueryベースの開発に影響を与えず、jQueryとReactのビルドシステムが疎結合となり、テスト工数も抑えられることから、
jQueryベースの既存エントリポイントとReactベースの新規エントリポイント(React)を共存させ、それぞれ既存エントリポイントからjQueryのモジュールをimport、新規エントリポイントからReactのモジュールをimportする」という方法を選択しました。

さいごに

食べログFEチームでは、一緒に楽しくリプレースに取り組んでくれる仲間を大募集中です!

・難しい課題にチーム一丸となって取り組みたい
・柔軟に働ける環境で自分のスキルを活かしたい
・React/TypeScriptでバリバリ開発したい
・レガシーなシステムのリファクタリングがしたい
・アーキテクチャについて探求したい
・食べログというプロダクトに貢献したい

どれかに当てはまった方は以下のリンクを是非御覧ください!

明日は食べログ Advent Calendar 2020の11日目の記事が公開されるのでご期待ください!

この記事が気に入ったらサポートをしてみませんか?