Cloud Functions for Firebase で環境変数を扱うための方法

# はじめに
Functionsにcrediancalな情報をもたせるにあたって、
可能な限り環境変数をそのまま利用した形式にしたかったのでその作業録となります。

外部APIのトークン情報などを環境変数で管理しており、CIや手元では環境変数の値で向き先を切り替えるといったことをしているので、可能な限りそこに合わせたかった都合もあります。

今回使用した言語はJavascriptで、[webpack](https://webpack.js.org/) + [Jest](https://jestjs.io/)を利用している構成になります。

JavaScriptでWebpackを利用したFunctionsに関しては[Cloud Functions for Firebase入門 (簡単なテストまで）](https://qiita.com/glassmonkey/items/a8a8a5f0b7c16b940f46)で記載したので、もしよければご確認ください。


# 公式が推奨する方法について
## 設定値をデプロイする方法
まず最初に公式で紹介されている設定値を与える方法を説明します。
これは環境変数をそのまま扱うわけではなく、予め設定値をデプロイしそれを利用する形式となります。
[公式のリファレンス](https://firebase.google.com/docs/functions/config-env?hl=ja)をみると`firebase`コマンドで設定や取得もできます。コマンドで設定ファイルを登録後なら、SDKからも設定値の取得もできます。

* 値の設定

``` shell
$ firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"
```

* 値の取得(コマンドの場合）

```shell
$ firebase functions:config:get
# -> 結果
{
  "someservice": {
    "key":"THE API KEY",
    "id":"THE CLIENT ID"
  }
}

```

* 値の取得(JavaScript SDKの場合）

```JavaScript
const functions = require('firebase-functions');
const someservice = functions.config().someservice;
```


また、デフォルトでFunctionsのRuntime上の`process.env`に展開される環境変数もあるようなので、興味のある方は調べてみても面白いかもです。(参考: [Firebase Cloud Functionsの環境変数はRuntimeによって変わるので注意 ](https://blog.ikedaosushi.com/entry/2019/08/20/114646))


## この方法について

公式が紹介している方法だけあって、こだわりがなく、複雑な設定項目が無いのならこれでもいいのかなと思います。

一方で自分は以下の観点で公式の方法は採用しない方針にしました。

* 設定値の制限の厳しさ
    * キーにUpperCamelケースの値がなぜかだめだった。
    * いわゆる`.env`の内容を流用するスクリプトを書いていたら怒られました。
    
* firebaseに依存する箇所の隠蔽化
    * 設定値がfirebase依存だと、全体的にfirebaseに依存する形になるので避けたかった。
    * テストを簡単にするため。
    * 将来的なfirebaseからの移行も検討の余地を残したかった。

* 構成の一貫性
    * 環境の差異はほぼ.envで記述できる向き先が異なる程度にとどめたかった。
    * 環境の差異からくる事故や作業漏れのリスク軽減のため。

# 環境変数をそのまま渡す方法
色々方法が考えられると思いますが、簡単なものはbundle時に環境変数そのものを埋め込む方法かなと思います。それにはwebpackの[val-loadler](https://github.com/webpack-contrib/val-loader)が簡単に利用できてオススメです。


## val-loaderを使った環境変数の渡し方

まずval-loaderをインストールします。

```bash

$ npm install val-loader --save-dev
```

次に環境変数(`prcess.env`)をbundleするためのファイル(`env.js`)を用意してwebpackにloaderの設定を追加すればOKです。

**env.js(環境変数をbundleに組み込むためのスクリプト)**
codeには実際にbundle時に評価されたい値を記載します。ほかにもキャッシュ用の項目などあったりするので、詳しくは[val-loadler](https://github.com/webpack-contrib/val-loader)を参照してください。
環境変数を利用する場合はこれをコピペすればOkです。


```javascript

module.exports = (options, loaderContext) => {
    return { code: `module.exports = ${JSON.stringify(process.env)};` };
};
```

**webpack.config.js**

```javascript

module.exports = {
  module: {
    rules: [
      {
        test: /env.js$/,
        use: [
          {
            loader: `val-loader`,
          },
        ],
      },
    ],
  },
};
```


利用する側は下記のようにenv.jsをimportをすれば変数`env`はビルド時の`process.env`と同じように環境変数にアクセスすることができます。

```javascript
// 環境変数に FOO=bar がセットされているとする

import env from "path/to/env.js";
// env["FOO"]
// -> bar となります。

```

## jestでの対応について
ちなみにここまでの内容だけだとwebpackの機能を使うことになっているので、import部分がjestだと動かずに失敗してしまいます。
なので少し追加作業が必要です。本当はこの作業も割愛したいのですが、方法を知っている方いれば教えてください。

**jest.config.js**
jestでimportを動くするためにはtrasnform設定を追加してあげます。キーには変換するファイルパターン、変換先ファイルを指定します。
今回は`import env from "path/to/env.js";`に対応する変換をjest用にしますので、それにマッチするパターンで記載します。これに限ったことではないですが、webpack側でloaderを追加した場合は何かしら対応するtransformを用意する必要があるみたいです。

```javascript

module.exports = {
//…
    transform: {
        'env\\.js': '<rootDir>/tests/module/env.js',
         //…
    },
//…
};

```

**tests/module/env.js**
実質val-loader用ファイルと同じことをしています。webpackとjestでコンパイルに微妙に差があるっぽいので別のファイルが必要な感じです。

```javascript

module.exports = {
    process(src, filename) {
        return `module.exports = ${JSON.stringify(process.env)}`;
    },
};
```

これでテストも無事に通るはずです。

# おわりに
環境変数を可能な限りそのまま活用する方法について説明しました。
Functionsで環境変数のまま利用しようとしている記事が見当たらずって感じだったので、自分なりにまとめてみましたがどうだったしょうか？

credencialな設定値を環境変数で管理はどこもやっている気はするので、それをFunctionsで活用できたら結構便利なんじゃないでしょうか？
おとなしくCloud Runを使う手も

同じような悩みを抱えている方の助けになれば幸いです。

感想や意見あればコメントあると嬉しいです。まさかりも歓迎です。