Monorepo で Cloud Functions for Firebase へデプロイ
2022/9/8
はじめに
.NET におけるプロジェクト分割に慣れている身としては、Node.js でも積極的にモジュールを分割しつつ、Monorepo な構成で進めがちなわけですが、そんな構成でいざ世の中の便利サービスを使おうとすると詰まることも多いです。今回は、Monorepo で Cloud Functions for Firebase へデプロイするためのまとめです。
前提
関連ツール
- Yarn v1 系
- Firebase CLI 11.8.0
今回扱うリポジトリ内構造(本題と逸れる内容は省略)
依存関係
Cloud Functions 用パッケージである firebase-functions がサーバー向けモジュール server を参照し、server はサーバー・クライアント共通モジュールである core を参照。
リポジトリルート
> tree -L 1 .
.
├── README.md
├── firebase.json
├── client
├── core
├── firebase-functions
├── server
├── node_modules
├── package.json
└── yarn.lock
5 directories, 4 files
※関係ない内容は割愛
package.json
{
  "private": true,
  "workspaces": {
    "packages": [
      "client",
      "core",
      "server",
      "firebase-functions"
    ]
  },
  "devDependencies": {},
  "dependencies": {}
}
※関係ない内容は割愛
firebase.json
{
  "functions": {
    "source": "firebase-functions"
  }
}
参考材料
公式ドキュメントから押さえておくべきポイント
- yarn.lock ファイルがプロジェクト内にある場合は、そのロックファイルが優先される。
- 関数をデプロイする際、Firebase CLI では、ローカルの node_modules フォルダを無視する。
- file:接頭辞を使用してローカルの Node.js モジュールを含めることもできる。
観測されたデプロイ挙動
- firebase.jsonで- sourceに指定したディレクトリ外に配置された Node.js モジュールは参照できない。
参考 Issues@Github
方針
上記参考材料をもとに試行錯誤し、
- 何かしらの形で Cloud Functions 用パッケージ内に Node.js モジュールを配置
- file:接頭辞を使用し、配置した Node.js モジュールを参照
が良さそうだと考えました。もちろん、通常の Yarn Workspaces 構成とはギャップがあるため、ギャップを埋めるために何かしらの対応を行う必要があります。
ちなみに、このレベルでのギャップ埋めを許容するのであれば、デプロイ時のみ非公開モジュールの使用も選択肢に上がるかもしれませんが、積極的に採用すべきメリットを見出せなかったため、今回は扱いません。
解決策1
firebase-yarn-workspaces を利用することで問題なくデプロイできました。このパッケージにより、
- Cloud Functions 用パッケージ内に .firebase-yarn-workspacesディレクトリを作成し、Monorepo 参照モジュールをコピー
- Cloud Functions 用パッケージの package.jsonで"core": "*"を"core": "file:.firebase-yarn-workspaces/core"のように Monorepo 参照をfile:接頭辞を使用して書き換え
が行われる結果、うまくデプロイできるようになります。
ちなみに、README に記載されている Turborepo を使った例に従えば、上記操作を turbo prune により作成されたディレクトリで行うことで、既存ディレクトリが汚れるのを防ぐことができる、かつ、yarn.lock ファイルも最適化されるのでより良い形になると思います。
解決策2
Issues@Github で言及されている通り、
- 参照先を pack し、Cloud Functions 用パッケージ内に配置
- Cloud Functions 用パッケージの package.jsonで"core": "*"を"file:./core.tgz"のように Monorepo 参照をfile:接頭辞を使用して書き換え
でもデプロイできました。ただ、2段階で参照する構成になっているせいか、色々詰まりました。
おわりに
なんとかデプロイできたものの、結局は(turbo prune で切り出せるものの)リポジトリ内に一時的な差分を作ることにはなるので、(自動化できるものの)本質的ではない定型作業を入れざるを得ない面倒さがありますね。暫定対応と割り切って、公式なサポートを待つ感じでしょうか。