🔥

1つのFirebaseプロジェクト内で複数のミニアプリを管理する

2024/01/24に公開

Firebaseのミニアプリをぽんぽんと作ろうと思うと,とても便利なのだけど,ミニアプリごとにFirebaseのプロジェクトを立てているとすぐに上限に達してしまう[1]ので,1つのプロジェクトの中にミニアプリを詰め込みたくなりますよね.

特に,自分はCloudFunctionsをよく使うのですが,関数を複数作って管理するのは良いのですが,複数のミニアプリの関数を全て1つのpackage.jsonで管理していくのはしんどいと思います.
Hostingについては比較的複数のWebサイトを別々に管理するのが簡単ですが,CloudFunctionsについて,複数のミニアプリで整理して管理する方法について調べてみました.

新しいプロジェクトを作成する

新しいプロジェクトを作成しながら試していきます.

$ mkdir test-multi-mini-app
$ cd test-multi-mini-app
$ firebase init
...
...
# (cloud functionsだけ,TypeScriptの設定で初期化しました)
...
...
$ tree
.
├── firebase.json
└── functions
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── src
    │   └── index.ts
    ├── tsconfig.dev.json
    └── tsconfig.json

初期化された時に生成されたindex.tsの中にhelloWorld関数があると思うので,コメントアウトを外して以下のようなものをそのまま公開してみます.

index.ts
import * as functions from "firebase-functions";

// Start writing Firebase Functions
// https://firebase.google.com/docs/functions/typescript

export const helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});

$ firebase deploy --only functions
=== Deploying to '<プロジェクト名>'...

i  deploying functions
...
...
...

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/my-kitchensink/overview

https://us-central1-<プロジェクト名>.cloudfunctions.net/helloWorld
にアクセスすればHello from Firebase!と表示されるはずです

複数の関数を作る

では,関数を増やしてみましょう.
index.tsに関数を2つほど増やしてみます

index.ts
import * as functions from "firebase-functions";

// Start writing Firebase Functions
// https://firebase.google.com/docs/functions/typescript

export const helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});

export const helloWorld2 = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase2!!");
});

export const helloWorld3 = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase3!!!");
});

確認するまでもないですがこれで

  • https://us-central1-<プロジェクト名>.cloudfunctions.net/helloWorld
  • https://us-central1-<プロジェクト名>.cloudfunctions.net/helloWorld2
  • https://us-central1-<プロジェクト名>.cloudfunctions.net/helloWorld3

という3つの関数が公開されました.
3つのミニアプリと言い換えてもいいかもしれません.

複数の関数をファイルごとに分ける

ですが,一つのindex.tsファイルの中に関数を増やしていくのは得策ではありません.
関数ごとにファイルを分割するのが良いですよね[2]

以下のようにファイルを変更しました

$ tree .
.
├── firebase.json
└── functions
    ├── lib
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── src
    │   ├── hello.ts
    │   ├── hello2.ts
    │   ├── hello3.ts
    │   └── index.ts 
    ├── tsconfig.dev.json
    └── tsconfig.json

index.ts

const myFunctions = {
  hello: "./hello",
  hello2: "./hello2",
  hello3: "./hello3",
};

const loadMyFunctions = (funcsObj: { [key: string]: string }): void => {
  for (const name in funcsObj) {
    if ((!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) && Object.hasOwnProperty.call(funcsObj, name)) {
      exports[name] = require(funcsObj[name]);
    }
  }
};
loadMyFunctions(myFunctions);

hello.ts
import * as functions from "firebase-functions";

module.exports = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});

hello2.ts
import * as functions from "firebase-functions";

module.exports = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase2!!");
});

hello3.ts
import * as functions from "firebase-functions";

module.exports = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase3!!!");
});

この内容でデプロイすれば,動作は全く同じですが,関数ごとにファイルを分けることができました.
ですが,あくまでindex.tsを分割したにすぎないので,全く関連のないミニアプリだとこの方法で管理するのはちょっと気持ち悪い感じです.

複数の関数を別のプロジェクトに分割する

そこで,functionsディレクトリにあるnode.jsと同じような設定で,複数のプロジェクトを作成します.
残念ながらfirebase init functionsのような便利なコマンドがないので,手作業で作成する必要がありそうです[3]

先ほどのディレクトリを以下のように変更しました.

変更前
$ tree .
.
├── firebase.json
└── functions
    ├── lib
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── src
    │   ├── hello.ts
    │   ├── hello2.ts
    │   ├── hello3.ts
    │   └── index.ts 
    ├── tsconfig.dev.json
    └── tsconfig.json
変更後
$ tree .
.
├── apps
│   ├── app-hello
│   │   ├── lib
│   │   ├── node_modules
│   │   ├── package-lock.json
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── hello.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.dev.json
│   │   └── tsconfig.json
│   ├── app-hello2
│   │   ├── lib
│   │   ├── node_modules
│   │   ├── package-lock.json
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── hello2.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.dev.json
│   │   └── tsconfig.json
│   └── app-hello3
│       ├── lib
│       ├── node_modules
│       ├── package-lock.json
│       ├── package.json
│       ├── src
│       │   ├── hello3.ts
│       │   └── index.ts
│       ├── tsconfig.dev.json
│       └── tsconfig.json
└── firebase.json

それぞれのindex.tsはそれぞれのhello?.tsを読むだけに修正するだけです.
hello?.tsの中身は全く同じものです.

そして,ここが最重要ですが,firebase.jsonを修正します

firebase.json(変更前)
{
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"      
    ]
  }
}
firebase.json(変更後)
{
  "functions": [
    {
      "source": "apps/app-hello",
      "codebase": "app-hello",
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run lint",
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    },
    {
      "source": "apps/app-hello2",
      "codebase": "app-hello2",
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run lint",
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    },
    {
      "source": "apps/app-hello3",
      "codebase": "app-hello3",
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run lint",
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    }
  ]
}

sourceの場所やcodebaseの名前は適宜読み替えてもらって良いですが,このように指定してあげることで,CloudFunctionsを同じFirebaseのプロジェクトの中で完全に別のnode.jsのアプリとして管理する(Monorepoっぽい感じで)ことができるようになりました.

初期化に時間がかかる処理を別プロジェクトとして分離したい場合などにも有効でしょう.Firebaseの公式ドキュメントにも少し記載があります.

デプロイするときには通常通りに

$ firebase deploy --only functions

で全てをまとめてデプロイすることもできますし,

$ firebase deploy --only function:app-hello2

のように,codebaseで指定したプロジェクトのみをデプロイすることもできます.
変更のあるプロジェクトをデプロイできるので手軽です.

仮に関数名が被っていた場合にはまとめてデプロイした場合にはエラーが出てくれますが,codebaseを指定した場合には,既存の関数を上書きするようにデプロイされてしまいますから注意が必要です.

これでミニアプリを大量に管理できるようになったと思います.
何か間違いやもっと良い方法があればコメントで教えてください.

参考

https://firebase.google.com/docs/functions/organize-functions#managing_multiple_repositories
https://github.com/shinyaoguri/test-multi-mini-app

脚注
  1. 上限を引き上げる方法はありますが面倒ですよね.参考:Firebaseアカウントあたりのプロジェクト数 ↩︎

  2. 「Firebase, 関数, 複数」とかでググると関数ごとにファイルを分ける方法がたくさん出てくると思います.参考:Firebase複数のファイルに関数を書き込む ↩︎

  3. 自分はfirebase init functionsで生成されたfunctionsディレクトリをapps/hoge/functionsとかに移動させるのが楽なのかな〜と思いました ↩︎

Discussion