Firebase JS SDK v9がバンドルサイズを激減した神アップデートだった話😲

#Overview

みんな大好きFirebaseですが、JSのSDKのファイルサイズが大きいという問題がありました。
ファイルサイズが大きいということは、ページ描画に影響があるため扱いにくい印象でした。  
  
あれから約二年、2021/08/09時点ではまだベータですが、導入したらバンドルサイズが激減したのでその紹介になります。
v9はコード構成をFunctionベースに変えるBreaking Changeのため、それなりのコード修正が必要になります。  

本件は公式ブログでも記事がありますので、最初に読むといいと思います。
https://firebase.googleblog.com/2021/07/introducing-the-new-firebase-js-sdk.html


#Target reader

- Firebase JS SDK v8のユーザー
- FirebaseのAuthやFirestoreのサイズが大きいのが気になっていた方

#Prerequisite

- Firebase JSを理解している
- 実行環境はNode V14系

#Body

## どのようにしてFirebaseはファイルサイズを削減したのか？

これはググってもらたほうがいいですが、`Tree Shaking`というテクニックで聞いたことある方も少なくないと思います。
簡単に言えば、未使用コードを除外してファイルサイズを小さくする技術です。
`Tree Shaking`において重要なのは、**未使用コードをわかりやすくする**ということです。
  
Firebaseはクラスベースの構成でしたが、これをFunctionベースにBreaking Changeすることで対応しています。
以下にv8とv9のFirestoreのドキュメント追加コードを掲載します。

```javascript:v8.js
db.collection("cities").add({
    name: "Tokyo",
    country: "Japan"
})
.then((docRef) => {
    console.log("Document written with ID: ", docRef.id);
});
```

```javascript:v9.js
import { collection, addDoc } from "firebase/firestore";

const docRef = await addDoc(collection(db, "cities"), {
  name: "Tokyo",
  country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
```

> https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja#web-v9_6

v8はクラスベースのため、dbからcollectionにアクセスし、collectionのメソッドで追加しています。
v9はFunctionベースのため、`addDoc`をライブラリからインポートし、引数としてcollectionをしています。
v9の変更はFunctionをパッケージからimportして、引数に対象のデータを入れるStyleになるので押さえておきましょう。

どうしてFunctionベースにする必要があるのか？簡単に言えば、未使用コードと分離が容易だからです。
v8の場合、`collection`が持つメソッドが10個あれば、未使用でもそれらをインポートしてしまいます。
しかし、v9の`addDoc`をインポートするスタイルでは、残り9個の未使用メソッドは削減対象にできるという理屈です。

## 具体的な修正内容

公式ブログからリンクされているアップグレード手順がベースになります。
https://firebase.google.com/docs/web/modular-upgrade?hl=ja

ただし、Breaking Changeになるため、この内容では圧倒的に参考コードが足りません。
そこで、お勧めしたいのでは、各種公式ドキュメントのコードです。
以下はFirestoreのデータ追加のページですが、`Web v9`としてv9のコードがサンプルにあるので頼りになります。

https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja#web-v9_6

ここにないものは自分でちょっとググるか頑張る必要があります。

### Storage編

Storageはまだv9のサンプルコードがないため、SDKのTS見ながら作りました。

ちょっと驚いたのはrefがそのままの名前でエクスポートされている点ですね。
インポートする際に名前変えるのもありかもしれないです。

```javascript:v8.js
    const url = await storage.ref("http://example.com/hoge.json").getDownloadURL();
```

```javascript:v8.js
    import { getStorage, ref, getDownloadURL } from "firebase/storage"
    import firebaseApp from "./firebase"

    const storage = getStorage(firebaseApp)

    const url = await getDownloadURL(ref(storage, "http://example.com/hoge.json"))
```

### Firestore編

Firestoreは公式にサンプルありますが、最初知らずに頑張りました😅
特徴的なのはv8ではChainでつないでいましたが、v9では`QueryConstraint[]`になっているので、ひたすら条件追加して最後に展開しています。
この辺が機械的な変更から、少し形を変えた変更になっている部分ですかね。

```javascript:v8.js
const getDocumentsFromStore = async (collectionName, tokens, limit, startDocument) => {
    let query = db.collection(collectionName);

    // AND条件連結
    tokens.forEach(v => {
        query = query.where(`token.${v.key}`, '==', true);
    });

    // 前回クエリーの続き
    if (startDocument) {
        query = query.startAfter(startDocument);
    }

    const snapshot = await query
        .limit(limit) // 一回の結果数制限
        .get();

    return snapshot
}
```

```javascript:v9.js
import {
    getFirestore, collection, query, where, getDocs, setDoc, addDoc, doc, startAfter, limit
} from "firebase/firestore/lite" // v9で登場したFirestoreの計量版
import { initializeApp } from "firebase/app"
import config from '../configs/firebase' // 個人の設定

const firebaseApp = initializeApp(config)
const db = getFirestore(firebaseApp)

const getDocumentsFromStore = async (collectionName, tokens, maxLength, startDocument) => {
    const src = collection(db, collectionName)

    // AND条件連結
    const queryParams = tokens.map(v => {
        return where(`token.${v.key}`, '==', true)
    })
    // 一回の結果数制限
    queryParams.push(limit(maxLength))

    // 前回クエリーの続き
    if (startDocument) {
        queryParams.push(startAfter(startDocument))
    }

    const snapshot = await getDocs(query(src, ...queryParams))

    return snapshot
}
```

## 実際どのくらい減るの？

実際に変更した結果が以下です。

![firebase バンドルサイズ減 2021-08-09 135906.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310578/86779159-1efd-2a55-dcf7-f0402d15f2ea.png)

…ビルドによってチャンクファイルのインデックスが変わるのでわかりにくいですね😅
KBレベルのものを計算すると…42KB削減になります！！！

```
63.92 - 31.87 - 6.24 - 68.16 = -42.35KB
```

今回削減した中で大きいのは、Authの31.87KBで、Firestoreは68.16から64.3KBとほぼ変わらず😢
Firestoreはもともと`firebase/firestore/memory`というオフライン未対応版を使っていたためです。
フル機能版は90KB強のため、結構削減してます！！！


…ご不満ですか？バンドルサイズ削減って大変なんです！
ご不満ですか？？？そんな方向けに神FirebaseはFirestore向けに`firebase/firestore/lite`というパッケージを用意しています。（ブログにセクションあり）
基本的なCRUDをサポートしたこいつを適用したらどうなったか？

![firestorelite 2021-08-09 155753.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310578/99da9578-30e6-a980-2c48-c05e1b0f4aa9.png)

64KB ＝＞ 19KBと約45KB削減されました🤣🤣🤣
そう、v9の隠し玉は`firebase/firestore/lite`でした。
ただクエリーするためだけに90KBのファイルをバンドルしていたのが、最新のv9では20KB以下のバンドルサイズ激減したんです！！！


#Conclusion

これを読んでしまったら、すぐにでもv9に移行したくなったんじゃないでしょうか？
v9はバンドルサイズの神アップデートなので、可能なら優先度高めで対応していいやつです。
ただし、まだベータですし、Breaking Changeなので、はやる気持ちをさえつつ十分にテストを行ったうえで移行しましょう。
段階的に移行するため、まずは構文のみを移行するためのcompatライブラリもあるようなので、そちらも確認しておきましょう。

v8までのクラスベースの記述は、あと数年で終わります。