1. 事前準備
模組化 Firebase JS SDK 是現有 JS SDK 的重寫版本,並會以下一個主要版本的形式發布。開發人員可以從 Firebase JS SDK 中排除未使用的程式碼,藉此建立較小的套件並提升效能。
模組化 JS SDK 最顯著的差異,在於功能現在已彙整成您可以匯入的免費浮動函式,而不是在包含所有所有項目的單一 firebase
命名空間中。這種新的程式碼整理方式可讓您進行樹軸作業,您將瞭解如何為目前使用 8 版 Firebase JS 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」分頁,然後勾選「停用快取」和「使用大型要求列」。停用快取可確保我們在重新整理後,一律取得最新變更,而使用大型要求列可讓列同時顯示資源的傳輸大小和資源大小。在本程式碼研究室中,我們主要關注
main.js
的大小。
- 使用模擬節流功能,在不同網路連線狀況下載入應用程式。您將使用 慢速 3G 測量本程式碼研究室中的載入時間,因為在這種情況下,較小的套件大小最有幫助。
立即展開應用程式,開始將應用程式遷移至新的模組化 API。
4. 使用相容性套件
相容性套件可讓您升級至新版 SDK,而無須一次變更所有 Firebase 程式碼。您可以逐步升級至模組 API。
在這個步驟中,您將將 Firebase 程式庫從 v8 升級至新版本,並變更程式碼以使用相容性套件。在下列步驟中,我們會說明如何只升級 Firebase 驗證碼,以便使用模組化 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';
- 從
import { firebaseAuth, User } from './firebase';
中移除User
,因為您已從‘firebase/auth'.
匯入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()
同樣地,如要從使用者的待觀看影劇清單中移除貼圖,請搭配 arrayRemove()
使用 setDoc()
:
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 });
}
更新 subscriptionToTickerChanges()
- 請先使用
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()。
更新 subscriptionToAllTickerChanges()
首先,您會使用 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 執行個體變更為 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 |
第 9 版相容性 | 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 所需的重要步驟。