Riot.js も Next.js 上で動かしちゃって React に移行している話
ストレッチ専門店 Dr.stretch を運営する 株式会社nobitel にて業務委託をしている shinnoki です。
ストレッチとウェブ開発は一見すると遠い分野ですが、 nobitel では「健康 × スポーツ × IT」を掲げ、全国約200店舗(2023年7月現在)の Dr.stretch で使われるお客様・店舗向け業務アプリ「Ficks」を社内で開発しています。
私は4月から業務委託として関わらせていただき、開発生産性および開発者体験の向上のためのいくつかの改善を行ってきました。
その中でも大きな取り組みとして Ficks フロントエンドの Riot.js から React と TypeScript への移行というものがあり、今回はその背景や経過について紹介します。
移行の背景
移行前は Riot.js v3 を使用しており、コンパイル済みの Riot.js のコードが Laravel の blade 内で呼び出されるような構成になっておりました。
API と フロントエンドはそれぞれ別の Laravel で動いており元々分離されていたため、その点フロントエンドの移行は考えやすい構成をしておりました。
Riot.js は公式ドキュメント以外の日本語の情報が少ないため新規実装が難しく、分かっている人が書いたコードをコピペするしかありませんでした。
さらに Riot.js は v3 以前と v4 以降で大きく書き方が変わるためこのまま使い続けるのであればどちらにしろ大規模な改修は避けられないという状況でした。
(偶然ですがこの記事を公開する2023年7月31日に v9 がリリースされたようです)
また、社内の他のプロジェクトで既に React と TypeScript を採用しており、移行先としては自然な選択肢でした。
なぜやるのか
React と TypeScript への移行は私が関わる前から CTO とプロジェクトマネージャーとで合意が取れておりロードマップ上にも載っていたところからのスタートでしたが、実際に走り始める前に改めてなぜ移行が必要なのかを言語化し認識を揃えました。
長期的な開発生産性のアップ
これは TypeScript での開発を経験した方なら実感があると思いますが、JavaScript で開発するときに比べて全体の記述量が増えたとしてもエディタ上でエラーが確認できたり強力な入力補完が効くことにより、実装スピードが向上する上に堅牢なコードを書くことができます。
単純に開発チームの実装スピードが上がることで、全体で見るともっと大きなくくりでの開発生産性に影響してきます。
今までは Riot.js での新規実装が難しかったため実装依頼を絞っているような状況でしたが、実装スピードが上がることによって他部署とのコミュニケーションも円滑になり、良いフィードバックループが生まれることが期待されます。
世界的に見て利用者が多い
State of JavaScript 2022 の報告では、フロントエンドフレームワークの利用率において React は近年トップを保ち続けています。
利用者が多いということは出回る情報が多かったり便利なライブラリが多く作られるということで、今まで Riot.js で抱えていた学習のしづらさを解消できます。
また、利用者が多いとその分だけ使えなくなると困る人が多いので、ある日突然メンテナンスが止まるというリスクがある程度抑えられるはずです。(これは一概には言えませんが...)
開発リソースの流動性が高い
開発者人口が多いことに付随しますが、採用においても外注においても需要も供給も多い状態なため、開発リソース流動性が高いと言えます。
2021年に Angular から React へのリプレイスを実施したログラス様の記事でも同様の主張がなされており、その後一定の成果を上げていることからも、開発言語やフレームワークと採用の関係の深さがわかります。
(補足) Riot.js を採用したのは間違いだったか
今回 Riot.js を脱却するという決断をしましたが、「技術投資」という言葉があるように技術的なトレンドというのは先が読みづらく、その時々の状況によるため一概には言えません。
今回の React 移行も数年後には負債になっている可能性も大いにありますが、トレンドを追い続けること自体が目的になってはいけなく、それに対するリターンは何なのかを考える必要があります。
これまで事業を支えてきた Riot.js と既にチームを離れた方を含む開発メンバーに敬意を示しつつ移行しましょう、というのをチーム内に伝えました。
移行方法の検討
前提として、以前は最も簡単な React の動作環境として Create React App がありましたが、 React 公式ドキュメントのプロジェクト作成ページ からも消えてしまい代わりに Next.js を最初に紹介するようになっているため、 Next.js を採用することにしました。
さて、一週間ほど集中すれば移植が完了するような規模のプロジェクトであれば話は別ですが、一定以上の規模のプロジェクトでは時間をかけて全てを移植してからビッグバンリリースしようとすると「永遠に終わらない」か「大きな障害を繰り返す」のような結末を迎える可能性が高いので、段階的な移行をできないか最初に戦略を考えることは大事です。
特に今回は今まで Riot.js で開発していたメンバーがスピードを落とさずに既存機能の改修に対応できるように移行中は Riot.js と React のどちらでも実装できる状況を作り出すことが望ましく、以下の3つの方法を検討しました。
a. Laravel と Next.js を並行で動かし、 nginx などのリバースプロキシのルーティングを移行が完了したところから Next.js 側に変更する
この方法は特定の言語やフレームワークにへの依存が少ないため、最も一般的に採用されることが多いと思われます。
デメリットとしてリリースの度にルーティングの設定を変更する必要が生じ、設定が肥大化する可能性があります。
b. Laravel 上で React を動かせるようにし、徐々に React へ移行し、最終的に Next.js に乗せ替える
この方法では最初の変更が少なく既存の Riot.js のコードで問題が起きる可能性は最も低いと考えられます。
一方で過渡期でだけ必要になる Laravel の設定が必要になったり、一番最後に大変な Next.js への載せ替えが発生するという懸念があり、面倒ごとを後に回している感が否めません。
c. Next.js 上で Riot.js を動かせるようにし、 Next.js に載せ替え、徐々に React へ移行する
そもそも出来るのかという技術検証が必要で最初の移植作業が大変ですが、それさえ乗り切ってしまえば後々の工数は少なくなります。
(強いて挙げると移行中にやっぱり Next.js を採用しないとなったときに無駄に複雑になってしまうというリスクがありますが、どこまで考慮するかという話になります。)
今回は検証の結果、もともと blade ファイル内で呼び出していた Riot.js のコードを next/script
で呼び出すことで動かせそうなことが分かったため懸念点がクリアになり、長期的に見たときにメリットの多い c. の方法を採用しました。
プロジェクトを取り巻く状況(ロードマップ、開発メンバー、組織構成など)は変わらないのが一番ですが、現実的には変わる可能性が大いにあります。
今回採用した方法のように不確実性を先に潰せるのであれば、最初に頑張るだけの価値はあるのかなと思います。
移行の経過と良かった点・工夫している点
すべての Riot.js のコードを Next.js 上に移植
移行方法の検討が終わったあと Riot.js で実装された移行対象のページをリストアップしたところ89ページありました。
一度にあれこれ改善しようとせず、とにかくまず Next.js で動くことを目標として、極力まるまるコピペで済むように実装を工夫したり、 VSCode の Snippet を利用したりして、流れ作業で移行できるように準備しました。
また似たようなコードを繰り返す必要があるところでは GitHub Copilot が大活躍しました。
もっとファイル数が多ければ移行用のスクリプトを実装することも検討されますが、スクリプトを実装するにも工数がかかりますし、既存のコードからある程度柔軟に提案してくれる GitHub Copilot はリファクタリングと相性が良いです。
結果、上記の移行方法の検討と準備に2週間くらいかけて、実際の移植作業の9割は2日で集中して終わらせました。
Next.js サーバーの本番稼働
Riot.js のコードを一通り Next.js へ移植した後、チーム内で動作確認も行い、さぁいつでも本番公開できるぞという状況になりましたが、万が一システムが使えなくって業務がストップしてしまうと約200店舗の現場で混乱を招いてしまうため、極力リスクを潰した上で本番環境を切り替える方法を検討しました。(ここは事前の検討が甘いところでした。)
そこで、従来の Laravel サーバーを安定版、 Next.js サーバーをベータ版として別の URL にデプロイし、いつでも安定版にアクセスできるようにした上でベータテストに協力してくれる店舗を募りました。
1ヶ月ほど問題なく業務が回るということが確認できたため全店舗向けに公開したところ、一部店舗で使用している iPad の iOS のバージョンが古い影響で画面が表示できないという問題が発生したため一度差し戻しましたが、不具合を修正して2日後に再公開し、その後は Next.js が稼働しています。(あとはいい感じだっただけに、惜しい...!)
データ取得処理の React への巻き上げ
TypeScript 化と同時にやりたいこととして API の型定義と自動生成がありました。
React 側の通信処理は都度自分たちで実装するのではなく、 OpenAPI を整備しつつ Orval を使って TypeScript の型定義 SWR のコードを生成しております。
(シンプルな作りなため Oval を採用しましたが、 OpenAPI の書き方に対応していないことも一部あり、パッチを当てるか他のツールに乗り換えるか検討の余地があります。)
データ取得処理を極力 React 側で行い Riot.js のマウント時にデータを受け渡すようなリファクタリングを行うことで、少しずつ Riot.js 内のコードを減らして React で再実装するときの差分を減らしています。
React でコンポーネント再実装
後は React のコードをガシガシ書いていくだけですが、チームに React と TypeScript を初めて触るメンバーも居るためキャッチアップが懸念でした。
りあクト! TypeScriptで始めるつらくないReact開発 等を読んで勉強してもらいながらも、色々と教えないといけないなと思っていたのですが、コードレビューをしているとたまに初学者がたどり着くには難しそうなコードが出てきて「どこで教わったの!?」となることがあり、聞いてみると GitHub Copilot や ChatGPT で提案されたコードだと聞き驚きました。
このように GitHub Copilot や ChatGPT は教育的な側面でも有用ですが、前提としてコードやコメントがある程度整理されていたほうが正確な提案がされやすいので、移行のタイミングとしては良かったのかもしれません。
他に気をつけていることとして、書き換えるついでに新しい機能を実装しようとせず書き換えの完了を優先する、逆に既存の実装を再現しようとすると大変な場合は無理に再現せず書き直す、というように状況に応じて臨機応変にやっています。
この辺りが柔軟にできているのは最初の検討と移植作業を頑張ったおかげかな思います。
まとめ
React への移行はまだ 100% 完了した訳ではありませんが、すでに Next.js が本番で動いており、元々 Riot.js を書いていたメンバーも徐々に React が書けるようになっているので、いい感じに軌道に乗ってきた感触があります。
(一方で、段階的な移行が途中で止まって余計に複雑化してしまっているプロジェクトも世の中には沢山あるため、できるだけ短期間で終わらせようとする気合いは必要です。)
取り巻く環境はプロジェクトによって様々なためリプレイスやリアーキテクティングに絶対的な正解はありませんが、一例として参考にしていただければ幸いです。
またこの記事でも何度か触れている GitHub Copilot や ChatGPT についても、 React 移行とタイミングが重なったことによってメリットを最大限引き出して開発できているのではないかと思います。
nobitel では GitHub Copilot for Business を導入しており今後も活用していく予定なので、その辺りの情報をチームとして発信していけるといいですね。(今後の記事に期待)
Discussion