見出し画像

Firebase Emulator Suiteをつかってみる

こんにちは。
本日はFirebase Emulator Suiteをつかってみたいと思います。
※v9はまだ不安定なため、今回はv8を使用して再現しています。ご注意ください。

1.Firebase Emulator Suiteとは?

Firebase Local Emulator Suite は、Cloud Firestore、Realtime Database、Cloud Storage、Authentication、Cloud Functions、Pub/Sub、Firebase Hosting を使用してアプリをローカルでビルドおよびテストするデベロッパー向けの高度なツールセットです。機能が豊富なユーザー インターフェースを備えており、アプリの本稼働やプロトタイピングにかかる時間を短縮できます。
Local Emulator Suite を使用したローカル開発は、プロトタイピング、開発、継続的インテグレーションのワークフローに適しています。

https://firebase.google.com/docs/emulator-suite?hl=ja

Firebaseは、Emulatorが導入されるまではローカル開発環境がなく、都度Firebase上に開発環境用のプロジェクトを構築する必要がありました(手間がかかりました)。
しかし、Emulatorの登場により、FirestoreやFunctionsなどを各自の開発環境で持つことができるようになりました。

それでは早速始めていきましょう。

2初期設定をする

// Firebase自体の初期化
$ firebase init

// Emulator Suiteの初期化
$ firebase init emulators

対話形式で初期化を進めていきましょう。
Emulatorで使用する要素に関して質問がありますので、選択して設定を完了へと進んでいきます。

設定が完了すると、各ファイルが生成されます。

// firebase.json

{
 "firestore": {
   "rules": "firestore.rules.json",
   "indexes": "firestore.indexes.json"
 },
 "functions": {
   "predeploy": [
     "npm --prefix \"$RESOURCE_DIR\" run lint",
     "npm --prefix \"$RESOURCE_DIR\" run build"
   ]
 },
 "emulators": {
   "auth": {
     "port": 9099,
     "host": "localhost"
   },
   "firestore": {
     "port": 8080,
     "host": "localhost"
   },
   "functions": {
     "port": 5051,
     "host": "localhost"
   },
   "ui": {
     "enabled": true, // trueにすることでlocalhost:4000でEmulateのページをひらくことができます
     "port": 4000,
     "host": "localhost"
   }
 }
}

このままでも起動はできますが、実際のコード上から各Emulatorのポートへ接続して使用できるように設定していきます。

3.Emulatorに繋いでみる

では、実際に接続してみましょう。

// index.ts

import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import 'firebase/compat/functions';

//----------------------------------------
// config
//----------------------------------------
const config = {
 apiKey: process.env.NEXT_PUBLIC_DEV_API_KEY,
 authDomain: process.env.NEXT_PUBLIC_DEV_AUTH_DOMAIN,
 projectId: process.env.NEXT_PUBLIC_DEV_PROJECT_ID,
 storageBucket: process.env.NEXT_PUBLIC_DEV_STORAGE_BUCKET,
 messagingSenderId: process.env.NEXT_PUBLIC_DEV_MESSAGING_SENDER_ID,
 appId: process.env.NEXT_PUBLIC_DEV_APP_ID
};

//----------------------------------------
// initialize firebase
//----------------------------------------
if (!firebase.apps.length) {
 firebase.initializeApp(process.env.NODE_ENV === 'development' ? configDev : configProd);
}

const isEmulator = window.location.hostname === 'localhost'
const db = firebase.firestore();
const functions = firebase.app().functions(isEmulator ? 'us-central1' : 'asia-northeast1'); // us-central1じゃないとcallable functionが動かない
const auth = firebase.auth();

// localhostの場合はEmulatorにつなぐ
if(isEmulator) {
 db.useEmulator("localhost", 8080);
 functions.useEmulator("localhost", 5051);
 auth.useEmulator('http://localhost:9099/');
}

export { db, functions, auth };

Emulatorを起動している状態かつlocalhostの場合は、Emulatorに接続することができます。
それでは、早速Emulatorを起動してみましょう。

$ firebase emulators:start

Emulatorの画面には、localhost:4000にアクセスすることで、AuthenticationやFirestore、Logs(Functions)などを確認することができます。

画像1

この状況で実際に動作確認をしてみたいと思います。

● フロントの画面からボタンをクリックしてデータをFirestoreに保存します。
● 保存したFirestoreデータをトリガーにして、別のコレクションにデータをコピーします。
● フロントの画面からボタンをクリックして、Callable Functionを起動します。
● Callable FunctionからFirestoreにデータを保存します。

