フロントエンドでmonorepoを採用してみた振り返り
はじめに
FEチームの遠藤です。
先日リリースした食べログノートでは、FEチーム初の試みである monorepo を採用しました。
monorepo とは一つのリポジトリに複数プロジェクトのコードを格納するリポジトリを指します。
しかし、複数プロジェクトのコードが格納されていれば monorepo になるのではありません。コードが意味を持ったまとまりになっていて、それぞれに境界が存在する必要があります。
今回の記事では monorepo を採用してみてどうだったかについて書いていきます。
前提の認識合わせ
今回の記事で言及する monorepo のリポジトリに対する概要や開発体制について予め認識を合わせないと記事の内容が伝わりにくいと思いますので、まずは前提の認識合わせを行います。
今回の記事での monorepo とはリポジトリにバックエンドのコードは含まず、フロントエンドのコードのみを含んだリポジトリを指します。
リポジトリに含まれているアプリケーションは現在2つです。
リポジトリ開発に携わるチームは現在2チームです。
結論
メリット
共有モジュールを楽に使用できる。
ライブラリのバージョン管理がしやすくなった。
アプリケーション間に跨る変更を1つの PR で作成できるようになった。
ESLint, Prettier, Jestなどの設定をアプリケーション・モジュール間で共有できる。
コードベースを把握しやすい。
デメリット
現状はまだデメリットなし。
詳細については以下で説明していきます。
なぜmonorepoを採用したのか?
食べログでは複数のアプリケーションで共通のモジュールを使用しているので、将来的には monorepo にした方が管理しやすそうという話が以前から挙がっていました。
先日リリースした食べログノートでは、Next.js で作成したアプリケーションと Ruby on Rails に組み込むための React で作成したアプリケーションの2つを作成することになっており、2つのアプリケーションで共通のモジュールを利用する予定になっていました。
また、既に他のアプリケーションでは複数リポジトリでプロジェクト開発を進めていたのですが、リポジトリ毎のライブラリのバージョン管理が大変という課題もありました。
まとめると以下のような要求がありました。
①複数のアプリケーションで共通のモジュールを手軽に使えて管理しやすくしたい。
②リポジトリ毎のライブラリのバージョン管理を楽にしたい。
上記の要求を解決するために、monorepoを採用しました。
ディレクトリ構成
主要なディレクトリとしては、root 直下に apps と packages 2つのディレクトリがあります。
root
├── apps
│ ├── app-pc
│ └── app-pc-partial
├── packages
│ ├── queries
│ ├── test-utils
│ ├── type-utils
│ └── ui-lib-pc
└── package.json
# 一部のディレクトリは割愛しています
apps 配下と packages 配下の各ディレクトリの package.json を複数管理するために npm workspaces を使用しています。
apps: アプリケーションを格納するディレクトリ
今回は下記2つのアプリケーションが格納されています。
app-pc
Next.jsで作成したアプリケーションapp-pc-partial
Ruby on Rails に組み込まれる React で作成したアプリケーション
packages: アプリケーションで使用するUIコンポーネントやutilty関数などを格納するディレクトリ
今回は下記のようなモジュールが格納されています。
queries
GraphQL Code Generator で生成された Querytest-utils
Test で使用する utility関数type-utils
utility型定義ui-lib-pc
UIコンポーネント群
※ 一部のモジュールは割愛しています
monorepo を使用してみて感じたメリットについて
「結論」で述べたメリットについての詳細を記載していきます。
共有モジュールを楽に使用できる
「ディレクトリ構成」で記載したようにモジュールは packages ディレクトリ配下に切り出して npm workspaces でモジュール毎に package.json を管理しています。
npm workspaces により node_modules にシンボリックリンクが作成され、 apps ディレクトリ配下から packages ディレクトリ配下のモジュールを import して参照することができます。
### apps/app-pc
# Footer UIコンポーネントを import
import { Footer } from "@tabelog/tabelog-note-ui-lib-pc";
### apps/app-pc-partial
# Footer UIコンポーネントを import
import { Footer } from "@tabelog/tabelog-note-ui-lib-pc";
これで各アプリケーションでモジュールを手軽に使えます。使用するモジュールがアプリケーションと同じリポジトリ内にあるので管理もしやすくなりました。
副次的効果として、モジュールを相対パスではなくモジュール名を指定して読み込むことができるようになりました。
npm workspaces を使用して複数 package.json を管理しているので、今後もし packages 配下のモジュールを他のリポジトリで使用したいときに手軽に npm にパッケージ公開をできるのも嬉しい点の一つです。
ライブラリのバージョン管理がしやすくなった
root の package.json にリポジトリ内で共通して使用する TypeScript, Jest, ESLint, Prettier などのライブラリを記載しているので、アプリケーション毎やモジュール毎に同じライブラリのバージョン管理をする必要がありません。
### package.json
"workspaces": [
"apps/*",
"packages/*",
],
"devDependencies": {
"typescript": "x.x.x"
"jest": "x.x.x",
"eslint": "x.x.x",
"prettier": "x.x.x",
}
アプリケーション・モジュール間に跨る変更を1つのPRで作成できるようになった
今回のケースでは各アプリケーションで 共通の UI Component や GraphQL の Query などのモジュールを使用することが多かったのですが、同じリポジトリに共通のモジュールがあることで1つの PR で修正ができるのは実装者視点とレビュアー視点の両者で開発体験が良かったです。
実装者としては packages 配下にある共通モジュールを使用するときに、モジュールを更新する PR と使用するアプリケーション側で新しいモジュールを読み込むようにする PR をそれぞれ作る手間がなくなります。アプリケーションとモジュールを結合した動作確認も手軽にできます。
仮にバグを埋め込んでしまって Revert する場合にも、1つのリポジトリに対する Revert PR を作成するだけなのでとても楽です。
レビュアーとしては、1つの PR だけで実装の内容が理解できるのも嬉しいです。実装者と同様に動作確認も手軽にできる点も良かったです。
ESLint, Prettier, Jestなどの設定をアプリケーション・モジュール間で共有できる
各アプリケーションやモジュールで ESLint, Prettier, Jest などの共通して使用しているライブラリの設定を共通化できるのは、設定を統一することができて一元管理できるので良かったです。
設定の差異によって詰まることもなくなりました。
新しいアプリケーションを開発するときも再度ライブラリを入れる必要もありません。既存の設定を使い回すことができるので、すぐに新しいアプリケーションの開発に着手することができます。
コードベースを把握しやすい
1つのリポジトリに関連するコードが集約しているのはコードが把握しやすく、既存の再利用できるモジュールを見つけやすくなって同じモジュールを作ってしまうリスクの軽減になります。
他チームも同じリポジトリで開発をしているので、追加・修正した内容が見えやすく変更が追いやすいのも良い点と感じました。
monorepo を使用してみて感じたデメリットについて
「結論」で述べたデメリットについての詳細を記載していきます。
現状はまだデメリットがありません。
もちろん monorepo で開発をするにあたって monorepo の概念を知る必要があったり、 npm workspaces などの monorepo ツールを使用している場合には使用する方法についても知る必要はあります。
どの粒度で npm workspaces で管理するモジュールとしてアプリケーションから切り出すかなど考える点も多くなりますが、これらは必要なステップでありチームによって方針が変わる事柄なので monorepo のデメリットとしては ”現状はまだデメリットなし” と記載しました。
今回のケースのリポジトリは「前提の認識合わせ」で記載したように、フロントエンドのコードだけを含んでいること、まだアプリケーションが2つであり、関わるチームも2チームと比較的小さい規模です。
CI のビルドやテストが遅い、Gitのパフォーマンスが悪い、コードベースが巨大で複雑になっているといった monorepo のデメリットが顕在化していないのかと思っています。
まとめ
「なぜmonorepoを採用したのか?」の文末に記載した2つの要求を再度確認してみましょう。
「monorepo を使用してみて感じたメリットについて」の「共有モジュールを楽に使用できる」、「ライブラリのバージョン管理がしやすくなった」で紹介したように、monorepo 導入の目的であった2つの要求を解決することができました。他のメリットも多く monorepo を導入して良かったと思います。
ただ先ほど述べたようにまだリポジトリの規模が比較的小さく、 monorepo のデメリットが顕在化していないだけのように感じるので、リポジトリの規模が大きくなったときに再度 monorepo に対する振り返りを実施して記事にしたいと思っています。
記事を読んでくださった皆様に少しでもお役に立てたら幸いです。記事に対してご指摘などありましたらコメントをいただけると助かります。
さいごに
現在、食べログではフロントエンドに関わるポジションとして以下の2つを募集しています。
気になった方は是非チェックしてみてください!
・フロントエンド統括チームに所属するフロントエンドエンジニア
・フロントエンドをメインにサービス開発を担当していくWEBエンジニア
どれかに当てはまった方は以下のリンクも是非御覧ください!