Firebase+Reactの開発環境構築とはまりどころ解説

# この記事の内容
* FirebaseでReactのSPAを作る際の開発環境構築手順を紹介します
* CORS等のはまりどころ回避方法を解説します
* create-react-appはちょっと触ったことがあるぐらいが想定です
* Firebase自体の概要は[ここ](https://qiita.com/ozota/items/a8db60cf167052cff22b)に書きました

# 開発環境整備手順
以下の手順はWindows10, VScode, WSLの環境で確認しています
## 準備
* 使うツールをインストールします

```
npm install -g create-react-app
npm install -g firebase-tools
```
* `firebase-tools`はfirebase管理用のコマンドラインツールです
* `create-react-app`で初期化します

      ```bash
      create-react-app gallery-tech
      ```

  * gallery-tech というディレクトリが作られ、その中がreact用に初期化されでいます
  * ところどころで出てくるgallery-techは私がこれから作るアプリ名なのでスルーしてください

* firebaeにログインし、プロジェクト追加します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/23a5fd1e-2b4e-a324-031f-b0bd6fad5100.png)

  * 「Firebase 向け Google アナリティクスのデータ共有にデフォルトの設定を使用する」はオフでもいいのですが、今回はオンにします

* firebaseの初期化
  * 初回の場合は`firebase login`でログインします 
  * gallery-techディレクトリで`firebase init --project gallery-tech`を実行します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/aacb1c7b-1368-d3ef-39d9-0e17c095fa9c.png)
  * スペースキーを押すと選択できます。今回はFirestore,Functions,Hostingを選択します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/e2fcbb71-ca4f-a553-0eba-ec9ae2dacb13.png)
  * エンターを押すと対話形式でいろいろ設定項目を聞いてきます。次の2つ以外は基本的にデフォルトでOKです
      * publicディレクトリを何処にするか？という問いは`build`ディレクトリにします。
 ![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/5aedb4ae-d40f-be00-254f-96e1158e86de.png)
          * 本番用にwebpackでコンパイルしたものは`build`ディレクトリに配置されるためです。
          * `create-react-app`で作られた`public`ディレクトリがあると思いますが、それは静的コンテンツ配置用です
              * コンパイルするとbuildにコピーされます
      * SPA用に設定するか、という問いもYesにします
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/5574dc94-7f56-54d8-90c8-f8b492acdbef.png)

  * firestoreの有効化はブラウザで行います。firebaseのサイトに行きます
    * 左のメニューからDatabaseを選択します。Firestoreの画面が出るので「データベースの作成」をクリックします  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/601b7fc6-37e7-0ab1-285e-bfbef1ab88c0.png)

    * ロックモード（アクセス全拒否）かテストモード（アクセス全許可）のどちらにするか聞かれます。今回はロックモードにします。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/95da1ca1-14af-7a8f-3714-8d1099a10a2d.png)


これで初期化完了です

## 動作確認
1. `npm run start`を実行し、reactのローカルデバッグ起動(webpack dev server)を試す
  * ブラウザが起動してこのような画面が出れば成功です。　　　　　　　　　　　　
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/4f6d9ca6-4629-a0f4-d946-ec88dd8e24fe.png)
  * 試しに`src/App.js`の`Edit <code>src/App.js</code> and save to reload.`あたりを編集・保存すると、リアルタイムでブラウザの内容も変わっているのが確認できます
1. `npm run build`を実行し、コンパイルしてみます。
1. `firebase serve`を実行し、firebaseのローカルデバッグサーバーを起動する
  * firebase functionsの動作確認をするため、`functions/index.js`を編集します。
  * コメントにあるHelloWorldをそのまま使います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/1147d20b-fde8-d58f-cb9b-7b5280f2aba3.png)

  * `firebase serve`を実行します  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/c2fb4a72-0c9f-9447-6f86-fcefa089010b.png)
    * functions, hostingのデバッグ用ローカルサーバーが起動します
    * コマンドの結果に出ているURLにアクセスすると結果が表示されます。
      * vscodeの機能で、ctrキーを押しながらURLをクリックすると、ブラウザで開いてくれます。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/e1327135-f512-bb40-0745-f58133f13753.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/18c6085d-9292-9550-8b31-37a0480ebd53.png)
    * 補足事項
      * `firebase serve`の結果で出てくる`http://localhost:5000/`は`build`ディレクトリを見ています。ソースコードを編集してもビルドするまでは反映されません 
      * ローカルのNodejsのバージョンがfirebaseでサポートされてないバージョンだと警告が出ます。きっちりしたい人はfirebaseにそろえるといいと思いますが、今回は面倒なのでそのままにします。
      * firestoreの方は残念ながらローカルのデバッグサーバーがないようです。
          * まだセキュリティルールを試せるローカルエミュレータがある程度です 
            * [参考](https://medium.com/google-cloud-jp/firestore-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%82%92%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F-e435668c18d1) 

1. 終わったらCtr-Cで`firebase serve`や`npm run start`を終了させます

## サーバーへデプロイ
* `firebase deploy`を実行します。完了すると次のような表示が出ます。  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/13aa25f9-3c09-deb1-6639-8e325712a6ab.png)
 
  * 実際にURLにアクセスするとアプリがデプロイされているのが確認できます
    * functionsは`https://us-central1-gallery-tech.cloudfunctions.net/helloWorld`
    * hostingは`https://gallery-tech.firebaseapp.com/`

