2024.03.16

Firebaseを使ったモノレポ構成のテンプレートリポジトリを作りました

ここ数ヶ月で新規プロジェクトの開発を担当することが場面が増え、Next.jsとFirebaseを組み合わせて開発することが多かったです。毎回同じような技術スタックで環境構築し、各種ツールの設定を繰り返していました。

プロジェクト初期の開発の効率化を図るために、Next.jsとFirebaseを使用したモノレポのテンプレートリポジトリを作成しました。この記事では、そのリポジトリの目的や背景、アーキテクチャ、開発時の課題と解決策、そして最終的な成果について書きました。

tl;dr

・Next.jsとFirebaseを使えるテンプレートリポジトリを作りました。 ・リポジトリをcloneしてpnpm installして環境変数をセットしたら、フロントエンドを起動して開発を始められます。 ・Cloud Functionsもすぐにデプロイできる状態になっています。

今回の成果物

今回の成果物はこちらのGitHubリポジトリで公開しております。 https://github.com/andmohiko/firebase-monorepo

WebのパッケージはNext.jsを使っていますが、こちらはすきなフロントエンドフレームワークに置き換えることができます。

目的・背景

モノレポとは

モノレポとはプロジェクト内の複数のアプリケーションを単一のリポジトリで管理する手法です。 モノレポには次のようなメリットがあります。

  1. コード共有: プロジェクト間で共通のコードを簡単に共有できるため、重複を避け、メンテナンスを容易にします。
  2. 依存関係管理: プロジェクト間の依存関係を一元管理できるため、依存パッケージのバージョン管理が容易になります。
  3. チーム全体の効率化: すべてのコードが同じリポジトリにあるため、開発者間のコミュニケーションとコラボレーションが向上します。

課題感

作るアプリケーションはプロジェクトごとに変わりますが、採用する技術スタックは同じになりがちです。開発スピードとコストを考えるとNext.js+Firestore+Cloud Functionsが結論パになりました。

新規プロジェクトが立ち上がるたびに、create-next-appをして、TypeScriptをinstallして、ESLintとPrettierの設定をして、Cloud Functionsの環境構築をして、という作業を行います。環境構築はマネージャーである私が担当することが多く、環境構築が終わるまでは他のメンバーは待ちになります。プロジェクト初期の開発を効率化し、本来集中すべき機能開発に最初から集中できるようにしたいという課題感を抱えていました。 特にFirebaseを使う場合はCloud FunctionsもTypeScriptで実装できるので、フロントエンドと共通化できることが多く、一元管理できると便利だと思っていました。

今回の目的

この環境構築と設定作業は決まった作業であるため、ある程度テンプレート化できると考えました。 このリポジトリは、開発チームが一貫した開発環境を構築し、プロジェクト全体の生産性を向上させることを目的としています。

技術選定の理由

Next.js

プロジェクトを新しく始めるときは一応他のフレームワークを検討するものの、毎回Next.jsに落ち着きます。 理由としては今のチームメンバーが慣れていて開発スピードを上げられることと、Reactのエコシステムを成熟具合が理由になります。

Firebase

データベースはFirestore, FaaSはCloud Functions for Firebase, オブジェクトストレージはCloud Storage for Firebaseを使います。設計のノウハウが社内に蓄積されていることと、ランニングコストの低さが選定理由として大きいです。

設計

ディレクトリ構成

このテンプレートリポジトリでは、フロントエンド(Next.js)とCloud FunctionsとFirebaseの設定を単一のリポジトリで管理する構成を採用しました。以下のようなディレクトリ構成になっています。

/monorepo
├── apps
│   ├── functions: Cloud Functions
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── web: フロントエンド
│       ├── src
│       │   └── pages
│       │       └── index.tsx
│       ├── package.json
│       └── tsconfig.json
├── packages
│   └── common: 共通で使用する型定義など
│       ├── src
│       ├── package.json
│       └── tsconfig.json
├── .eslintrc.js
├── .prettierrc.js
├── firebase.json
├── package.json
├── pnpm-lock.yaml: 各パッケージの依存関係はこちらで一元管理します
└── tsconfig.json

ここに管理画面やLPなどを追加する場合もappsに生やしていくことができます。

CI/CDの設定

lintのチェックをするGitHub Actionsが含まれています。 tsc, prettier, eslint, cspellを走らせます。

開発時の課題と解決策

依存関係の管理

モノレポで複数のアプリケーションを1つのリポジトリで管理する際、それぞれのパッケージの依存関係やバージョンが一致していないとビルドエラーや実行時エラーが発生しやすくなります。特に、アプリケーションごとに異なるバージョンのライブラリを利用する場合、コンフリクトが生じることがあります。

この問題を解決するため、パッケージ管理にはpnpmを採用しています。モノレポでは複数のパッケージで同じモジュールを使用することがありますが、そのときに依存関係をうまく解決できないことがあります。pnpmのメリットとしてはnode_modulesでシンボリックリンクを利用することでモジュールが重複してしまう問題を解決しています。 また、npmやyarnではlernaを使う必要がありましたが、pnpmではデフォルトでworkspace機能があります。

こちらにturborepoを組み合わせて使っています。turborepoは高速でスケーラブルなモノレポビルドシステムで、TypeScript 及び JavaScript のモノレポ環境に特化したツールです。Next.jsを提供しているVercelによって開発されています。 モノレポではビルド時間の増加やデプロイの複雑さといった課題がありますが、turborepoではキャッシュとインクリメンタルビルドの仕組みにより、変更された部分だけを再ビルドすることができます。

開発者間のコラボレーションとコードレビュー

モノレポではプロジェクト全体のコードが1つのリポジトリに集約されるため、特定のアプリケーションやパッケージだけを変更した場合でも他の部分に影響を与えないようにする必要があります。各開発者が異なるパッケージで並行作業する際、衝突やレビュー不足によるバグの発生が懸念されます。

この問題は開発フローを固め、開発ドキュメントを充実させることで対応しています。また、自動化できるチェックはGitHub Actionsでなるべくチェックすることでレビューのコストも落としています。

CI/CDの最適化

モノレポでは単体のアプリケーションに比べてコードベースが大きく、CI/CDの実行時間が長くなりがちです。影響範囲が小さいコードでも実行時間の長いCIを走らせるのは無駄が多いと思いました。

そこで、CI/CDは各アプリケーション・パッケージごとに設定しておくようにしました。commitした内容の影響範囲だけCIが回るようにすることでCIの実行時間を短縮しています。

まとめ

Next.jsとFirebaseを使用したMonorepoのテンプレートリポジトリを構築することで、開発チームは一貫した開発環境を構築し、プロジェクト全体の生産性を向上させられるようになりました。 今後も新規プロジェクトが立ち上がることがあると思うのでこのテンプレートを使っていきたいと思います。

参考

node_modulesの問題点とその歴史 npm, yarnとpnpmTurborepoのチュートリアル