📝

Firebase Javascript SDKv7をv9(modular方式)へ変更した記録

2022/07/15に公開
2

今回はJavaScriptでの記述、パッケージマネージャーはnpmとなります。
サービスごと、関数ごとに(自分が使っている範囲ですが)変更点を書いてみます。
自分のケースではv7からのアップデートでしたが
v8からもほとんど同じだと思います。

v9の利点

ソースのデータ量が最大で80%軽くなるとのことです。
これが実現できるのはJavaScriptのツリーシェイキングを利用しているとのことで必要なモジュールのみを使用するようにソースを削ることができるようになるためとのことでした。

compatライブラリについて

v8からv9の以降は段階的に実施できる措置としてcompatライブラリというものが用意されています。

https://firebase.google.com/docs/web/modular-upgrade#about_the_compat_libraries

ただしcompatライブラリを使用している間はv9の利点はほとんどありません。
今回はそこまで大規模ということでもないため一気にモジュラーに変換する記録を書いてみます。

アップグレード作業概要

基本的にv8以前はメソッドチェーンだった書き方が
関数宣言的な書き方に変わります。

個人的に厄介だったのはFirestoreのパス指定,データ追加方法などが変わる部分でした。
ここは独特な変わり方をしていたので慣れるまでは違和感があるかもしれません。

参考

https://zenn.dev/isosa/articles/037ed47ee3dfe5

現状を確認

まずはプロジェクトの現状を確認するところから整理してみます。
yarnで管理していれば以下のコマンドでローカルのnpmライブラリリストが表示されます。

% yarn list --depth=0

npmで管理していれば以下のコマンドでローカルのnpmライブラリリストが表示されます。

% npm ls --depth=0

リストが表示されたらfirebaseというライブラリの@以降のバージョンを確認しましょう。ちなみに私の環境では7.15.5でした。

sdkをv9に変更

まずはfirebaseコアライブラリの最新版をインストールします。
以下はローカル環境のプロジェクトディレクトリで実行しています。

% npm i firebase@9.8.3

これ以降はサービスごと、機能ごとに自分が触ったことを書いています。
ページ内検索でつまみ読み&公式と見比べるなどすると良いかもしれません。

initializeApp関数(初期化コード)の変更

初期化部分を担うところを変更します。
個人的には今回のように一気にモジュラーに変更する場合
この変更は最初にやらないとテストができないので最初に書いています。

【v7】

import firebase from 'firebase/app'
firebase.initializeApp(config.js);

【v9】

import { initializeApp } from "firebase/app"
const firebaseApp = initializeApp(config.js);

Firebase Authenticationのリファクタリング|認証関連

自分の環境では以下のようにコードを変更しました。

【v7】

import 'firebase/auth'
export const auth = firebase.auth();

auth.onAuthStateChanged(user => { 
  // Check for user status
});

【v9】

import { getAuth, onAuthStateChanged } from "firebase/auth";
export const auth = getAuth(firebaseApp);

onAuthStateChanged(auth, user => {
  // Check for user status
});

パスワードリセットの機能

パスワードリセットをEmail経由で実行する関数のリファクタリングです。

【v7】

firebase.auth().sendPasswordResetEmail(email)
  .then(() => {
    // Password reset email sent!
    // ..
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ..
  });

【v9】

import { getAuth, sendPasswordResetEmail } from "firebase/auth";

const auth = getAuth();
sendPasswordResetEmail(auth, email)
  .then(() => {
    // Password reset email sent!
    // ..
  })
  .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    // ..
  });

参考URL

https://firebase.google.com/docs/auth/web/manage-users?hl=ja#send_a_password_reset_email

sendEmailVerification|ユーザーにEメールを送信し認証する

自分のケースではユーザー登録時に自分のドメインに飛ばす設定をしていたので
sendSignInLinkToEmailという関数を使う形に変更しました。

【v7】

auth.currentUser.sendEmailVerification(actionCodeSettings)

【v9】

import { getAuth, sendSignInLinkToEmail } from "firebase/auth";

const auth = getAuth();
sendSignInLinkToEmail(auth, email, actionCodeSettings)
    .then(() => {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
        // ...
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        // ...
    });

参考

https://firebase.google.com/docs/auth/web/manage-users?hl=ja#send_a_user_a_verification_email

https://firebase.google.com/docs/auth/web/email-link-auth?hl=ja

https://firebase.google.com/docs/auth/web/passing-state-in-email-actions?hl=ja#passing_statecontinue_url_in_email_actions

サインイン関連関数のリファクタリング

【v7】

auth.signInWithEmailAndPassword(email, password)
auth.isSignInWithEmailLink(window.location.href)
auth.signInWithEmailLink(email, window.location.href)
auth.applyActionCode(actionCode)
auth.verifyPasswordResetCode(state.actionCode)
auth.confirmPasswordReset(state.actionCode, newPassword)
auth.signInWithEmailAndPassword(email, newPassword)

【v9】

signInWithEmailAndPassword(auth, email, password)
isSignInWithEmailLink(auth, window.location.href)
signInWithEmailLink(auth, email, window.location.href)
applyActionCode(auth, actionCode)
verifyPasswordResetCode(auth, state.actionCode)
confirmPasswordReset(auth, state.actionCode, newPassword)
signInWithEmailAndPassword(auth, email, newPassword)

参考

https://firebase.google.com/docs/auth/web/password-auth?hl=ja#before_you_begin

CloudFirestore関数のリファクタリング

【v7】

import 'firebase/firestore'
export const db = firebase.firestore();

return db.collection('users').doc(userId).set(userInitialData).then();

【v9】

import { getFirestore, collection, query, where, getDocs } from "firebase/firestore";
export const db = getFirestore(firebaseApp);

