本指南說明如何將擴充功能從已淘汰的 Firebase Extensions 環境遷移至函式,讓使用者在自己的 Cloud Functions 中安裝及部署,以用於 Firebase (第 2 代) 程式碼集。
建議採用這個遷移路徑。Firebase 會維護擴充功能清單,列出官方對應的 npm 擴充功能;本指南將逐步說明如何建立擴充功能。
本指南會以「Stream Firestore to BigQuery」BigQuery擴充功能 (firestore-bigquery-export) 為例。每個章節的結尾都會提供實際範例,說明遷移前和遷移後擴充功能的樣貌,以及 @firebase/firestore-bigquery-export 套件。
註冊即可取得更多資訊,並獲得遷移擴充功能的協助
如對如何從 Firebase Extensions 遷移有任何疑問,歡迎來信至 firebase-extensions-migrator-support-external@google.com。此外,我們也會在指南中新增有關封裝、測試及發布第 2 代函式的資訊時,透過電子郵件通知這個群組。
如要加入這個群組,請傳送郵件至 firebase-extensions-migrator-support-external+subscribe@google.com, 系統會回覆一封成員資格要求電子郵件。請務必回覆該電子郵件,不要點選「加入這個群組」按鈕。
事前準備
如要完成這項遷移作業,請使用 Cloud Functions 的下列功能:
參數化設定。 您在
extension.yaml中宣告的每個參數,都會成為套件程式碼中定義的參數。宣告式 IAM 角色和必要 API。您在
extension.yaml中宣告的每個角色都會成為requiresRole(...)呼叫,而每個 API 都會成為函式程式碼中的requiresAPI(...)呼叫。在部署時,Firebase CLI 會將宣告的角色授予代管執行階段服務帳戶,並代您啟用宣告的 API。Cloud Functions 程式碼集的生命週期事件。Cloud Functions 程式碼集現在支援與 Firebase Extensions 類似的生命週期事件。使用生命週期掛鉤
afterFirstDeploy(...)和afterRedeploy(...)宣告安裝時和更新時的設定。取代您在extension.yaml中宣告的lifecycleEvents。
清查擴充功能
首先,請清查擴充功能:列出擴充功能宣告、運送及記錄的所有項目,確保每個行為在第 2 代函式中都有定義目的地,且遷移作業不會遺失任何項目。
請檢查下列各項,並記錄發現的內容:
extension.yaml,其中會宣告參數、函式、事件、IAM 角色、必要 API、密鑰和生命週期掛鉤。functions/,其中包含函式程式碼、依附元件、建構設定、觸發條件和工作佇列函式。README.md、PREINSTALL.md和POSTINSTALL.md,其中包含設定步驟、警告和帳單注意事項。scripts/,其中包含任何匯入、回填、IAM、修復或遷移公用程式,以及您隨擴充功能出貨的任何其他工具。
接著,針對 extension.yaml 中的每個項目,決定在 npm 套件中的位置:
將使用者設定轉換為 Cloud Functions 參數 (第 5 節)。
將密鑰轉換為 Cloud Functions 密鑰 (第 6 節)。
將 IAM 角色轉換為
requiresRole(...)宣告 (第 8 節)。將必要的 Google API 轉換為適當的
requiresAPI(...)宣告 (第 8 節)。將安裝和更新掛鉤轉換為
afterFirstDeploy(...)和afterRedeploy(...)宣告 (第 8 節)。
工作範例:將 Firestore 串流至 BigQuery
讀取 firestore-bigquery-export/extension.yaml 和 functions/ 會產生下列清單:
| 在 extension.yaml 中 | 數量 / 值 | 資料去向 |
|---|---|---|
| params | 25 (COLLECTION_PATH、DATASET_ID、TABLE_ID、DATASET_LOCATION、VIEW_TYPE 等) | Cloud Functions 參數 (第 5 節) |
| API | bigquery.googleapis.com | requiresAPI(...) (第 7 節) |
| 角色 | bigquery.dataEditor、datastore.user、bigquery.user | requiresRole(...) (第 7 節) |
| 資源 | 1 個事件觸發條件 (fsexportbigquery) + 工作佇列函式 (initBigQuerySync、setupBigQuerySync) | 匯出的套件函式 (第 3 節) |
| lifecycleEvents | onInstall → initBigQuerySync;onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (第 9 節) |
| scripts/ | import/ (backfill)、gen-schema-view/ | 保留為指令碼 (不在本文討論範圍內) |
擴充功能未宣告任何型別:私密參數,因此本指南第 6 節沒有任何內容需要遷移。事件觸發程序已是第 2 代,只有工作佇列函式仍為第 1 代 (相關資訊請參閱第 3 節)。
更新 package.json
更新擴充功能的 package.json 檔案。如果只遷移一個擴充功能,這可以是根 package.json。如要在單一存放區中遷移多個擴充功能,請為每個擴充功能提供專屬套件。
最低 SDK 版本。將 firebase-functions >= 7.3 和 firebase-admin >= 14.2.0 宣告為依附元件。同時將 firebase-functions 版本宣告為對等互依性
{
"name": "<package-name>",
"version": "1.0.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
},
"engines": {
"node": ">=22"
},
"peerDependencies": {
"firebase-functions": "^7.3.0"
},
"dependencies": {
"firebase-functions": "^7.3.0",
"firebase-admin": "^14.2.0"
}
}
除了正常依附元件外,請將 firebase-functions 宣告為對等依附元件,確保使用者 Cloud Functions 專案的 SDK 版本與程式庫編寫時使用的版本相同。
工作範例:將 Firestore 串流至 BigQuery
變更前。擴充功能的 functions/package.json 為私有,會命名擴充功能 ID,並將 firebase-functions 宣告為直接依附元件:
{
"name": "firestore-bigquery-export",
"main": "lib/index.js",
"private": true,
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^6.3.2"
}
}
變更後。可發布的套件:範圍名稱、匯出對應檔,以及
firebase-functions 移至 peerDependencies:
{
"name": "@firebase/firestore-bigquery-export",
"version": "0.1.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" }, },
"engines": { "node": ">=22" },
"peerDependencies": { "firebase-functions": "^7.3.0" },
"dependencies": {
"@firebaseextensions/firestore-bigquery-change-tracker": "^2.0.4",
"firebase-admin": "^14.2.0",
"firebase-functions": "^7.3.0"
}
}
將第 1 代函式升級至第 2 代
如果擴充功能仍匯出第 1 代函式,請將每個函式轉換為對應的第 2 代函式。從 firebase-functions/... 模組匯入,並在函式選項中傳遞執行階段設定。
您可以使用第 2 代修補事件解構功能盡量減少重寫作業,並避免重寫函式邏輯,因為第 2 代 SDK 現在會將 V1 參數公開為事件物件中的欄位,讓您使用解構/具名參數,並維持業務邏輯不變。
變更前。第 1 代:
import * as functions from "firebase-functions/v1";
export const sync = functions.firestore
.document("{collectionId}/{documentId}")
.onWrite(async (change, context) => {
await handleWrite(change.before, change.after, context.params);
});
變更後。第 2 代:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
如需第 1 代和第 2 代函式之間的完整差異清單,請參閱 Cloud Functions 版本比較。
轉換擴充功能參數和密鑰
轉換參數
您在 extension.yaml 中宣告的每個參數都會成為 Cloud Functions 參數。
轉換直接環境讀取作業:
const collectionPath = process.env.COLLECTION_PATH;
到 Cloud Functions 參數:
import { defineString } from "firebase-functions/params";
import { onDocumentWritten} from "firebase-functions/firestore";
const collectionPath = defineString("COLLECTION_PATH");
// Pass the param directly when used as a placeholder (e.g. trigger path)
export const sync = onDocumentWritten(
{ document: collectionPath },
async (event) => {
// Call .value() to read the string inside a handler
const path = collectionPath.value();
await handleWrite(path, event);
}
);
如要讀取處理常式內的字串,請使用 collectionPath.value();如要使用預期會出現預留位置的項目 (例如函式觸發路徑),請直接使用 collectionPath。
Firebase CLI 會探索參數,並從 .env、.env.projectId 讀取參數值,或在部署期間提示使用者。保留相同的參數名稱,以便沿用現有安裝項目的值。
請務必不要變更程式碼中宣告的參數名稱。擴充功能遷移作業會自動保留現有的使用者參數值,但前提是名稱維持不變。
工作範例:將 Firestore 串流至 BigQuery
之前。在 extension.yaml 中宣告的參數,在 config.ts 中讀取為原始環境變數:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
變更後。一個 defineString;CLI 會探索並從 .env 讀取:
// src/config.ts
import { defineString } from "firebase-functions/params";
collectionPath: defineString("COLLECTION_PATH", {
label: "Collection path",
// We now support "nonEmpty: true" to ensure a value other than the empty string
// is entered, analagous to "required: true" in extensions.yaml
input: { text: { nonEmpty: true} }
}),
參數名稱維持不變,因此現有的 .env 仍可正常運作。
轉換密鑰
在 extension.yaml 中,您可以使用 type: secret 宣告密鑰。擴充功能執行階段會儲存並繫結這些變數,因此擴充功能程式碼可以直接讀取 process.env.PARAM_NAME。在典型的 Cloud Functions 程式碼集中,您會明確宣告及繫結每個密鑰:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
將擴充功能遷移至 npm 套件/套件後,機密參照就會在使用者 .env 檔案中管理。請務必不要變更程式碼中宣告的密鑰名稱。遷移期間,系統會一併遷移使用者密碼。
實際範例:從 Cloud Firestore 觸發電子郵件
變更前。MAIL_COLLECTION 和 SMTP_PASSWORD 會在 config.ts 中讀取為原始環境變數:
# extension.yaml
- param: MAIL_COLLECTION
label: Email documents collection
type: string
default: mail
required: true
- param: SMTP_PASSWORD
label: SMTP password
type: secret
// functions/src/config.ts
mailCollection: process.env.MAIL_COLLECTION,
smtpPassword: process.env.SMTP_PASSWORD,
變更後。一個 defineString 和一個 defineSecret;CLI 會同時探索這兩者,並從 .env 讀取
import { defineString, defineSecret } from "firebase-functions/params";
import { onDocumentWritten } from "firebase-functions/firestore";
const mailCollection = defineString("MAIL_COLLECTION",{ label: "Email documents collection",
default: "mail"
});
const smtpPassword = defineSecret("SMTP_PASSWORD", { label: "SMTP password" });
export const processQueue = onDocumentWritten(
{ document: `${mailCollection}/{documentId}`, secrets: [smtpPassword] },
async (event) => {
const collection = mailCollection.value();
const password = smtpPassword.value();
// ...
}
);
遷移內部工作佇列呼叫
部分擴充功能會使用 Firebase Admin SDK,從函式程式碼內部將工作加入自己的工作佇列。這與接收已派送的工作不同 (請參閱「升級函式」和「轉換生命週期掛鉤」一節)。在這裡,您的程式碼是生產者,會呼叫 queue.enqueue(...)。
舊版 Admin SDK 需要擴充功能傳遞自己的擴充功能執行個體 ID 做為第二個參數,才能在同一個擴充功能中指定 Task Queue 函式。從 `firebase-admin` 14.2.0 版開始,這項做法既非必要,也不建議採用。Task Queue API 現在預設會以相同環境 (例如擴充功能) 中的工作佇列為目標。無論是擴充功能還是獨立函式,都建議您從程式碼中移除這個參數,移除這個參數可確保可攜性及向前相容性。
佇列呼叫的其他所有項目 (包括 locations/region/functions/name 資源路徑、工作酬載和重試邏輯) 都不會改變。
如要進一步瞭解如何使用 Cloud Tasks 將函式加入佇列,請參閱 /docs/functions/task-functions。
變更前。第 1 代擴充功能
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${config.location}/functions/syncBigQuery`,
process.env.EXT_INSTANCE_ID, // extension instance ID, injected by the runtime
);
await queue.enqueue(taskData);
變更後。第 2 代擴充功能
import { getFunctions } from "firebase-admin/functions";
import { region } from "firebase-functions/params";
const queue = getFunctions().taskQueue(
`locations/${region.value()}/functions/syncBigQuery`);
await queue.enqueue(taskData);
如果佇列呼叫的目標是加上前置字元的程式碼集,系統也會為探索到的函式名稱加上前置字元 (例如 orders-syncBigQuery)。
宣告必要 API 和 IAM 角色
將擴充功能的 IAM 和 API 需求從 extension.yaml 移至程式碼:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
使用宣告式安全性時,Firebase CLI 會為程式碼集建立或更新受管理執行階段服務帳戶,並授予所有已宣告角色的聯集。向使用者說明程式碼集中的所有函式都會使用這些角色執行,除非最終 API 支援範圍較窄的模型。
工作範例:將 Firestore 串流至 BigQuery
變更前。在 extension.yaml 中宣告;擴充功能執行階段已啟用 API,並將角色授予受管理帳戶:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
變更後。在程式碼中以 requiresAPI 和 requiresRole 宣告:
import { requiresAPI, requiresRole } from "firebase-functions/";
requiresAPI("bigquery.googleapis.com",
"Needed to write changelog rows and views");
requiresRole("roles/biguqery.dataEditor");
requiresRole("roles/datastore.user");
requiresRole("roles/bigquery.user");
轉換生命週期掛鉤
舉例來說,如果擴充功能呼叫 getExtensions().runtime() (例如 setProcessingState 或 setFatalError),請刪除這些呼叫,因為從正常部署的第 2 代函式呼叫時,這些呼叫會擲回錯誤。生命週期狀態現在是由 afterFirstDeploy 和 afterRedeploy 驅動,因此不會使用這個狀態追蹤功能。
使用者安裝、更新或重新設定擴充功能時,Firebase Extensions 可以執行設定。在 npm 套件中,以程式碼宣告等效的生命週期動作。
一次性設定:
import { afterFirstDeploy } from "firebase-functions/lifecycle";
import { onTaskDispatched } from "firebase-functions/tasks";
export const runInitialSetup = onTaskDispatched(async (request) => {
await initializeResources(request.data);
});
afterFirstDeploy({
task: {
function: "runInitialSetup",
body: {}
}
});
更新設定或程式碼:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
讓生命週期動作具備等冪性。如果調度或執行失敗,使用者可能需要手動重新執行:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
工作範例:將 Firestore 串流至 BigQuery
變更前。「lifecycleEvents」產生了金額為 extension.yaml的費用,原因是擴充功能執行階段:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
變更後。在程式碼中宣告;工作會在首次部署時佈建 BigQuery:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
佈建作業是冪等作業,因此重新執行會協調資料集、資料表和檢視區塊。使用者可以透過 firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME 手動重新執行。
為使用者設定文件
撰寫套裝方案
README,至少要說明:套件要求的
.env值。套件所需的密鑰,以及如何遷移現有的密鑰值。
套件使用
requiresRole(...)宣告的 IAM 角色。套件啟用或需要的 Google API。
套件宣告的生命週期掛鉤,以及如何手動重新執行這些掛鉤。
帳單附註。
與原始擴充功能相比,有哪些變更。
工作範例:將 Firestore 串流至 BigQuery
套件 README 會提供具體的「變更內容」表格:
| 疑慮 | 擴充功能 | As @firebase/firestore-bigquery-export |
|---|---|---|
| 設定 | 擴充功能參數 | 透過 .env 設定函式參數 |
| IAM | 由擴充功能授予 | requiresRole(...),在部署時套用 |
| 佈建中 | 擴充功能生命週期工作 | afterFirstDeploy / afterRedeploy 工作 |
| 函式名稱 | ext-instanceId-fsexportbigquery | fsexportbigquery (可選擇加上前置字元) |
測試第 2 代函式
您現在應該會有第 2 代函式,部署後,其行為與擴充功能的新安裝作業完全相同。最後一步是驗證並修正過程中意外發生的任何問題。
請務必使用 firebase-tools
>= 15.24.0,並將轉換後的第 2 代函式部署至測試專案,確保有適當的資源可測試函式行為。如果您已設定測試專案來測試擴充功能,請使用下列指令:
firebase deploy --only functions
輸入這項指令後,請填寫精靈顯示的參數值提示,方式與在擴充功能的 Firebase 控制台中填寫安裝表單相同。
工作範例:將 Firestore 串流至 BigQuery
我們驗證 Cloud Firestore 到 BigQuery 的端對端同步:
- 在 Cloud Firestore 控制台中,建立您設為 COLLECTION_PATH (使用者) 的集合 (如果尚未建立)。
- 建立名為 bigquery-mirror-test 的文件,其中包含任何欄位和值。
- 在 BigQuery 控制台中,查詢原始變更記錄表。其中應包含記錄文件建立作業的單一資料列:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- 查詢最新檢視畫面,系統應會傳回唯一文件的最新變更事件:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- 在 Cloud Firestore 中刪除
bigquery-mirror-test文件。最新檢視畫面會移除該項目,原始變更記錄表格則會附加DELETE事件。
您可以使用下列指令檢查單一文件的完整記錄:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
與測試擴充功能的差異:
- 觸發程序會部署為
fsexportbigquery(可選擇加上程式碼庫前置字元),而不是ext-<instanceId>-fsexportbigquery。在 Cloud Functions 資訊主頁和記錄中尋找該名稱。 - 程式碼現在會照常在 Firebase Local Emulator Suite 函式中執行。您可以使用
.env.local,設定要在模擬器中使用的參數值。您也可以使用 firebase-functions-test SDK 進行單元測試,詳情請參閱「單元測試Cloud Functions」。 - 佈建作業不再由擴充功能執行階段驅動。如果部署後缺少變更記錄表,請手動重新執行設定工作:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME。這項工作是冪等,因此重新執行工作會協調資料集、資料表和檢視區塊。 - 參數值來自
.env,而非安裝表單,因此.env完成後,firebase deploy的重新執行作業不會有互動式功能。