4.フロントの画面からボタンをクリックしてデータをFirestoreに保存。

// pages/index.tsx

const Home: NextPage = () => {

 const onSaveHandler = async () => {
   const id = 'a7d5dd1e-1f7d-47e7-8be2-314150e5676f';
   const datas = {
     id: id,
     name: 'yamada taro',
     age: 38,
     email: 'example@example.com'
   };
   await db.doc(`users/${id}`).set(datas);
 };

 return (
   <div>
     <h1>サンプル</h1>
     <button onClick={() => onSaveHandler()}>
       保存します
     </button>
   </div>
 );
};

export default Home;

5.保存したFirestoreデータをトリガーにしてデータを別コレクションへコピー。

// functions/sample.tsx

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
const firestore = admin.firestore();

module.exports = functions.region("asia-northeast1").firestore.document('users/{userId}').onWrite(async (change, context) => {
 
 console.log(context.params.userId);
 
 const userId = context.params.userId as string;
 await firestore.doc(`users_copy/${userId}`).set({
   id: userId,
   name: context.params.name,
   age: context.params.age,
   email: context.params.email
 });
});

画面からボタンをクリックしました。
その結果、usersコレクションにデータが投入され、データの投入がトリガーとなり、users_copyコレクションにデータがコピーされました。

スクリーンショット 2022-01-30 22.03.45
スクリーンショット 2022-01-30 22.04.01

次に、Functionsの実行ログを確認してみましょう。

画像4

idがログに表示されていますね。
これでEmulatorを使用してローカル開発環境を構築することができました。
次に、Emulator上でCallable Functionを実行してみましょう。

6.フロントの画面からボタンをクリックしてCallable Functionを起動。

// pages/index.tsx

const Home: NextPage = () => {

const onSaveHandler = async () => {
  const id = 'a7d5dd1e-1f7d-47e7-8be2-314150e5676f';
  const datas = {
    id: id,
    name: 'yamada taro',
    age: 38,
    email: 'example@example.com'
  };
  await db.doc(`users/${id}`).set(datas);
};

// ここから追加
const onCallSaveHandler = async () => {
   const onCallSample = functions.httpsCallable(isMockFunctionsPath('sample'));
   await onCallSample({ data: {} }).catch((e) => {
     console.log(e);
     alert(e);
   });
};
// ここまで追加

return (
  <div>
    <h1>サンプル</h1>
    <button onClick={() => onSaveHandler()}>
      保存します
    </button>
    
    { /* ここから追加 */}
    <button style={{ backgroundColor: 'blue' }} onClick={() => onCallSaveHandler()}>
       onCall発火
    </button>
    { /* ここまで追加 */}
  </div>
);
};

export default Home;
// util/index.ts

//----------------------------------------
// localhostの場合mock関数の文字列を付与して返す
//----------------------------------------
export const isMockFunctionsPath = (path: string) => {
 const isEmulator = window.location.hostname === 'localhost';
 if(isEmulator) {
   return `mock${path}`;
 }
 return path;
}

フロント画面に新たにonCallイベントでFunctionsを起動するイベントを設定しています。

7.Callable FunctionからFirestoreにデータを保存。

// functions/mock/call.tsx
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
const firestore = admin.firestore();
module.exports = functions.region('us-central1').https.onCall((data, context) => {
console.log(context);
console.log(data);
firestore
  .doc('mock/123456789')
  .set({
    id: '123456789',
    body: 'onCallイベントです'
  })
  .catch((e) => {
    console.log(e);
  });
return 'hello onCall';
});

onCallしているCallable Functionsですが、Functions内からmockコレクションにデータを保存しているだけのFunctionsです。

7-1.isMockFunctionsPath()関数

localhostの場合、エミュレータでonCallを実行するためには、regionが`us-central1`である必要があります。
そのため、mock用の関数を作成し、isMockFunctionsPath()関数でmock用の関数を呼び出すために、関数名に'mock'という文字列を追加しています。
これにより、コード上でmock用の関数を呼び出すことができます。

7-2.Callable Functionsが「call」の場合

● localhostの場合のFunctions名 → mockcall
● localhost以外の場合のFunctions名 → call

regionを切り分ける上での綺麗なやり方が知りたい。。。
それでは。

この記事が気に入ったらサポートをしてみませんか?