Vue.jsでFirebase Functionsを使う

と思ったときの個人用メモです。とりあえずFunctionsの動作を確かめたい場合の必要最低限です。Vue.jsのバージョンは2.xで事前にプロジェクトは作成済み、インストールする``firebase``のバージョンは9.xの体で書いています。

#開発環境

* node.js : 14.18.3
* vue-cli : 4.5.10
* Firebase CLI : 9.16.5

#Firebaseプロジェクトの作成+α

1. コンソールからプロジェクトの作成
1. アプリを追加→ウェブからアプリを追加しSDKを取得
1. Functionsを使うので、料金プランをBlazeに変更

#VueプロジェクトのFirebaseのセットアップ

Vue CLI UIが相も変わらず便利なので、こちらから``firebase``と後で行うHTTPリクエストに備えて``axios``をインストールします(``fetch``も併記しておくので``axios``は必要に応じて)。そうでない場合は``npm``なり``yarn``なりでインストールします。
インストールが終わったら、(必要であれば)ルートディレクトリに``.env.local``を、``src``ディレクトリ直下に``plugins``ディレクトリを作成し``firebase.js``を作成、それぞれ以下のようにします。

```..env.local
VUE_APP_API_KEY="<MY-API-KEY>"
VUE_APP_AUTH_DOMAIN="<MY-AUTH-DOMAIN>"
VUE_APP_PROJECT_ID="<MY-PROJECT-ID>"
VUE_APP_STORAGE_BUCKET="<MY-STORAGE-BUCKET>"
VUE_APP_MESSAGING_SENDER_ID="<MY-MESSAGING_SENDER_ID>"
VUE_APP_APP_ID="<MY_APP_ID>"
```

```javascript:src/plugins/firebase.js
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
import { getFunctions } from 'firebase/functions';
import { getAnalytics } from 'firebase/analytics';

const firebaseConfig = {
  apiKey: process.env.VUE_APP_API_KEY,
  authDomain: process.env.VUE_APP_AUTH_DOMAIN,
  projectId: process.env.VUE_APP_PROJECT_ID,
  storageBucket: process.env.VUE_APP_STORAGE_BUCKET,
  messagingSenderId: process.env.VUE_APP_MESSAGING_SENDER_ID,
  appId: process.env.VUE_APP_APP_ID,
  measurementId: process.env.VUE_APP_MEASUREMENT_ID,
};

const app = initializeApp(firebaseConfig);

const firestore = getFirestore(app);
const storage = getStorage(app);
const functions = getFunctions(app);
functions.region = 'asia-northeast1'; // regionの変更
const analytics = getAnalytics(app);

export { firestore, storage, functions, analytics };
```

今回はFunctionsしか使いませんが、折角なので一通り記述しておきました。Analyticsを使う場合は``main.js``に下記を追記。

```javascript:src/main.js
import Vue from 'vue';
import App from './App.vue';
import { analytics } from './plugins/firebase'; // 追記

Vue.config.productionTip = false;

new Vue({
  analytics, // 追記
  render: (h) => h(App),
}).$mount('#app');
```

#Firebaseプロジェクトの初期化

を行います。Firebase CLIにまだログインしていない場合は``firebase login``でログインします。Functionsのセットアップをしましょう。

```
cd VUE-PROJECT
firebase init

... なんやかんや
```

初期化が終わったら、ルートディレクトリに``functions``ディレクトリが作成されているので、``index.js``をコメントアウトしておきます。

#Functionsの動作確認

をしてみます。めっちゃ便利なエミュレータがあるので、こちらで動作確認をしてみましょう。

```
firebase emulators:start --only functions
```

そうしたらhelloWorldのエンドポイントが出てくるのでアクセスしてみましょう。``http://localhost:5001/MY-PROJECT/us-central1/helloWorld``みたいな感じになってると思います。Hello from Firebase !が確認できればおｋです。
確認出来たらレスポンスの内容を適当に書き換えてみましょう。もう一度アクセスすると変更されているはずです。これで開発が捗りますね！めっちゃ便利！

#Functionsの環境変数の設定

が必要であれば以下のように設定します。Functionsのエミュレータでも使用する場合は、``.runtimeconfig.json``を生成しましょう(上から順に、環境変数の設定、設定の確認、JSONファイルの生成)。

```
firebase functions:config:set <projectID>.<key>=<value>
firebase functions:config:get
firebase functions:config:get > .runtimeconfig.json
```

次いで設定した環境変数をFunctionsで使用する場合は以下を追記。

```javascript:functions/index.js
const config = functions.config()
const env = config["projectID"]
```

``env.key``にアクセスして``value``が返ってくれば設定完了です。

#VueプロジェクトでFunctionsの動作確認

をします。``index.js``を以下のようにします。~~とりあえず動作確認したいだけなのでCORS対応はめんｄ~~あくまで動作確認なので、本番環境ではちゃんとしましょう。

```javascript:functions/index.js
const functions = require('firebase-functions');

exports.helloWorld = functions
  .region('asia-northeast1')
  .https.onRequest((request, response) => {
    functions.logger.info('Hello logs!', { structuredData: true }); 
    response.set('Access-Control-Allow-Headers', '*');
    response.set('Access-Control-Allow-Origin', '*');
    response.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST');
    response.send('Hello from Firebase !');
  });

exports.helloVue = functions
  .region('asia-northeast1')
  .https.onCall((data, context) => {
    const name = data.name;
    return `Hello ${name} from Vue !`;
  });
```

続いてVueのサンプルコードです。

```vuejs:App.vue
<template>
  <div id="app">
    <button @click="onRequest">on request</button>
    <p>{{ result.request }}</p>

    <input v-model="name" type="text" />
    <button @click="onCall">on call</button>
    <p>{{ result.call }}</p>
  </div>
</template>

<script>
import axios from 'axios';

import { functions } from '@/plugins/firebase';
import { httpsCallable, connectFunctionsEmulator } from 'firebase/functions';
// エミュレータを使用する
if (process.env.NODE_ENV === 'development') {
  connectFunctionsEmulator(functions, 'localhost', 5001);
}

export default {
  name: 'App',
  data: () => ({
    requestUrl: 'http://localhost:5001/MY-PROJECT/asia-northeast1/helloWorld', // HTTPリクエストのエンドポイント
    result: {
      request: '',
      call: '',
    },
    name: 'Taro',
  }),
  methods: {
    async onRequest() {
      const res = await axios.get(this.requestUrl);
      this.result.request = res.data;

      // fetch
      /*
      fetch(this.requestUrl)
        .then((res) => res.text())
        .then((data) => (this.result.request = data));
      */
    },

    async onCall() {
      const helloVue = httpsCallable(functions, 'helloVue');
      const data = await helloVue({ name: this.name });
      this.result.call = data.data;
    },
  },
};
</script>
```

ボタンを押して、無事レスポンスなり何なりが返ってくれば成功です！

#最後に

以下の記事を参考にさせていただきました。感謝！

[ドキュメント | Firebase - Google]
(https://firebase.google.com/docs?hl=ja)
[Firebase Javascript SDK v8→v9における進め方と注意事項]
(https://zenn.dev/mktu/articles/3905b13500ffb6)
[Firebase for Cloud Functionsで環境変数を使用する]
(https://zenn.dev/yonajs/articles/1318abb80680cd)