1. 事前準備
模組化 Firebase JS SDK 是現有 JS SDK 的重寫版本,並將以下一個主要版本的形式發布。開發人員可以從 Firebase JS SDK 中排除未使用的程式碼,藉此建立較小的套件並提升效能。
模組化 JS SDK 最明顯的差異在於,功能現在會以您要匯入的自由浮動函式進行整理,而非在包含所有內容的單一 firebase
命名空間中。這種新的程式碼整理方式可讓您進行樹狀圖搖動,您將瞭解如何將目前使用 v8 Firebase JS SDK 的任何應用程式升級至新的模組化 SDK。
為提供順暢的升級程序,我們提供一組相容性套件。在本程式碼研究室中,您將瞭解如何使用相容性套件逐一移植應用程式。
建構項目
在本程式碼研究室中,您將逐步將現有的股票觀察清單網頁應用程式 (使用 v8 JS SDK) 遷移至新的模組化 JS SDK,分成三個階段:
- 升級應用程式以使用相容性套件
- 逐一將應用程式從相容性套件升級至模組化 API
- 使用 Firestore Lite (Firestore SDK 的輕量實作方式),進一步提升應用程式的效能
本程式碼研究室著重於升級 Firebase SDK。我們不會對其他概念和程式碼區塊多做介紹,但會事先準備好這些程式碼區塊,屆時您只要複製及貼上即可。
事前準備
2. 做好準備
取得程式碼
本專案所需的一切都位於 Git 存放區中。如要開始使用,請先取得程式碼,然後在您偏好的開發環境中開啟。
從指令列複製程式碼研究室的 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. 建立基準
從哪裡開始?
您要從專為本次程式碼研究室所設計的股票觀察清單應用程式開始。為了說明本程式碼研究室中的概念,程式碼已簡化,且幾乎沒有錯誤處理。如果您選擇在實際執行應用程式中重複使用任何這類程式碼,請務必處理任何錯誤,並全面測試所有程式碼。
確認應用程式中的所有功能皆正常運作:
- 使用右上角的「登入」按鈕匿名登入。
- 登入後,請搜尋並將「NFLX」、「SBUX」和「T」加入待觀察清單,方法是按一下「Add」按鈕,輸入字母,然後點選下方彈出的搜尋結果列。
- 按一下列尾端的「x」,即可從觀察清單中移除股票。
- 查看股票價格的即時更新資訊。
- 開啟 Chrome 開發人員工具,前往「Network」分頁,然後勾選「Disable cache」和「Use large request rows」。停用快取可確保系統在重新整理後,一律取得最新變更,而使用大型要求列可讓列同時顯示資源的傳輸大小和資源大小。在本程式碼研究室中,我們主要關注
main.js
的大小。
- 使用模擬節流功能,在不同網路連線狀況下載入應用程式。您將使用 慢速 3G 來測量本程式碼研究室中的載入時間,因為在這種情況下,較小的套件大小最有幫助。
接下來,我們將開始將應用程式遷移至新的模組化 API。
4. 使用相容性套件
相容性套件可讓您升級至新版 SDK,而無須一次變更所有 Firebase 程式碼。您可以逐步將這些 API 升級為模組化 API。
在這個步驟中,您將將 Firebase 程式庫從 v8 升級至新版本,並變更程式碼以使用相容性套件。在後續步驟中,您將瞭解如何先升級 Firebase Auth 程式碼,以便使用模組化 API,然後再升級 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. 升級 Auth 以使用模組化 API
您可以依任何順序升級 Firebase 產品。在本程式碼研究室中,您會先升級 Auth,以便瞭解基本概念,因為 Auth API 相對簡單。升級 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 開發人員工具。
- 切換至「Network」分頁。
- 重新整理網頁以擷取網路要求。
- 找出 main.js 並檢查其大小。您只需更改幾行程式碼,就能縮減套件大小 100 KB (經 GZIP 壓縮後為 36 KB),也就是縮減約 22%!在 3G 連線速度緩慢的情況下,網站的載入速度也加快了 0.75 秒。
6. 升級 Firebase 應用程式和 Firestore,以便使用模組化 API
更新 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()
建立使用者觀察清單的文件參照,然後使用 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 開發人員工具。
- 切換至「Network」分頁。
- 重新整理網頁以擷取網路要求。
- 找出
main.js
並檢查其大小。再比較一下原始套件大小,我們已將套件大小縮減超過 200 KB (經 GZIP 壓縮後為 63.8 KB),也就是縮小了 50%,因此載入時間也縮短了 1.3 秒!
7. 使用 Firestore Lite 加快初始網頁轉譯速度
什麼是 Firestore Lite?
Firestore SDK 提供複雜的快取、即時串流、永久性儲存空間、多分頁離線同步、重試、樂觀並行處理等功能,因此檔案大小相當大。但您可能只想取得一次資料,而不需要任何進階功能。針對這些情況,Firestore 推出了簡單輕量級的解決方案,也就是全新的 Firestore Lite 套件。
Firestore Lite 的一個絕佳用途,就是改善初始網頁轉譯的效能,您只需要知道使用者是否已登入,然後從 Firestore 讀取一些資料來顯示。
在這個步驟中,您將瞭解如何使用 Firestore Lite 減少套件大小,加快初始網頁轉譯速度,然後動態載入主要 Firestore SDK 來訂閱即時更新。
您將重構程式碼,以便:
- 將即時服務移至個別檔案,以便使用動態匯入功能動態載入。
- 建立新的函式,使用 Firestore Lite 擷取觀察清單和股票價格。
- 使用新的 Firestore Lite 函式擷取資料,以便初始頁面算繪,然後動態載入即時服務,以便監聽即時更新。
將即時服務移至新檔案
- 建立名為
src/services.realtime.ts.
的新檔案 - 將
subscribeToTickerChanges()
和subscribeToAllTickerChanges()
函式從src/services.ts
移至新檔案。 - 在新的檔案頂端新增必要的匯入項目。
您仍需要在此處進行幾項變更:
- 首先,請在檔案頂端使用主要 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
是應保證,會在程式碼載入後,與即時服務解析。稍後您會用它訂閱即時更新。
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 開發人員工具。
- 切換至「Network」分頁。
- 重新整理網頁以擷取網路要求
- 找出
main.js
並檢查其大小。 - 目前只有 115 KB (經 GZIP 壓縮後為 34.5 KB)。比原始套件的大小(446 KB,經 GZIP 壓縮後為 138 KB) 小了 75%!因此,在 3G 連線下,網站的載入速度加快了 2 秒以上,大幅改善效能和使用者體驗!
8. 恭喜!
恭喜,您已成功升級應用程式,讓應用程式變得更小、更快速!
您使用了相容性套件逐一升級應用程式,並使用 Firestore Lite 加快初始網頁轉譯作業,然後動態載入主要 Firestore 以串流傳輸價格變更。
您也縮減了套件大小,並在本程式碼研究室中縮短了載入時間:
main.js | 資源大小 (KB) | 經過 GZIP 壓縮的大小 (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 所需的重要步驟。