docker-composeでfirebase functionsのローカル実行を試してみたメモ

## 概要

* functionsをローカル実行したかった。
* [[Firebase] Cloud Functionsで消耗したくない人のために、開発環境のベストプラクティスをまとめていったらDockerに行き着いた話（随時更新）][*1]がdockerでfirebase functionsを使うサンプルをあげてくれているので、docker-composeで行った。
* package.jsonが2重管理になってしまっているので、解決法が知りたい。知ってる方いたらコメントで教えていただけると。。。

## 環境
* Windows10
* virtualbox 6.0.4
* vagrant 2.2.4
* Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)
* Docker version 18.09.2, build 6247962
* docker-compose version 1.23.2, build 1110ad01

## ファイル構成

```yml
- bin
  - firebase
    - serve-functions-local.sh # 実行用のコマンドを書いたシェル
- docker
  - docker-compose.yml
  - firebase
    - Dockerfile
    - functions
      - package.json # backendフォルダと同じもの。2重管理やめたい。。
- backend
  - functions
    - tsconfig.json
    - tslint.json
    - package.json # dockerフォルダと同じもの。2重管理やめたい。。
    - src
      - index.ts
- app
  - firebase.json
```

## ファイル

### docker関係の設定

```yml:docker/docker-compose.yml
version: '3'
services:
  garden_firebase:
    build: ./firebase
    volumes:
      - ../backend/functions:/app/functions
      - /app/functions/node_modules # コンテナ内のnode_modulesを使用
      - ../app/.firebaserc:/app/.firebaserc
      - ../app/firebase.json:/app/firebase.json
    ports:
      - 5000:5000
      - 9005:9005
```

`functions: Cannot start emulator. Error: Cannot find module '@google-cloud/functions-emulator/src/config`とでたので、パッケージをグローバルに１つ追加している。

```docker:docker/firebase/Dockerfile
FROM node:8.15.1-alpine 

# コンテナ上の作業ディレクトリ作成
WORKDIR /app

# 後で確認出来るようにpackage.jsonを作成
RUN npm init -y

# firebase
RUN yarn global add firebase-tools
RUN yarn add --dev firebase-tools

RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"firebase\": \"firebase\", /g" /app/package.json

# firebase function 
COPY functions /app/functions

# functions用のモジュールをインストールしておく。
RUN cd /app/functions && NODE_ENV=development npm install -y

# ローカルでfunctionを実行するために必要
RUN yarn global add @google-cloud/functions-emulator --ignore-engines
```

### firebase functionsの設定

以下のpackage.jsonは2重管理となってしまっている。ポイントは`firebase serve --only functions -p 5000 -o 0.0.0.0`でホストをグローバルにしているところ。コンテナ外からアクセスするために必要。

```json:docker/firebase/functions/package.json
{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions -p 5000 -o 0.0.0.0",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "firebase-admin": "~7.0.0",
    "firebase-functions": "^2.2.1"
  },
  "devDependencies": {
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  },
  "engines": {
    "node": "8"
  },
  "private": true
}
```

```json:/app/firebase.json
{
  "functions": {
    "source": "functions",
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  }
}
```

### functionsのソース

```ts:/backend/functions/src/index.ts

import * as functions from 'firebase-functions';
export const hello = functions.https.onRequest((request, response) => {
  response.status(200).send("Hello World")
});
```

### 実行コマンド

```bash:bin/firebase/local-functions-serve.sh
#!/bin/bash

# このシェルスクリプトのディレクトリの絶対パスを取得。
bin_dir=$(cd $(dirname $0) && pwd)

# docker-composeの起動
cd $bin_dir/../../docker && docker-compose run -p 9005:9005 -p 5000:5000 garden_firebase sh -c 'cd /app/functions && npm run serve'
```

## 実行

`./bin/firebase/serve-functions-local.sh`で実行。
画面に以下のように出てきたら成功。

```console
✔  functions: hello: http://0.0.0.0:5000/{ここはそれぞれ違います}/us-central1/hello
```

vagrantで起動しているならば、そこのIPに`http://{IPアドレス}:5000/{ここはそれぞれ違います}/us-central1/hello`とアクセスすればHello Worldが表示されるはず。


[functions設定時点](https://github.com/hibohiboo/garden/tree/dddb56e6e5a5950605d839fc3a0dc2df7a2d5715)  

## 参考

[[Firebase] Cloud Functionsで消耗したくない人のために、開発環境のベストプラクティスをまとめていったらDockerに行き着いた話（随時更新）][*1]
[ローカルでのファンクションの実行][*2]
[firebaseローカルサーバ起動時にfunctions: Cannot start emulator. Error: Cannot find module '@google-cloud/functions-emulator/src/configと返された。][*3]
[Firebase Cloud Function をローカルで実行するときにはまった点][*4]
[Cloud Functions for Firebaseが最高だった話][*5]
[DockerでのNodeアプリ構築で学んだこと][*6]
[firebase-functions-test を使って Firestore の offline テストを書いてみる][*10]
[Cloud Functions for Firebaseを楽々デバッグするには][*11]

[*1]:https://qiita.com/pannpers/items/244a7e3c18d8c8422e4f
[*2]:https://firebase.google.com/docs/functions/local-emulator
[*3]:https://qiita.com/muuuuminn/items/6aeb235df23a181b024c
[*4]:https://crieit.net/posts/Firebase-Cloud-Function
[*5]:https://qiita.com/HALU5071/items/e43729ac5b06b0506fbe
[*6]:https://postd.cc/lessons-building-node-app-docker/

[*10]:https://qiita.com/star__hoshi/items/f904fce72c52fdeb9334
[*11]:https://qiita.com/seya/items/c37207cd65ec914692ba