💻

Web版TimeTreeのスタック変遷 #TimeTreeアドカレ

2023/12/01に公開

これは株式会社TimeTree Advent Calendar 2023の1日目の記事です。
https://qiita.com/advent-calendar/2023/timetree

こんにちは、TimeTreeのフロントエンドエンジニア @fujikky です。

TimeTreeユーザーの多くはiOSやAndroidなどのアプリで使って頂いていますが、実は2016年からWeb版の提供もされており、私は開発の初期から今までずっと関わってきました。

この記事では技術スタックの変遷に焦点を当て、変化の早いフロントエンド業界を TimeTree の Web 版がどのように渡り歩いてきたかを紹介します。

2016年 開発開始

開発メンバーは自分を含めて3名で、iOS、Androidエンジニアの兼務でした。ES5以降のモダンなJavaScriptベースのアプリケーション開発も初めてだったので「最近はvar使わないのかー」のような会話をしたところから始まりました。

初期の技術選択ですが、カレンダーをSPAで作ることは早々に決定していて、当時流行っていたサービス(Evernoteなど)のスタックを眺めつつ、Reactに絞りました。Reactはまだv0.14.xで、日本語の情報もあまりなかったのですが、今思うとReactは設計思想がしっかりしており、大規模な開発にも耐えうるので良い選択だったと思っています。


当時の調査資料

そのほかのスタックとしては、BrowserifygulpBabel(v6)、Redux(v3)、React Router(v2)、Sass などになります。Reduxの導入も最初は初めての概念に戸惑ったりしましたが、社内勉強会などを開催して理解に努めました。テストはほぼ書かれていなかったのですが、ユニットテストにmocha、E2Eテストにはnightmareなどが使われていました。Nodeのバージョンは2015年のio.js騒動が落ち着いたv4〜v5でした。

TimeTreeの予定データは繰り返し予定の仕組みの関係やBEサーバーの都合上、すべての予定データをローカルにキャッシュする必要があるため、そのストレージとしてIndexedDBが使われました。そのままでは使いづらいため、ラッパーライブラリとしてGoogleのLovefieldを使っています。ただしこれもメンテが止まっているので移行したいライブラリの一つです。


キャッシュDB選定資料

開発から4ヶ月後の2016年5月にWeb版はリリースされました。当初は独立したリポジトリで開発されていましたが、リリースフローの簡略化のため、開発終盤でではBEサーバー(Rails)のリポジトリとの統合されました。セッション周りの実装がRailsに依存しているため、Railsとは比較的密結合な構成となっており、HTML配信は現在でもRailsから行われています。

2016〜2018年 メンテナンス期

Web版をローンチしたあとは、iOS、Androidアプリの新機能開発にリソースが必要だったため、開発チームは実質解散となり、プロダクトはメンテナンス期に入りました。 新規の機能開発はほぼできなかったのですが、バグ修正やライブラリのアップデートなどは定期的にやっており、リソースの隙間を狙って各種ライブラリの移行などが行われていました。

ローンチ後割とすぐに Browserify から webpack に移行し、複雑だったJS以外のバンドル処理が統合されました。2017年にはnpmからYarnへ切り替えました。当時はnpmのインストールが遅いことや、npmと比べて厳密なバージョン固定が可能なこと、その他便利なユーティリティも多く提供されていたことなどが移行の理由でした。現在はYarnとnpmは機能的も差異はほぼなくなったので、元に戻すか、pnpmにするか、などの議論がされています。合わせてタスクランナーのgulpもnpm scriptsに切り替えています。

2018年になると React Router v4 になり、大きな breaking change に当時の React 業界がざわついたのですが、TimeTreeではまだルートの数が多くなかったため、移行は比較的スムーズに行われました。