## まだいろいろ足りていません。
### functionsのFQDNとHostingのFQDNが異なるため、CORSで失敗します
* `fetch("https://us-central1-gallery-tech.cloudfunctions.net/helloWorld")`を実行した例    
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/a5520c2c-27e3-149d-5fd6-d8a54fd5e577.png)
* [functions側でCORS許可する](https://qiita.com/seya/items/0f12bd09c8e856123bc3)方法もありますが、今回はHostingのリライトを使用します

```json:firebase.json
...
"rewrites": [
      {
        "source": "/api/**",
        "function": "api"
      },
      {
        "source": "**",
        "function": "/index.html"
      }
    ]
```

```javascript:functions/index.js
exports.api = functions.https.onRequest((request, response) => {
    response.send(request.path);
});
```

* hostingのFQDNに対し、`/api/`のパスでアクセスしてきたものはfunctionsのapi関数の結果を返すようにします
  * `/api/`配下のパスが何であっても、呼び出されるfunctionsの関数はapi関数です 
* functions側では、`request.path`でどのようなパスでアクセスしてきたかを取得できます。
  * これを用いてapi関数の中で分岐を書けば、パスによって呼び出される関数を変えることができます
  * 例えば、hostingに`/api/helloWorld`でアクセスした場合、functionsの`request.path`は`/api/helloWorld`です 

* これで、クライアント側のjavascriptで`fetch("/api/helloWorld")`とすればfunctionsのapi関数が呼び出せます
  * ブラウザから見ると同一オリジンになるため、CORSのエラーは起きません
 
### webpack dev serverがローカルのfunctionsを呼び出せない
* このままだと、ローカルデバッグでfunctionsとの連携がテストできません
    * webpack dev serverは`http://localhost:3000/`で待ち受けています
    * firebaseのhostingは`http://localhost:5000`で待ち受けています。
        * 上で解説したfunctionsへのリライトをこのポートで行います
    * webpack dev server側で開いたサイトの`fetch("/api/helloWorld")`というコードは`http://localhost:3000/api/helloWorld`を呼び出すのでエラーになります

* そのため、次のproxy設定がpackage.jsonに必要です。（紛らわしい名前ですが、よく企業にあるhttp proxyとは無関係の設定です）

```json:package.json
{
  ...
  "proxy": "http://localhost:5000",
  "scripts": {
  ...
}
```
* `http://localhost:3000/`へのアクセスでファイルがない場合、`http://localhost:5000`に飛ばしてくれます

### firestoreのテストデータはどうするか
* ローカルで開発してるときに本番データにアクセスさせたくないですよね
    * （個人開発なら別に気にしなくてもいいかもしれません） 
* 現状はfirebaseのプロジェクト自体を分けるぐらいしかなさそうです
* そこで、アクセス先のfirestoreを開発(ローカル)と本番で切り替える仕組みが必要になります

##### functionsのアクセス先firestore切り替え
環境設定で切り替えます

* functionsサーバー側の環境設定はfirebase cliで行います
    * 確認コマンド`firebase functions:config:get`  
    * 設定コマンド`firebase functions:config:set service.stage=production"`
      * `service.stage`に`production`を指定します。変数は必ず`.`が1つ必要です
      
* functionsのコード内からは`functions.config().service.stage`で値を取得できます  

* ローカル実行のfunctionsサーバーの環境設定は`functions/.runtimeconfig.json`に書きます
  *  `firebase functions:config:get > functions/.runtimeconfig.json`を実行して、できたファイルを編集します

* 上記の環境設定方法を使って、アクセス先firestoreのアクセス設定を切り替えてやればいいでしょう

##### hostingのアクセス先firestore切り替え
create-react-appの機能を使います。

* .envファイルと`process.env`で切り替えられます。`npm run build`と`npm run start`の時で格納される値を変えられます
* [ここ](https://qiita.com/zgmf_mbfp03/items/008436c5749d65f96e55)に解説があります


実際にアプリを書いていく解説は次回からします