この記事はテックタッチアドベントカレンダー16日目の記事です。 15日目は terunuma サンによる Chrome 拡張の Overview of Manifest V3 を翻訳しました でした。Greasemonkey 黄金期の牧歌的な時代を過ごしてきた身なので、ブラウザの拡張がどんどん肩身が狭くなっていくのは少し寂しかったりします。セキュリティ上仕方ないということなのかもしれませんが。
エンジニアの jigsaw です。今夏からパートタイムでテックタッチさんのお手伝いをしています。もうすぐ人生初のマイカーが納車されるので、今からどこへ行こうか考えながら床につく日々を送っています。
テックタッチでのガイド等を作成するウェブページ(いわゆる管理画面)は Nuxt で作られていました。ブラウザの拡張が Vue で書かれていたので当然のチョイスといえます。しかし、拡張が React ベースに書き換えられたため、共通で使うコードの取り回しなどを考えると、Nuxt から React の何かに移行しようという話が持ち上がってきます。そういうわけで、イイ塩梅で移行する方法はないかを調べるところからはじめました。
結果から先に書くと、イチから全部 React で書き換えることになったのですが、うまくいった話ばかりではなく、失敗したこともアウトプットしてよいとのことだったので、移行パスの調査からプロトタイピングまでをやった試行錯誤を放流して供養します。失敗は成功の母といいますし。
アウトサイダーから見たテックタッチ社
本題に入る前に余談ですが、いい機会なので外の人間からみたテックタッチ社について少し書いておきます。
作っているサービスについて
DX!DX!と叫ばれて久しい昨今ですが、私はてっきり Developer Experience の DX なのかとおもっていたところ、どうやら Digital Transformation らしいです。そんな世情で、デジタルディバイドという言葉があったりするように、DX の流れに取り残されてしまう人もいるわけで、そういう人を包摂しようとする取り組みがテックタッチというサービスだと捉えています。そうやって考えるとめっちゃいいサービスだなあ、と初見でおもいました。そして、大変そうな部分は瞬時に察しがついて、最初の面談で取締役の jun サンとひと盛り上がりしたのも思い出です。
中にいると、使っている人の声を垣間見ることもあるのですが、いいコメントを目にする度にいいサービスだなあ〜と勝手にぽかぽかしてたりします。
社風について
2020年になって働き方が大きく変わり、フルリモートで働くようになりました。私にとって、はじめて面談から仕事の開始までフルリモートで完結した会社がこの会社です。12月現在でも、社員の皆さんとは一度もお会いしたことがなく、画面越しにしか話したこともないままでいます。オンラインだけのコミュニケーションだけでうまく回るかな...と心配していたのですが、全くの杞憂で、とても暖かくそして手厚く迎い入れていただいてとても嬉しかったです。
また、書く文化がかなり浸透しているのも好きなところで、出入り業者風情にここまで見れてしまっていいのか...??というぐらい Notion には情報が書き貯められていて(本当に見せられないよ!!は伏せられているようです)、Slack も非常に活発かつパブリックで、スタートアップのちょっとヒリつくようなダイナミックさが感じられたりします。
というわけで、本題へ。
状態と要件
Nuxt でつくられた管理画面はサーバーサイドレンダリング(SSR)なしでシングルページアプリケーション(SPA)として運用されています。ページ数は40ページほどあり、そこそこの規模に見えます。サービスの性質上ブラウザの拡張と通信するシーンなど、いくつか複雑そうなページがあります。
ブラウザの拡張は Vue から React への書き換えが進められており、それに並行して Nuxt も React に置き換えてかつ新規開発になるところは React で書いていきたいので移行パスを考えてほしい、ということで手を動かしはじめました。
プランとプロトタイピング
以下、考えたプランとプロトタイピングの結果を綴っていきます。
A - フルスクラッチ
管理画面とはいえ、シンプルな SPA に見えたので create-react-app なんかでサクッと作ってしまえばよいのでは?という発想のもと作りはじめました。
メリット
- 何も考えず、既存のアプリケーションの挙動をみつつ移していけばよい
デメリット
- 全てを捨てることになるので大変
結論
このカードはホイホイ切るものでもないとはおもいますが、基幹となるライブラリを変えるので仕方ないかなと判断しました。少し手をつけて、Nuxt のコード量が想定より多くて複雑だったのと、私のドメイン知識が少なかったのでどこまでが仕様なのか判断がつきにくかったのがネックで別の手を試してみることにしました。
B-1 Next.js でリクエストを受けて Nuxt にリバースプロキシする
Next.js のカスタムサーバを立てて React で置き換えるページに関しては Next から出力したものを使用して、それ以外に関しては Nuxt にリバースプロキシすることで Nuxt と Next の共存を図ろうとしました。
メリット
- Nuxt の資産がそのまま活用できる
- カスタムサーバーを立ててしまえば、大抵のことはできるといえばできる
デメリット
- 先に書いたように、現状ではランタイムが必要なかったところにサーバーが増えることになってしまうので微妙
- ステートの共有が難しい
結論
ある程度要件通りに動作することは確認できました。ページ毎に Nuxt と Next がバチッと切り替わるので、複雑さは最小限で済ませられたとおもいます。ただ、Nuxt のステートを Next に持ち込もうとすると localstorage を経由したり、query parameter で送ったりと手段自体はありますが、少し躊躇うものでした。
また、サーバーが増えることになるので保守対象が増えてしまうのと、これまではブラウザのページ遷移なしでページ遷移できていたのがページ毎にサーバにリクエストを送る形式になると体験が損なわれるので二の足を踏んでしまいました。
B-2 Nuxt の Vue コンポーネントを Web Components に書き出して React から埋め込む
大枠として React の SPA をつくって、そこへビルドターゲットを Web Components にした Vue のコンポーネントを埋めることで共存を図ろうとしました。
Vuex のステートと、注入されている API 呼び出し用のインスタンスを window に持たせて、Web Component 側からそれを参照することで擬似的に Nuxt っぽい挙動はできるようになりました。
余談ですが、このステート部分と API 部分を面白半分で esbuild でビルドしてみて、信じられない速さで驚きました。正確にベンチをとったわけではないですが、体感で速さを感じるレベルです。swc など、ツールチェインまわりの脱 JavaScript/Node.js は来年はもっと進んだりするのでしょうか。
メリット
- 既存の Vue の資産を活かせる
- 新規の部分は React でバリバリ書ける
デメリット
- Nuxt にベッタリの部分が剥がすのが少し手がかかり、そしてそれも正常に動く保証があんまりないので、それをページ数分やっていくのと、フルスクラッチするのを天秤にかけるとどっちが早くて硬いだろうね?とはなってしまいそう
- 一番の懸念は vue-router と react-router の共存で、vue-router のインターフェースを react-router から変換して渡す方式などを検討した
結論
React を大枠とした Nuxt の既存コードを活かす路線はなかなかに茨の道であることがわかりました。また、Vue の Web Component ビルドはメンテされていくのでしょうか?ざっと見たところ2年ほど動きがないようですが...。Web Component 自体もプロダクションで採用するレベルかというと、少し尻込みしてしまう面はあるかなと。
B-3 Nuxt をそのままに React のコンポーネントを入れ込む
こちらはそこまで検証の時間はとれなかったのですが、逆に大枠を React にするのではなく、一旦 Nuxt のまま置いてそこに新規で作っていく部分を React にして、徐々に Vue の部分を置き換えてだいたいのコンポーネントができあがってから本格的に React ベースに移行するのはどうだろう?という案です。
メリット
- ここまでで一番 Nuxt の資産を活用できる
デメリット
- React を Web Component として扱えなくはないが、Vue ほどお手軽ではなさそうだった
- これはただの経験則ですが、初手で既存のアーキテクチャを残留させると、そいつは少なくともかなり長い期間生き残ることになり、結果的に複雑度が増すだけ、というオチになりがち
結論
これまで挙げてきた中でかなり保守的な選択ではありますが、硬さでいえば一番かなとおもいます。
C - Micro Frontends を想定したライブラリ/フレームワーク
Vue と React の共存という文脈で、Micro frontends も少し調べました。
IKEA や Amazon, Spotify などが Micro frontends というアーキテクチャに取り組んでいると言われています。日本語の情報は少ないですが、本家として[翻訳記事]マイクロフロントエンド - マイクロサービスのフロントエンドへの応用、副読書としてMicro Frontends を学んだすべて - LifeHack Engineering Blogがとても参考になりました。
Podium
上記の記事で取り上げられているものです。これは、podlet というページのフラグメントを1ユニットとして自由にライブラリを選択することができ(podlet A は React, podlet B は Vue というようなチョイスも可能)、SSR する際に podlet を layout で集約して html を返却し、クライアントでレンダリングした後は MessageBus という PubSub 型の仕組みで podlet 間で協調します。
とても面白い仕組みでしたが、
- ランタイムとなるサーバが必要になる
- 既存の Nuxt をバラしていく作業が必要になる
といったあたりが障壁となりました。他方、SSR が必要な場面では、逆に強力な武器になると感じました。
single-spa
こちらは Importmaps, Module federation などのいくつかの要素技術が集まったような仕組みになっています。独自のライフサイクルを持っていて、そこへアプリケーションを登録してページを構成していくような考え方のようです。
Importmaps は最新の Google Chrome でもフラグを付けないと有効にならない機能だったり(Polyfill の実装はいくつかあります)、Module federation は Webpack 5 で追加された機能で、こちらは Vue がまだ5系に対応してないようだったので断念しました。
また、既存の Nuxt のアプリケーションを移植する、ということに関しては Migrating existing SPAs に書かれているように、Nuxt のコードをバラして、かつ single-spa のライフサイクルにハメ込んでいく工程がなかなか重そうでした。
まとめ
Nuxt で書かれたアプリケーションを React に移すためのプランとプロトタイピングをいくつかご紹介しました。
結論としては、冒頭に書いた通り React の SPA をフルスクラッチでつくっていくことになりました。いろんなパターンで検証してみて、そこから大まかな移行コストを推測できたのと、他のいろいろな要件を踏まえての判断なのかな〜とおもっています。
Vue から React への置き換えや、React から Vue への置き換えという需要は一定程度あるのかなと察していますが、なかなか一朝一夕で達成は難しそうだと改めて感じました。一方で、ピュアな Vue のプロジェクトであれば、Web Components を介して React と共存するルートがあり得ることは収穫でした。