現CTOの@bontyが、ソースコードの自動フォーマットツールである Prettier を導入してくれたのですが、そのPRのdiffが4万行ぐらいだったので破壊神の異名がつきました。とはいえ、ソースコードのフォーマットを人間が考えなくてよいというのは、想像以上に開発体験の向上に寄与してくれたと思います。


破壊神

2018年の序盤に念願だった React のサーバーサイドレンダリング (SSR) を導入しました。導入した背景としては、SEOなどのmetaタグを生成する処理をFE側に寄せたかったことや、First ContentfuL Paint (FCP) を早くしたかったことなどがあります。 ただし、認証周りの制約から Rails と切り離すのは難しかったので、Airbnbが開発したHypernovaというライブラリを使って、Node サーバー上で React を SSR させたものを Rails のビューに埋め込んでクライアントに返すという形で実現しました。しかしHypernovaは2019年ごろからAirbnb社で使われなくなり開発が停滞してしまい、その後は弊社の中でも技術負債扱いとなるのでした。


当時の導入時の資料

また、Hot Module Replacementを導入して開発体験を向上させたり、React Router のルートごとにCode Splittingをしてパフォーマンスを向上させたりしたのもこの頃でした。

2018〜2020年 アーキテクチャの改善

今までiOS、Androidの兼務エンジニアが担当していたWeb版だったのですが、待望のFE専任メンバーが2名入社してくれたのをきっかけに、開発基盤が急ピッチで改善されていきました。


待望のFEエンジニアが入社したときにお願いしたリスト

この頃 Babel v7 がリリースされ、TypeScriptのサポートが追加されたことで、既存のJavaScriptベースのプロジェクトをインクリメンタルにTypeScript化させていく下地ができました。これ以降は新規ファイルはTSで書かれ、既存ファイルもリファクタリングの度にTS化されていきました。現在はリポジトリ上の90%近くのファイルがTS化されています。

スタイリングはSassを使っていたのですが、コード規約もゆるかったので名前空間のコンフリクトによるスタイルの崩れが問題になっていたところ、CSS in JSのライブラリであるstyled-jsxが導入され、CSSのカプセル化が可能になりました。styled-jsxは書き味が微妙だったので、わりとすぐにEmotionに置き換えられました。

コンポーネント周りの実装ですが、Presentation層とContainer層を分離する設計や、Presentation層をAtomic Designにそって階層化する規約などが導入されました。 クラスコンポーネントから関数コンポーネントに切り替えていったのもこの頃だったと思います。Presentation層はステートレスな関数コンポーネントに、Container層はrecomposeを使ってロジックを注入する作りになりました。

また、Storybookの導入により、Presentation層のコンポーネントのカタログ化も行われました。Storybookの各ストーリーに対して、storycap + reg-suit による画像差分チェックを行い、コンポーネントのビジュアルリグレッションを防ぐ仕組みも導入されました。

ライブラリの更新は定期的に手運用でバージョンアップしていたのですが、Renovateを導入して自動化しました。

この頃は機能開発もガンガン行われており、デグレを起こさないようにE2Eの強化も必要になりました。当時はまだIEをサポートしていたので、WebDriverIO が採用されました。 ただし、E2EをIEで動かすためには、BrowserStackなどのクラウドサービスを使う必要があり、追加の費用やCIの実行時間が長くなってしまうという問題があり、ほぼChromium系ブラウザでのテストしか走らせていませんでした。

2020〜2022年 再びメンテナンス期

2020年ごろには、FEエンジニアも増えてはいたのですが、別の新規プロジェクトにリソースが充てられるようになり、Web版の開発リソースは再び縮小されていきました。UIのリデザインやパフォーマンス改善などが進行中の状態だったのですが、チームは実質解散状態となり、Web版の開発もほぼ停滞状態になってしまいました。

この頃は E2E テストも不安定になってきており、デプロイ時に CIが落ちてしまう原因となっていたので、不安定なテストから徐々に無効化されていきました。


E2Eが無効化されていく悲しみ

