準備將 Firebase 擴充功能遷移至 Cloud Functions

本指南說明如何將擴充功能從已淘汰的 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.mdPREINSTALL.mdPOSTINSTALL.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() (例如 setProcessingStatesetFatalError),請刪除這些呼叫,因為從正常部署的第 2 代函式呼叫時,這些呼叫會擲回錯誤。生命週期狀態現在是由 afterFirstDeployafterRedeploy 驅動,因此不會使用這個狀態追蹤功能。

使用者安裝、更新或重新設定擴充功能時,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 FirestoreBigQuery 的端對端同步:

  1. Cloud Firestore 控制台中,建立您設為 COLLECTION_PATH (使用者) 的集合 (如果尚未建立)。
  2. 建立名為 bigquery-mirror-test 的文件,其中包含任何欄位和值。
  3. BigQuery 控制台中,查詢原始變更記錄表。其中應包含記錄文件建立作業的單一資料列:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
  1. 查詢最新檢視畫面,系統應會傳回唯一文件的最新變更事件:bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
  1. 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-&lt;instanceId&gt;-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 的重新執行作業不會有互動式功能。