1. 始める前に
モジュラー型の Firebase JS SDK は、既存の JS SDK を書き換えたものであり、次のメジャー バージョンとしてリリースされる予定です。これにより、Firebase JS SDK から使用されていないコードを除外して、より小さなバンドルを作成してパフォーマンスを向上させることができます。
モジュラー JS SDK の最も大きな違いは、すべてを含む単一の firebase
名前空間ではなく、インポートする無料のフローティング関数に機能が編成されていることです。コードの編成のこの新しい方法により、ツリー シェイキングが可能になります。ここでは、現在 v8 Firebase JS SDK を使用しているアプリを、新しいモジュラー SDK にアップグレードする方法について説明します。
アップグレードをスムーズに行うために、互換性パッケージのセットが提供されています。この Codelab では、互換性パッケージを使用してアプリを 1 つずつ移植する方法を学びます。
作成するアプリの概要
この Codelab では、v8 JS SDK を使用する既存のストック ウォッチリスト ウェブアプリを、次の 3 段階で新しいモジュラー JS SDK に段階的に移行します。
- 互換性パッケージを使用するようにアプリをアップグレードしてください
- アプリを互換性パッケージからモジュラー API に 1 つずつアップグレードする
- Firestore SDK の軽量実装である Firestore Lite を使用して、アプリのパフォーマンスをさらに改善します。
この Codelab では、Firebase SDK のアップグレードに重点を置いています。その他のコンセプトとコードブロックについては軽く触れるにとどめ、そのままコピーして貼り付けられるようにしています。
必要なもの
2. セットアップする
コードを取得する
このプロジェクトに必要なすべてのものは Git リポジトリにあります。まず、コードを取得して、お好みの開発環境で開きます。
コマンドラインから、Codelab の GitHub リポジトリのクローンを作成します。
git clone https://github.com/FirebaseExtended/codelab-modular-sdk.git
git がインストールされていない場合は、リポジトリを ZIP ファイルとしてダウンロードし、ダウンロードした ZIP ファイルを解凍できます。
アプリをインポートする
- IDE を使用して、
codelab-modular-sdk
ディレクトリを開くかインポートします。 npm install
を実行して、アプリをローカルでビルドして実行するために必要な依存関係をインストールします。npm run build
を実行してアプリをビルドします。npm run serve
を実行してウェブサーバーを起動する- ブラウザタブを開いて http://localhost:8080 にアクセスします。
3. ベースラインを確立する
出発点の概要
出発点は、この Codelab 用に設計されたストック ウォッチリスト アプリです。この Codelab のコンセプトを説明するために、コードは簡略化されており、エラー処理はほとんど行われていません。このコードを本番環境のアプリで再利用する場合は、エラーに対処してすべてのコードを十分にテストするようにしてください。
アプリですべてが機能することを確認します。
- 右上の [ログイン] ボタンを使用して、匿名でログインします。
- ログインしたら、「NFLX」、「SBUX」を検索して追加しますおよび「T」ウォッチリストに追加するには、[Add] ボタンをクリックし、文字を入力して、下にポップアップ表示される検索結果の行をクリックします。
- ウォッチリストから株価情報を削除するには、行の端にある [x] をクリックします。
- 株価のリアルタイムの更新を確認できます。
- Chrome DevTools を開き、[ネットワーク] タブに移動して、[キャッシュを無効にする] と [大きいリクエスト行を使用する] をオンにします。[キャッシュを無効にする] を選択すると、更新後に常に最新の変更が取得されます。[大きなリクエスト行を使用する] を選択すると、リソースの送信サイズとリソースサイズの両方が行に表示されます。この Codelab では、主に
main.js
のサイズに注目します。
- スロットリングのシミュレーションを使用して、さまざまなネットワーク条件下でアプリを読み込みます。この Codelab では [Slow 3G] を使用して読み込み時間を測定します。これは、バンドルサイズが小さいほど役立つためです。
新しいモジュラー API へのアプリの移行を始めましょう。
4. 互換性パッケージを使用する
互換性パッケージを使用すると、すべての Firebase コードを一度に変更することなく、新しい SDK バージョンにアップグレードできます。モジュラー API に段階的にアップグレードできます。
このステップでは、Firebase ライブラリを v8 から新しいバージョンにアップグレードし、互換性パッケージを使用するようにコードを変更します。以下の手順では、モジュラー API を使用するように Firebase Auth コードのみをアップグレードしてから、Firestore コードをアップグレードする方法を学習します。
各ステップを完了すると、アプリをコンパイルして実行できるようになり、各プロダクトの移行に伴ってバンドルサイズが小さくなります。
新しい SDK を入手する
package.json
の依存関係セクションを見つけて、次のように置き換えます。
package.json
"dependencies": {
"firebase": "^9.0.0"
}
依存関係を再インストールする
依存関係のバージョンを変更したため、npm install
を再実行して依存関係の新しいバージョンを取得する必要があります。
インポート パスを変更する
互換性パッケージはサブモジュール firebase/compat
の下に公開されているため、それに応じてインポートパスを更新します。
src/firebase.ts
ファイルに移動- 既存のインポートを次のインポートに置き換えます。
src/firebase.ts
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
アプリが機能することを確認する
npm run build
を実行してアプリを再ビルドします。- ブラウザタブを開いて http://localhost:8080 にアクセスするか、既存のタブを更新します。
- アプリでプレイする。すべてがそのまま動作しているはずです。
5. モジュラー API を使用するように Auth をアップグレードする
Firebase プロダクトは任意の順序でアップグレードできます。この Codelab では、Auth API は比較的シンプルなため、まず Auth をアップグレードして基本的なコンセプトを学びます。Firestore のアップグレードはもう少し複雑なので、次に行う方法を学びます。
Auth の初期化を更新する
src/firebase.ts
ファイルに移動- 次のインポートを追加します。
src/firebase.ts
import { initializeAuth, indexedDBLocalPersistence } from 'firebase/auth';
import ‘firebase/compat/auth'.
の削除export const firebaseAuth = app.auth();
を次のように置き換えます。
src/firebase.ts
export const firebaseAuth = initializeAuth(app, { persistence: [indexedDBLocalPersistence] });
- ファイルの末尾の
export type User = firebase.User;
を削除します。User
はsrc/auth.ts
に直接エクスポートされます。これを次に変更します。
認証コードを更新する
src/auth.ts
ファイルに移動- ファイルの先頭に次のインポートを追加します。
src/auth.ts
import {
signInAnonymously,
signOut,
onAuthStateChanged,
User
} from 'firebase/auth';
‘firebase/auth'.
からUser
をすでにインポートしているため、import { firebaseAuth, User } from './firebase';
からUser
を削除します- モジュラー API を使用するように関数を更新する。
以前に import ステートメントを更新したときに見たように、バージョン 9 のパッケージは、ドットチェーンの名前空間とサービス パターンに基づくバージョン 8 の API とは対照的に、インポート可能な関数を中心に編成されています。このコードの新しい構成によって、使用されていないコードのツリー シェイキングが可能になります。これは、ビルドツールが、どのコードを使用し、どのコードを使用しないかを分析できるためです。
バージョン 9 では、サービスは最初の引数として関数に渡されます。Service は、Firebase サービスの初期化で取得されるオブジェクトです。たとえば、getAuth()
または initializeAuth()
から返されたオブジェクト。特定の Firebase サービスの状態を保持し、関数はその状態を使用してタスクを実行します。このパターンを適用して、次の関数を実装してみましょう。
src/auth.ts
export function firebaseSignInAnonymously() {
return signInAnonymously(firebaseAuth);
}
export function firebaseSignOut() {
return signOut(firebaseAuth);
}
export function onUserChange(callback: (user: User | null) => void) {
return onAuthStateChanged(firebaseAuth, callback);
}
export { User } from 'firebase/auth';
アプリが機能することを確認する
npm run build
を実行してアプリを再ビルドします。- ブラウザタブを開いて http://localhost:8080 にアクセスするか、既存のタブを更新します
- アプリでプレイする。すべてがそのまま動作しているはずです。
バンドルサイズを確認
- Chrome DevTools を開きます。
- [ネットワーク] タブに切り替えます。
- ページを更新して、ネットワーク リクエストを取得します。
- main.js を見つけてサイズを確認します。わずか数行のコードを変更するだけで、バンドルサイズを 100 KB(gzip で 36 KB)削減できました。つまり、約 22% 削減できました。また、低速の 3G 接続では、サイトの読み込みが 0.75 秒速くなっています。
6. モジュラー API を使用するように Firebase アプリと Firestore をアップグレードする
Firebase の初期化を更新する
src/firebase.ts.
ファイルに移動import firebase from ‘firebase/compat/app';
を次のように置き換えます。
src/firebase.ts
import { initializeApp } from 'firebase/app';
const app = firebase.initializeApp({...});
を次のように置き換えます。
src/firebase.ts
const app = initializeApp({
apiKey: "AIzaSyBnRKitQGBX0u8k4COtDTILYxCJuMf7xzE",
authDomain: "exchange-rates-adcf6.firebaseapp.com",
databaseURL: "https://exchange-rates-adcf6.firebaseio.com",
projectId: "exchange-rates-adcf6",
storageBucket: "exchange-rates-adcf6.appspot.com",
messagingSenderId: "875614679042",
appId: "1:875614679042:web:5813c3e70a33e91ba0371b"
});
Firestore の初期化を更新する
- 同じファイル
src/firebase.ts,
で、import 'firebase/compat/firestore';
を次の内容に置き換えます。
src/firebase.ts
import { getFirestore } from 'firebase/firestore';
export const firestore = app.firestore();
を次のように置き換えます。
src/firebase.ts
export const firestore = getFirestore();
- 「
export const firestore = ...
」の後の行をすべて削除
インポートを更新する
src/services.ts.
ファイルを開く- インポートから
FirestoreFieldPath
、FirestoreFieldValue
、QuerySnapshot
を削除します。'./firebase'
からのインポートは次のようになります。
src/services.ts
import { firestore } from './firebase';
- 使用する関数と型をファイルの先頭でインポートします。
**src/services.ts**
import {
collection,
getDocs,
doc,
setDoc,
arrayUnion,
arrayRemove,
onSnapshot,
query,
where,
documentId,
QuerySnapshot
} from 'firebase/firestore';
search() を更新する
- すべてのティッカーを含むコレクションへの参照を作成します。
src/services.ts
const tickersCollRef = collection(firestore, 'current');
getDocs()
を使用して、コレクションからすべてのドキュメントを取得します。
src/services.ts
const tickers = await getDocs(tickersCollRef);
最終的なコードについては、search()
をご覧ください。
addToWatchList() を更新する
doc()
を使用してユーザーのウォッチリストへのドキュメント参照を作成し、setDoc()
と arrayUnion()
を使用してティッカーを追加します。
src/services.ts
export function addToWatchList(ticker: string, user: User) {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
return setDoc(watchlistRef, {
tickers: arrayUnion(ticker)
}, { merge: true });
}
deleteFromWatchList() を更新する
同様に、setDoc()
と arrayRemove()
を使用して、ユーザーのウォッチリストからティッカーを削除します。
src/services.ts
export function deleteFromWatchList(ticker: string, user: User) {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
return setDoc(watchlistRef, {
tickers: arrayRemove(ticker)
}, { merge: true });
}
subscribeToTickerChanges() を更新する
- まず
doc()
を使用してユーザーのウォッチリストへのドキュメント参照を作成し、次にonSnapshot()
を使用してウォッチリストの変更をリッスンします。
src/services.ts
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const unsubscribe = onSnapshot(watchlistRef, snapshot => {
/* subscribe to ticker price changes */
});
- ウォッチリストにティッカーを追加したら、
query()
を使用して価格を取得するクエリを作成し、onSnapshot()
を使用して価格変更をリッスンします。
src/services.ts
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
unsubscribePrevTickerChanges = onSnapshot(priceQuery, snapshot => {
if (firstload) {
performance && performance.measure("initial-data-load");
firstload = false;
logPerformance();
}
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
完全な実装については、subscribeToTickerChanges() をご覧ください。
subscribeToAllTickerChanges() を更新する
まず、collection()
を使用してすべてのティッカーの価格を含むコレクションへの参照を作成し、次に onSnapshot()
を使用して価格変更をリッスンします。
src/services.ts
export function subscribeToAllTickerChanges(callback: TickerChangesCallBack) {
const tickersCollRef = collection(firestore, 'current');
return onSnapshot(tickersCollRef, snapshot => {
if (firstload) {
performance && performance.measure("initial-data-load");
firstload = false;
logPerformance();
}
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
アプリが機能することを確認する
npm run build
を実行してアプリを再ビルドします。- ブラウザタブを開いて http://localhost:8080 にアクセスするか、既存のタブを更新します
- アプリでプレイする。すべてがそのまま動作しているはずです。
バンドルサイズを確認
- Chrome DevTools を開きます。
- [ネットワーク] タブに切り替えます。
- ページを更新して、ネットワーク リクエストを取得します。
main.js
を探してサイズを確認します。再び元のバンドルサイズと比較してみましょう。バンドルサイズを 200KB(gzip で 63.8 KB)以上小さくしました。つまり、50% 小さくなり、読み込み時間が 1.3 秒短縮されています。
7. Firestore Lite を使用して最初のページのレンダリングを高速化する
Firestore Lite とは
Firestore SDK は、複雑なキャッシュ保存、リアルタイム ストリーミング、永続ストレージ、マルチタブ オフライン同期、再試行、オプティミスティック同時実行など、多くの機能を備えているため、サイズは非常に大きくなります。しかし、高度な機能を使用せずに、データを 1 回だけ取得したい場合もあります。そこで、Firestore は、シンプルかつ軽量なソリューションである、まったく新しいパッケージである Firestore Lite を開発しました。
Firestore Lite の優れたユースケースの一つは、最初のページ レンダリングのパフォーマンスを最適化することです。ユーザーがログインしているかどうかを把握し、Firetore から一部のデータを読み込んで表示します。
このステップでは、Firestore Lite を使用してバンドルサイズを縮小し、最初のページのレンダリングを高速化してから、メインの Firestore SDK を動的に読み込み、リアルタイムの更新を受け取る方法を学習します。
コードを次のようにリファクタリングします。
- リアルタイム サービスを個別のファイルに移動し、動的インポートを使用して動的に読み込みできるようにします。
- Firestore Lite を使用してウォッチリストと株価を取得する新しい関数を作成します。
- 新しい Firestore Lite の関数を使用してデータを取得し、最初のページをレンダリングしてから、リアルタイム サービスを動的に読み込み、リアルタイムの更新をリッスンします。
リアルタイム サービスを新しいファイルに移動する
src/services.realtime.ts.
という名前の新しいファイルを作成します。src/services.ts
からsubscribeToTickerChanges()
関数とsubscribeToAllTickerChanges()
関数を新しいファイルに移動します。- 必要なインポートを新しいファイルの先頭に追加します。
ここでも、いくつかの変更を加える必要があります。
- まず、関数で使用するファイルの先頭に、メインの Firestore SDK から Firestore インスタンスを作成します。ここでは
firebase.ts
から Firestore インスタンスをインポートできません。これは、最初のページのレンダリングにのみ使用されるいくつかの手順で Firestore Lite インスタンスに変更するためです。 - 次に、
firstload
変数と、それによって保護されている if ブロックを削除します。これらの関数は、次のステップで作成する新しい関数に移行します。
src/services.realtime.ts
import { User } from './auth'
import { TickerChange } from './models';
import { collection, doc, onSnapshot, query, where, documentId, getFirestore } from 'firebase/firestore';
import { formatSDKStocks } from './services';
const firestore = getFirestore();
type TickerChangesCallBack = (changes: TickerChange[]) => void
export function subscribeToTickerChanges(user: User, callback: TickerChangesCallBack) {
let unsubscribePrevTickerChanges: () => void;
// Subscribe to watchlist changes. We will get an update whenever a ticker is added/deleted to the watchlist
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const unsubscribe = onSnapshot(watchlistRef, snapshot => {
const doc = snapshot.data();
const tickers = doc ? doc.tickers : [];
if (unsubscribePrevTickerChanges) {
unsubscribePrevTickerChanges();
}
if (tickers.length === 0) {
callback([]);
} else {
// Query to get current price for tickers in the watchlist
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
// Subscribe to price changes for tickers in the watchlist
unsubscribePrevTickerChanges = onSnapshot(priceQuery, snapshot => {
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
});
return () => {
if (unsubscribePrevTickerChanges) {
unsubscribePrevTickerChanges();
}
unsubscribe();
};
}
export function subscribeToAllTickerChanges(callback: TickerChangesCallBack) {
const tickersCollRef = collection(firestore, 'current');
return onSnapshot(tickersCollRef, snapshot => {
const stocks = formatSDKStocks(snapshot);
callback(stocks);
});
}
Firestore Lite を使用してデータを取得する
src/services.ts.
を開く- インポート パスを
‘firebase/firestore'
から‘firebase/firestore/lite',
に変更し、getDoc
を追加してインポート リストからonSnapshot
を削除します:
src/services.ts
import {
collection,
getDocs,
doc,
setDoc,
arrayUnion,
arrayRemove,
// onSnapshot, // firestore lite doesn't support realtime updates
query,
where,
documentId,
QuerySnapshot,
getDoc // add this import
} from 'firebase/firestore/lite';
- Firestore Lite を使用して、最初のページ レンダリングに必要なデータを取得する関数を追加します。
src/services.ts
export async function getTickerChanges(tickers: string[]): Promise<TickerChange[]> {
if (tickers.length === 0) {
return [];
}
const priceQuery = query(
collection(firestore, 'current'),
where(documentId(), 'in', tickers)
);
const snapshot = await getDocs(priceQuery);
performance && performance.measure("initial-data-load");
logPerformance();
return formatSDKStocks(snapshot);
}
export async function getTickers(user: User): Promise<string[]> {
const watchlistRef = doc(firestore, `watchlist/${user.uid}`);
const data = (await getDoc(watchlistRef)).data();
return data ? data.tickers : [];
}
export async function getAllTickerChanges(): Promise<TickerChange[]> {
const tickersCollRef = collection(firestore, 'current');
const snapshot = await getDocs(tickersCollRef);
performance && performance.measure("initial-data-load");
logPerformance();
return formatSDKStocks(snapshot);
}
src/firebase.ts
を開き、インポート パスを‘firebase/firestore'
から‘firebase/firestore/lite':
に変更します。
src/firebase.ts
import { getFirestore } from 'firebase/firestore/lite';
まとめ
src/main.ts.
を開く- 最初のページ レンダリングのデータを取得するために新しく作成された関数と、アプリの状態を管理するためのヘルパー関数が必要になります。そこでインポートを更新します。
src/main.ts
import { renderLoginPage, renderUserPage } from './renderer';
import { getAllTickerChanges, getTickerChanges, getTickers } from './services';
import { onUserChange } from './auth';
import { getState, setRealtimeServicesLoaded, setUser } from './state';
import './styles.scss';
- ファイルの先頭で動的インポートを使用して
src/services.realtime
を読み込みます。変数loadRealtimeService
は、コードが読み込まれた後にリアルタイム サービスで解決される Promise です。後でこれを使用して、リアルタイムの更新情報を受け取るように登録します。
src/main.ts
const loadRealtimeService = import('./services.realtime');
loadRealtimeService.then(() => {
setRealtimeServicesLoaded(true);
});
onUserChange()
のコールバックをasync
関数に変更して、関数本体でawait
を使用できるようにします。
src/main.ts
onUserChange(async user => {
// callback body
});
- 次に、前のステップで作成した新しい関数を使用してデータを取得し、最初のページをレンダリングします。
onUserChange()
コールバックで、ユーザーがログインしている if 条件を見つけ、&if ステートメント内にコードを貼り付けます。
src/main.ts
onUserChange(async user => {
// LEAVE THE EXISTING CODE UNCHANGED HERE
...
if (user) {
// REPLACE THESE LINES
// user page
setUser(user);
// show loading screen in 500ms
const timeoutId = setTimeout(() => {
renderUserPage(user, {
loading: true,
tableData: []
});
}, 500);
// get data once if realtime services haven't been loaded
if (!getState().realtimeServicesLoaded) {
const tickers = await getTickers(user);
const tickerData = await getTickerChanges(tickers);
clearTimeout(timeoutId);
renderUserPage(user, { tableData: tickerData });
}
// subscribe to realtime updates once realtime services are loaded
loadRealtimeService.then(({ subscribeToTickerChanges }) => {
unsubscribeTickerChanges = subscribeToTickerChanges(user, stockData => {
clearTimeout(timeoutId);
renderUserPage(user, { tableData: stockData })
});
});
} else {
// DON'T EDIT THIS PART, YET
}
}
- ユーザーがログインしていない else ブロックで、Firestore Lite を使用してすべての株価情報を取得し、ページをレンダリングして、リアルタイム サービスが読み込まれた後に価格変更をリッスンします。
src/main.ts
if (user) {
// DON'T EDIT THIS PART, WHICH WE JUST CHANGED ABOVE
...
} else {
// REPLACE THESE LINES
// login page
setUser(null);
// show loading screen in 500ms
const timeoutId = setTimeout(() => {
renderLoginPage('Landing page', {
loading: true,
tableData: []
});
}, 500);
// get data once if realtime services haven't been loaded
if (!getState().realtimeServicesLoaded) {
const tickerData = await getAllTickerChanges();
clearTimeout(timeoutId);
renderLoginPage('Landing page', { tableData: tickerData });
}
// subscribe to realtime updates once realtime services are loaded
loadRealtimeService.then(({ subscribeToAllTickerChanges }) => {
unsubscribeAllTickerChanges = subscribeToAllTickerChanges(stockData => {
clearTimeout(timeoutId);
renderLoginPage('Landing page', { tableData: stockData })
});
});
}
完成したコードについては src/main.ts をご覧ください。
アプリが機能することを確認する
npm run build
を実行してアプリを再ビルドします。- ブラウザタブを開いて http://localhost:8080 にアクセスするか、既存のタブを更新します。
バンドルサイズを確認
- Chrome DevTools を開きます。
- [ネットワーク] タブに切り替えます。
- ページを更新してネットワーク リクエストを取得する
main.js
を探してサイズを確認します。- 現在はわずか 115 KB(gzip で 34.5 KB)になりました。これは、元のバンドルサイズである 446 KB(gzip で 138 KB)よりも 75% 小さくなります。その結果、サイトの読み込みが 3G 接続で 2 秒以上高速化され、パフォーマンスとユーザー エクスペリエンスが大幅に向上しました。
8. 完了
アプリのアップグレードが完了し、サイズを小さくして高速化できました。
互換パッケージを使用してアプリを一つずつアップグレードし、Firestore Lite を使用して最初のページのレンダリングを高速化してから、メインの Firestore を動的に読み込んで価格変更をストリーミングしました。
また、この Codelab のコースを通して、バンドルサイズを削減し、読み込み時間も改善しました。
main.js | リソースサイズ(KB) | gzip で圧縮されたサイズ(KB) | 読み込み時間(秒)(3g 以上の場合) |
v8 | 446 | 138 | 492 |
v9 互換 | 429 | 124 | 4.65 |
v9 のみのモジュラー Auth | 348 | 102 | 4.2 |
v9 は完全なモジュラー | 244 | 74.6 | 3.66 |
v9 のフル モジュラー + Firestore Lite | 117 | 34.9 | 288 |
Firebase JS SDK v8 を使用しているウェブアプリをアップグレードして、新しいモジュラー JS SDK を使用するために必要な主な手順を学習しました。