import {collection} from "firebase/firestore";
const q = query(collection(db, "users"));
const querySnapshot = await getDocs(q);
return querySnapshot.doc(uid).set(userInitialData).then();

batch処理でのデータ書き込み

【v7】

const batch = db.batch();
batch.commit().then(() => {})

【v9】

const batch = writeBatch(db);
batch.commit().then(() => {})

備考:ちなみに既存のデータを読み込んで計算して書き込むといった読み込みが必要な場合はrunTransactionを使用する。書き込みオンリーの場合はwriteBatchを使用。

firestoreにデータを加える|doc,set関数などの使い方

firestoreのデータベース内の特定のコレクション、ドキュメント内にデータを加える処理も書き方が変わりました。

【v7】

db.collection("cities").doc("LA").set({
    name: "Los Angeles"
})

【v9】

import { doc, setDoc } from "firebase/firestore";

// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
  name: "Los Angeles"
});

参考

https://firebase.google.com/docs/firestore/manage-data/add-data#web-version-9

サブコレクションへのデータ追加

以下の記事を参考にしてください。

参考

https://zenn.dev/isosa/articles/037ed47ee3dfe5

functions関連のリファクタリング

firebase functions関連の呼び出し方もアップデートします。

【v7】

import 'firebase/functions'
export const functions = firebase.functions();

【v9】

import { getFunctions } from 'firebase/functions';
export const functions = getFunctions(firebaseApp);

参考ページ

https://firebase.google.com/docs/functions/callable?hl=ja#web-version-9

functions呼び出し側|httpsCallableの書き方

httpsCallableの書き方も変わったようなので変更します。
ちなみに自分の環境ではfunctions自体に登録している関数
(GCPにアップロードしているfunctionsの本体)は特に変更しなくても動作していました。
このあたり色々調べながらやっているとややこしくて混乱しがちですので一応メモしておきます。
以下はアプリ側でfirebase functionsの関数を呼び出しているところのものです。

【v7】

var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    var sanitizedMessage = result.data.text;
  });callable.js

【v9】

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const sanitizedMessage = data.text;
  });

storage関連のリファクタリング

firebase storage関連の呼び出し方もアップデートします。

【v7】

import 'firebase/storage';
export const storage = firebase.storage();

【v9】

import { getStorage } from "firebase/storage";
export const storage = getStorage(firebaseApp);

参考ページ

https://firebase.google.com/docs/storage/web/start#add-bucket-url

blobかfileからファイルをアップロードする

【v7】

pathReference.put(file);

【v9】

import { getStorage, ref, uploadBytesResumable } from "firebase/storage";

const storage = getStorage();
const storageRef = ref(storage, 'some-child');

uploadBytesResumable(storageRef, file);

firestore timestampのリファクタリング

【v7】

import 'firebase/firestore'
export const FirebaseTimestamp = firebase.firestore.Timestamp;

【v9】

import { doc, updateDoc, serverTimestamp } from "firebase/firestore";

const docRef = doc(db, 'objects', 'some-id');

const updateTimestamp = await updateDoc(docRef, {
    timestamp: serverTimestamp()
});

参考ページ

https://cloud.google.com/firestore/docs/manage-data/add-data#server_timestamp

エラー

一通り変更した後にエラーが出ました。

TypeError: F.storage.ref is not a function

これはそのままstorage.ref関数の書き方が変わったというものでした。

【v7】

import 'firebase/storage';
export const storage = firebase.storage();

const exampleFile = storage.ref().child('examplefile');

【v9】

import { getStorage } from "firebase/storage";
// Get a reference to the storage service, which is used to create references in your storage bucket
export const storage = getStorage();

import { ref } from "firebase/storage";
const exampleFile = ref(storage, 'examplefile');

参考ページ

https://firebase.google.com/docs/storage/web/create-reference

ReferenceError: createUserWithEmailAndPassword is not defined

firebaseのユーザーをメールとパスワードで生成する関数のところでエラーが出ました。
こちらも同様に関数を呼び出して使用する形に変更します。

【v7】

import 'firebase/auth'
export const auth = firebase.auth();

auth.createUserWithEmailAndPassword(email, password)

【v9】

import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
export const auth = getAuth(firebaseApp);
export const createUserWithEmailAndPasswordInVariable = createUserWithEmailAndPassword;

createUserWithEmailAndPasswordInVariable(auth, email, password)

そのまま変更なく使用できたもの

auth.currentUserは変更なく使用することができました。

Functionsの本体は影響なしでした

私のケースではGCPにアップロードしているFirebase Functionsの本体部分で影響を受けるところはありませんでした。

rules関連も影響なしでした

Firestore,storage共にrulesは特に変更することなくアップデートできました。

Extensionsも影響なしでした|Trigger Email

Trigger Email関連の設定も影響はありませんでした。

備考

ドキュメントで関数ごとの情報を探す時、サイト内検索が便利だったので書いておきます。

googleの検索窓に
site:firebase.google.com/docs getDocs
などと入力すると公式リファレンス内から検索ができるので便利です。

これはgoogleにクロールされているドメイン、ページに限定して使用できる機能なので
そもそも検索エンジンを拒否しているページでは機能しませんのでご注意ください。

全体参考ページ

https://firebase.google.com/docs/web/modular-upgrade

https://zenn.dev/mktu/articles/3905b13500ffb6

https://zenn.dev/knaka0209/articles/9472e94612a6f0

わかりづらい部分はコメントいただけたら嬉しいです。

Discussion

standard softwarestandard software

V9のところ、下記ではないでしょうか?

const exampleFile = ref(storage).child('examplefile');const exampleFile = ref(storage, 'examplefile');
isosaisosa

ご指摘ありがとうございます!
修正させていただきました。