2023年〜 再び開発がアクティブに

2023年に今まで機能としてあった公開カレンダーのリデザイン・機能強化のプロジェクトが動き始めました。公開カレンダーはアプリでも使えますが、公開されたスケジュールという機能の性質上、Web版の価値が高くなります。今まで別プロジェクトに充てられていたリソースが再びWeb版に集まり、これを機にWeb版の開発が再びアクティブになりました。

まずは開発が停止されたSSRライブラリである hypernova の脱却が行われました。SSR自体をやめることで構成がシンプルになり、機能追加をしやすい体制に変わりました。

各社の IE11 のサポートが終了に合わせて、Web版の IE11 サポートも終了しました。これにより、E2EテストでWebDriverIOを使い続ける必要がなくなり、ほぼ無効化されたテストを Playwright に置き換える作業が開始され、再びCIでのテストが行われるようになりました。Playwright導入にあたってはE2E勉強会を開催して書き方のベストプラクティスをチームで学んでいます。

E2E勉強会

新機能を作る際に、APIの通信周りにReact Queryを使って実装されました。OpenAPIの定義から型定義とReact Queryのラッパー関数を生成するツール(openapi-codegen)を使って、型安全なAPI通信を実現しています。状態管理は今までReduxでしたが、API通信の状態管理はReact Queryに任せ、それ以外はJotaiへの移行を進めています。

2年間のろくにライブラリのメジャーバージョンアップがされていなかったので、breaking changeが大きいものもありましたが、機能開発の妨げにならない範囲で徐々にアップデートを行っていきました。Renovateのチームレビューワーアサイン機能とGitHubのレビューワーの自動ローテーション機能を使って、アップデート作業を個人に割り振ることで各自が責任を持ってアップデート作業に取り組めるようにしました。移行作業の大きいものはステップを切って、スプリントの一部に組み込むようにしています。直近だと React 18 へのアップグレードを進めているのですが、依存ライブラリのバージョンを先に上げる必要があり、その調査&タスク分割から開始しています。React Router の v5 から v6 も大きな breaking change があるので、今後対応予定です。


Renovateが作ったPRを空にできた瞬間

今後やりたいこと

たくさんやりたいことがありますが、現在候補に上がっているのは以下のものです。

  • webpackからViteへ切り替え: 開発時のビルド速度の向上を目的としています。
  • Zero Runtime CSS-in-JS への切り替え: 社内のほかプロジェクトで採用例がある vanilla-extract が候補に上がっています。
  • IndexedDB ライブラリの移行: 現在使っている Lovefield のメンテが止まっているので、ライブラリの移行が必要です。候補はReact HooksをサポートしているDexie.jsが濃厚です。
  • Next.jsへの移行: SSRを復活させた上で構成をシンプルにできるメリットはありますが、Railsと素結合にする必要があり、まだまだ課題が山積みです。

おわりに

この記事では、Web版TimeTreeの開発の歴史と技術的な変遷について詳しく紹介しました。

初期の技術スタックから始まり、webpackへの移行、新しいツールの導入、SSRの取り組み、そして現在進行形のアップデートまで、チームは変革の波に乗りながら成長してきました。特に、自動化ツールの導入やライブラリのアップデートにおいては、開発効率の向上が実感できました。

会社の優先度の都合上、一時的なメンテナンス期に入ることもあったのですが、2023年に入り、Web版にはリソースが再投入され、再び開発がアクティブになっています。

TimeTreeでのフロントエンド開発に興味を持たれた方は、ぜひカジュアル面談などでお話ししましょう!

https://open.talentio.com/r/1/c/timetree/pages/29064

TimeTreeの採用情報

TimeTreeのミッションに向かって一緒に挑戦してくれる仲間を探しています。TimeTreeで働くことに興味がある方はぜひ、Company Deck(会社紹介資料)や採用ページをご覧ください!

TimeTree Tech Blog

Discussion