1. 始める前に
モジュラー Firebase JS SDK は、既存の JS SDK を書き換えたもので、次回のメジャー バージョンとしてリリースされます。これにより、デベロッパーは Firebase JS SDK から未使用のコードを除外して、より小さなバンドルを作成し、パフォーマンスを向上させることができます。
モジュラー JS SDK で最も顕著な違いは、機能がすべて含まれる単一の firebase
名前空間ではなく、インポートするフリーフローティング関数で編成されていることです。この新しいコード整理方法により、ツリー シェイキングが可能になります。また、現在 v8 Firebase JS SDK を使用しているアプリを、新しいモジュラー SDK にアップグレードする方法についても説明します。
スムーズなアップグレード プロセスを提供するために、一連の互換性パッケージが用意されています。この Codelab では、互換性パッケージを使用してアプリを部分的に移植する方法について学習します。
作成するアプリの概要
この Codelab では、v8 JS SDK を使用する既存の株式ウォッチリスト ウェブアプリを、新しいモジュラー JS SDK に段階的に移行します。移行は次の 3 つのステージで行います。
- 互換性パッケージを使用するようにアプリをアップグレードする
- 互換性パッケージからモジュラー API にアプリを段階的にアップグレードする
- 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」を検索し、ウォッチリストに追加します。
- 行の末尾にある [x] をクリックして、ウォッチリストから株式を削除します。
- 株価のリアルタイムの更新を確認する。
- Chrome DevTools を開き、[ネットワーク] タブに移動して、[キャッシュを無効にする] と [大きなリクエスト行を使用する] をオンにします。[キャッシュを無効にする] を選択すると、更新後に常に最新の変更が取得されます。[大きなリクエスト行を使用する] を選択すると、リソースの送信サイズとリソースサイズの両方が行に表示されます。この Codelab では、主に
main.js
のサイズに注目します。
- シミュレートされたスロットリングを使用して、さまざまなネットワーク状態の下でアプリを読み込みます。この Codelab では、低速 3G を使用して読み込み時間を測定します。これは、バンドルサイズを小さくすると効果が最も大きい場合だからです。
では、アプリを新しいモジュラー API に移行しましょう。
4. 互換性パッケージを使用する
互換性パッケージを使用すると、Firebase コードをすべて一度に変更することなく、新しい SDK バージョンにアップグレードできます。モジュラー API に段階的にアップグレードできます。
このステップでは、Firebase ライブラリを v8 から新しいバージョンにアップグレードし、互換性パッケージを使用するようにコードを変更します。次の手順では、まず Firebase Auth コードのみをアップグレードしてモジュラー API を使用し、次に Firestore コードをアップグレードする方法を学習します。
各ステップの終わりには、アプリを破損することなくコンパイルして実行できるようになります。また、各プロダクトの移行に伴い、バンドルのサイズが縮小されます。
新しい SDK を入手する
package.json
で dependencies セクションを見つけて、次のように置き換えます。
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 のアップグレードは少し複雑です。その方法については、次で説明します。
認証の初期化を更新
- ファイル
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 を使用するように関数を更新します。
インポート ステートメントを更新したときに説明したように、バージョン 9 のパッケージは、ドットチェーンの名前空間とサービス パターンに基づくバージョン 8 の API とは対照的に、インポート可能な関数を中心に編成されています。未使用のコードのツリー シェイクを可能にするのは、この新しいコードの編成です。これにより、ビルドツールは使用されているコードと使用されていないコードを分析できます。
バージョン 9 では、サービスが関数の最初の引数として渡されます。サービスは、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 App と 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.firebasestorage.app",
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()
を使用してユーザーのウォッチリストへのドキュメント リファレンスを作成し、arrayUnion()
で setDoc()
を使用してそこにチッカーを追加します。
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
を見つけてサイズを確認します。元のバンドルサイズと比較すると、バンドルサイズは 200 KB 以上(gzip 圧縮で 63.8 KB)縮小され、50% 小さくなりました。これにより、読み込み時間が 1.3 秒短縮されました。
7. Firestore Lite を使用して、最初のページのレンダリングを高速化する
Firestore Lite とは
Firestore SDK は、複雑なキャッシュ、リアルタイム ストリーミング、永続ストレージ、マルチタブ オフライン同期、再試行、オプティミスティック コンカレンシなど、さまざまな機能を提供しているため、サイズが非常に大きくなります。ただし、高度な機能は必要がなく、データを 1 回だけ取得したい場合もあります。このようなケースのために、Firestore はシンプルで軽量なソリューションである、まったく新しいパッケージである Firestore Lite を作成しました。
Firestore Lite の優れたユースケースの 1 つは、最初のページのレンダリングのパフォーマンスを最適化することです。この場合、ユーザーがログインしているかどうかを把握し、Firestore からデータを読み取って表示するだけで済みます。
このステップでは、Firestore Lite を使用してバンドルサイズを削減し、最初のページのレンダリングを高速化してから、メインの Firestore SDK を動的に読み込んでリアルタイム更新をサブスクライブする方法について説明します。
コードを次のようにリファクタリングします。
- リアルタイム サービスを別のファイルに移動して、動的インポートを使用して動的に読み込めるようにします。
- Firestore Lite を使用してウォッチリストと株価を取得する新しい関数を作成します。
- 新しい Firestore Lite 関数を使用してデータを取得し、最初のページのレンダリングを行います。その後、リアルタイム サービスを動的に読み込んで、リアルタイム アップデートをリッスンします。
リアルタイム サービスを新しいファイルに移動する
src/services.realtime.ts.
という名前の新しいファイルを作成します。- 関数
subscribeToTickerChanges()
とsubscribeToAllTickerChanges()
をsrc/services.ts
から新しいファイルに移動します。 - 新しいファイルの先頭に必要なインポートを追加します。
まだいくつか変更する必要があります。
- まず、関数で使用する Firestore インスタンスを、ファイルの上部にあるメインの Firestore SDK から作成します。ここでは、
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
は、コードが読み込まれるとリアルタイム サービスで解決されるプロミスです。後で、リアルタイム更新の定期購入に使用します。
src/main.ts
const loadRealtimeService = import('./services.realtime');
loadRealtimeService.then(() => {
setRealtimeServicesLoaded(true);
});
- 関数本体で
await
を使用できるように、onUserChange()
のコールバックをasync
関数に変更します。
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) | 圧縮サイズ(KB) | 読み込み時間(秒)(低速 3G 経由) |
v8 | 446 | 138 | 4.92 |
v9 互換 | 429 | 124 | 4.65 |
v9 のみのモジュラー認証 | 348 | 102 | 4.2 |
v9 完全モジュラー | 244 | 74.6 | 3.66 |
v9 完全モジュラー + Firestore Lite | 117 | 34.9 | 2.88 |
これで、v8 Firebase JS SDK を使用するウェブアプリをアップグレードして、新しいモジュラー JS SDK を使用するのに必要な主な手順を理解できました。