Firebaseの設定ファイルをプロジェクト毎に動的に生成する（Expo/React Nativeほか）

React Nativeのアプリの結合テスト環境を作りたいのですが、そのためには開発用・テスト用・本番用など、複数のFirebaseプロジェクトを容易に切り替えられるような仕組みが欲しいところです。
そこで、
[**webpackでfirebaseのconfigファイルをプロジェクト毎に動的に生成する（環境切り替えに使える）**]
(https://qiita.com/zaburo/items/25e32e33943581b38ad9)
こちらの記事を参考にしながら、Firebaseの設定ファイルを動的に生成してみたいと思います。

上記の記事ではWebpackの独自loaderを作って設定ファイルを差し替えていますが、Expoで作成しているアプリなど、モジュールバンドラの設定を変更できない場合もありますので、loaderで直接Webpack等に渡すのではなく、設定ファイル自体を生成してしまって、`.gitignore`に追加するという方針でやってみます。

# 切り替えるプロジェクトの情報を用意

```console
$ firebase login
$ firebase init
```

は、すでに済んでいるでしょうから、`.firebaserc`に[プロジェクトエイリアス](https://firebase.google.com/docs/cli?hl=ja#use_aliases)の情報があるはずです。

```.firebaserc
{
  "projects": {
    "default": "some-default-project",
    "test": "some-test-project"
  }
}
```

なんでもいいですが、こんな感じでデフォルトはこのプロジェクト、テスト用はこのプロジェクト、というエイリアスを登録します。これは直接`.firebaserc`を編集するでもいいし、

```
$ firebase use --add
```

で追加するのでもいいです。
`.firebaserc`をリポジトリ上に公開するかどうかは場合によるでしょうが、公開しない場合、かつ必要なエイリアスを明示したい場合は`.firebaserc-example`などを置いてもいいかと思います。

# 設定ファイルを取得するスクリプトを作成

ローカルに`firebase-tools`をインストールします。

```console
$ npm install --save-dev firebase-tools
```

これをコマンドを直接叩くのではなくJSファイルで使います。
コマンドの挙動は`node_modules/firebase-tools/lib/commands`を直接見るか、
[公式の簡易的なリファレンス](https://firebase.google.com/docs/cli?hl=ja#command_reference)で確認してください。

では、ここで[元の記事](https://qiita.com/zaburo/items/25e32e33943581b38ad9)で使用している`setup:web`を使いたいのですが、どうやら公式リファレンスを見るとこのコマンドは非推奨とされているようです。
なのでリファレンスに書いてある通り、`apps:sdkconfig`を使用してみます。

このコマンドで生成できる設定ファイルはJSONではなくglobal変数に`firebase`がある前提のJSファイルという面倒な代物なので（後ほど記載）、正直なところ`setup:web`を使ってしまった方がいいと思いますが、我慢します。

まず、そもそもReact NativeアプリはFirebaseにとってはWebアプリということでいいのかという疑問もあります。
少なくともExpoで開発しているときはWebアプリとして扱い、ejectしたりした後はiOS/Androidそれぞれという感じかと思っています。

さて、scriptsディレクトリを作り、Node.jsの世界で動く以下のようなJSを書きます。

```javascript:scripts/setFirebaseConfig.js
const fs = require('fs');
const firebaseTools = require('firebase-tools');

(async () => {
  try {
    const firebaseRC = JSON.parse(fs.readFileSync('./.firebaserc'));
    const projectId = firebaseRC.projects[process.argv[2]];
    await firebaseTools.use(projectId, {});
    const apps = await firebaseTools.apps.list('WEB', {});
    const currentApp = apps[0];
    const config = await firebaseTools.apps.sdkconfig('WEB', currentApp.appId, {});
    fs.writeFileSync('./js/firebaseConfig.js', config.fileContents);
    console.log(`Current firebase project is ${currentApp.projectId}`);
  } catch (e) {
    console.log(e);
  }
})();
```

1. 先ほどの`.firebaserc`を読み込み
2. 実行時の引数（`process.argv[2]`）からプロジェクトを特定。
3. `use`コマンドで使用するプロジェクトを宣言
4. プロジェクト内のWebアプリ一覧を取得
5. とりあえず0番目のアプリを現在のアプリだとする。Webアプリが複数ある場合はここでさらに特定が必要です。
6. 設定ファイルの内容を取得
7. 設定ファイルをwrite

このような流れで、`js/firebaseConfig.js`が生成されます。
後々整形するかもと思ったのでfsモジュールを使ってwriteしていますが、このように

```javascript
await firebaseTools.apps.sdkconfig(
  'WEB',
  currentApp.appId,
  { out: './js/firebaseConfig.js' }
);
```

3番目の引数にファイルの作成先を直接指定しても書き込んでくれます。

`.firebaserc`で設定したプロジェクトエイリアスを引数にして、コマンドを叩いてみてください。

```
$ node scripts/setFirebaseConfig.js default
```

生成される設定ファイルは、

```javascript:js/firebaseConfig.js
// Copy and paste this into your JavaScript code to initialize the Firebase SDK.
// You will also need to load the Firebase SDK.
// See https://firebase.google.com/docs/web/setup for more details.

firebase.initializeApp({
  "projectId": "project-id",
  "appId": "1:000000000000:web:xxxxxxxxxxxxxxxxxx",
  "databaseURL": "https://project-id.firebaseio.com",
  "storageBucket": "project-id.appspot.com",
  "locationId": "location-id",
  "apiKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "authDomain": "project-id.firebaseapp.com",
  "messagingSenderId": "0000000000000000",
  "measurementId": "G-XXXXXXXXXXX"
});
```

このようなフォーマットになっています。
これをそのままExpoなどで使うには,

```global.js
import firebase from 'firebase';
import "firebase/firestore";
import "firebase/functions";

global.firebase = firebase;
```

このようにあらかじめグローバル変数に`firebase`をセットしておけばOKです。

あとは
`.gitignore`に`js/firebaseConfig.js`を追加して、

```package.json
  "scripts": {
    "fb:config": "node scripts/setFirebaseConfig.js",
    "start": "npm run fb:config default && expo start",
    "android": "npm run fb:config default && expo start --android",
    "ios": "npm run fb:config default && expo start --ios",
    "web": "npm run fb:config default && expo start --web",
    "eject": "expo eject",
    "test": "npm run fb:config test && jest --coverage"
  },
```

`package.json`で適宜スクリプトを実行するよう定義しておきます。