將已安裝的 Firebase 擴充功能(或擴充功能原始碼)轉換為獨立的 Cloud Functions for Firebase 程式碼庫或可發布的 npm 套件,包括將觸發器從 V1 升級到 V2,以及設定生命週期鉤子和宣告式安全性的技能。
擴充功能轉換為 Functions 程式碼庫與 npm 套件遷移
概述
本技能引導代理程式將 Firebase 擴充功能儲存庫或實例遷移為以下任一形式:
- 獨立的 Cloud Functions for Firebase 程式碼庫(
firebase-functions,供終端使用者應用程式整合),或 - 可發布的 npm 套件/可分享的開源套件(供擴充功能發布者發布可重複使用的 V2 函式)。
它利用 Cloud Functions for Firebase 的正式版(GA)功能,以程式碼原生處理權限、相依性和生命週期鉤子,並提供使用解構相容性墊片(Destructuring Compatibility Shim)將舊版 V1 觸發器現代化為 V2 的指示。
觸發條件
當使用者要求以下事項時,啟動此技能:
- 將已安裝的 Firebase 擴充功能遷移或轉換為獨立的 functions 程式碼庫。
- 將擴充功能儲存庫轉換為可發布的 npm 套件(可分享的開源套件)。
- 將擴充功能觸發器從 V1 升級到 V2。
目標遷移工作流程
開始前,請與開發者確認目標目的地:
-
目標 A:本機 Functions 程式碼庫(終端使用者應用程式整合)
- 輸出:原始碼放置在專案的
functions/src/資料夾中。 - 設定:透過
defineString、defineSecret等定義參數,並放在.env中。 - 部署:直接透過
firebase deploy --only functions部署。
- 輸出:原始碼放置在專案的
-
目標 B:可發布的 npm 套件/可分享的開源套件
- 輸出:包含匯出的 V2 函式的可重用 npm 套件。
- 設定:
package.json中包含exports對應,並在dependencies(或peerDependencies)中宣告firebase-functions。 - 使用方式:終端使用者安裝套件(
npm i <package-name>),並在他們的index.ts中重新匯出函式。
開始與 Git 安全
- Git 狀態:開始前,確認工作區的 git 狀態是乾淨的。
- 原地複製:如果要在同一儲存庫內的新子目錄中複製程式碼:
- 使用
git cp(或複製檔案並提交)將擴充功能的原始碼目錄複製到目標目錄。 - 立即提交:
"Copying [extension-name] extension to [directory] in preparation for rewrite"
- 使用
規則與限制
1. 零本機開銷(Cloud Functions 整合)
假設 Cloud Functions for Firebase 工作負載身分、宣告式安全性和 SDK 生命週期鉤子已完全正式發布(GA)。
- 請勿輸出指示或腳本,要求使用者執行手動的
gcloudIAM 指令或建立服務帳戶。 - 請勿撰寫程式碼註解,指示使用者在雲端主控台中手動啟用 Google API。
- 相反,請使用 SDK 中的宣告式
requiresAPI和requiresRole匯入。
2. 全域參數存取限制
- 永遠不要在全域範圍內對任何參數呼叫
.value()。 - 如果全域變數或類別實例使用參數值初始化,請在全域宣告變數,並在
onInit()回呼中初始化:import { defineString } from "firebase-functions/params"; import { onInit } from "firebase-functions/v2"; const bqDataset = defineString("DATASET_ID"); let bqClient: BigQuery; onInit(() => { bqClient = new BigQuery({ datasetId: bqDataset.value() }); });
3. V2 的並行與成本對等
升級觸發器到 V2 時:
- 預設情況下,V2 函式啟用並行(每個實例最多 80 個請求)。
- 如果您想維持 V1 的分數 CPU 計價(並停用並行),請在函式的選項物件中設定
cpu: "gcf_gen1"。
逐步遷移執行
步驟 1:盤點擴充功能
完整盤點擴充功能宣告、發布和記錄的所有內容,確保遷移過程中不會遺失任何東西:
- 盤點
extension.yaml:params:轉換為 Functions 參數(defineString、defineSecret等)。apis:轉換為requiresAPI(...)宣告。roles:轉換為requiresRole(...)宣告。lifecycleEvents(onInstall、onUpdate、onConfigure):轉換為afterFirstDeploy和afterRedeploy鉤子。resources:記錄所有要從第 1 代(firebase-functions/v1)轉換為第 2 代(firebase-functions/v2)的函式觸發器,包括標準事件觸發器、HTTP 處理器和任務佇列(onTaskDispatched)。
- 盤點檔案與工具:
functions/:原始碼、觸發器、輔助函式和任務佇列處理器。- 文件:
README.md、PREINSTALL.md和POSTINSTALL.md。 scripts/:記錄擴充功能隨附的任何回填、匯入或輔助腳本。
步驟 2:建立/更新 package.json
為遷移後的擴充功能程式碼建立 npm 套件(在專案根目錄或專用的工作區目錄中):
- 設定可發布的套件名稱(
name: "<package-name>")。 - 保留開發與測試相依性:保留舊版擴充功能(
functions/package.json或根目錄package.json)中所有現有的devDependencies、測試執行器(jest、ts-jest、@types/jest、mocha、@types/mocha)和測試腳本("test": "...")。不要刪除測試框架或型別定義。 - 在
dependencies(或peerDependencies,如果是建立輕量級中介軟體套件,由根目錄消費者管理執行時期版本)中宣告firebase-functions(例如^7.0.0):{ "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" }, "dependencies": { "firebase-admin": "^12.0.0", "firebase-functions": "^7.0.0" } }
步驟 3:移動函式程式碼並公開可部署的函式
將擴充功能的函式原始碼移到套件的 src/ 資料夾中:
- 保留正常的 Firebase 觸發器匯出。套件必須公開可部署的函式,讓終端使用者可以從他們的進入點(
index.ts)重新匯出。 - 記錄消費者必須透過重新匯出來部署套件函式:
注意:僅匯入(export * from "<package-name>"; // 或具名匯出: // export { syncV2, initBigQuerySync } from "<package-name>";import "<package-name>")是不夠的。Firebase CLI 只會部署從使用者的根進入檔匯出的函式。
步驟 4:將函式從第 1 代升級到第 2 代
將每個匯出的第 1 代函式觸發器(onWrite、onRequest、tasks.taskQueue().onDispatch)轉換為對應的第 2 代版本(onDocumentWritten、onRequest、onTaskDispatched,來自 firebase-functions/v2/...):
- 簽名與解構墊片:使用解構相容性墊片(
{ shimmedKey, context })保留 V1 的商業邏輯,而無需重寫函式主體。請參閱 signature-mapping.md 和 destructuring-shim.md。 - 驗證觸發器例外:如果您的擴充功能使用 v1 驗證觸發器(
auth.user().onCreate()、auth.user().onDelete()),指示代理程式即時檢查已安裝的firebase-functions套件或即時文件中是否存在第 2 代替代方案。如果這些事件尚不支援第 2 代驗證觸發器,請清楚警告使用者,並暫停或拒絕這些特定觸發器的遷移,直到 V2 替代方案可用。
步驟 5:將擴充功能參數替換為 Functions 參數
extension.yaml 中的每個參數都變成參數化設定呼叫:
- 對應參數原始型別和屬性:
- 型別
string->
defineString("PARAM_NAME", { label: "...", description: "...", default: "..." }) - 型別
secret->defineSecret("PARAM_NAME") - 型別
int->defineInt("PARAM_NAME", { label: "...", default: 123 }) - 型別
boolean->defineBoolean("PARAM_NAME", { default: true }) - 型別
select/multiSelect-> 將options對應到input:
defineString("PARAM_NAME", { input: { select: { options: [{ value: "val", label: "Val" }] } } }) validationRegex-> 對應到文字輸入選項:
defineString("PARAM_NAME", { input: { text: { validationRegex: "^[a-z]+$" } } })required: true/ 非空驗證 -> 將nonEmpty: true對應到輸入選項:
defineString("PARAM_NAME", { input: { text: { nonEmpty: true } } })
- 型別
- 在處理器內部使用
paramName.value()讀取參數值。 - 保持確切的參數名稱:永遠不要更改參數名稱(
COLLECTION_PATH、DATASET_ID等),以便現有值在.env中無縫延續。
步驟 6:遷移內部任務佇列呼叫(queue.enqueue(...))
如果您的擴充功能程式碼使用 Admin SDK(getFunctions().taskQueue(...))將任務排入自己的佇列:
- 在擴充功能執行時期,Admin SDK 會透過函式名稱加上
process.env.EXT_INSTANCE_ID來解析佇列。 - 在 npm 套件/一般程式碼庫中:完全移除第二個引數(
EXT_INSTANCE_ID)。Admin SDK 會自動鎖定目前的程式碼庫:// 之前(擴充功能執行時期): // const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`, process.env.EXT_INSTANCE_ID); // 之後(npm 套件): const queue = getFunctions().taskQueue(`locations/${region}/functions/syncBigQuery`); await queue.enqueue(taskData);
步驟 7:遷移機密
對於宣告為 type: secret 的參數:
- 明確宣告機密:
import { defineSecret } from "firebase-functions/params"; const apiKey = defineSecret("API_KEY"); - 在觸發器選項中綁定機密:
export const fn = onRequest({ secrets: [apiKey] }, handler); - 保持確切的機密名稱不變,以便現有的 Secret Manager 綁定正常運作。
步驟 8:宣告所需的 API 和 IAM 角色
將 extension.yaml 中的 apis 和 roles 替換為進入檔中的宣告式程式碼:
import { requiresAPI, requiresRole } from "firebase-functions";
requiresAPI("bigquery.googleapis.com", "需要寫入變更日誌列");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
部署時,Firebase CLI 會自動將這些角色授予受管理的執行時期服務帳戶,並啟用所需的 API。
步驟 9:轉換生命週期鉤子(afterFirstDeploy 和 afterRedeploy)
將 lifecycleEvents(onInstall、onUpdate、onConfigure)替換為 SDK 生命週期鉤子:
- 將任務佇列處理器(
initBigQuerySync、setupBigQuerySync)轉換為來自firebase-functions/v2/tasks的 V2onTaskDispatched(移除舊版的getExtensions().runtime().setProcessingState(...)呼叫)。 - 在程式碼中註冊生命週期鉤子:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/v2"; // 取代 onInstall: afterFirstDeploy({ task: { function: "runInitialSetup", body: {} } }); // 取代 onUpdate 和 onConfigure: afterRedeploy({ task: { function: "runInitialSetup", body: { reconcile: true } } }); - 確保處理器具有冪等性,並記錄手動重新執行的指令:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
步驟 10:為您的使用者記錄設定(README.md)
保留原始擴充功能在 README.md(以及 PREINSTALL.md/POSTINSTALL.md)中已記錄的任何文件、機密和設定指示,並視需要更新:
- 更新設定參考:將參考舊版
extension.yaml安裝提示或ext-*.env檔案的指示,替換為符合defineString參數的標準.env參數化設定。 - 包含重新匯出片段:確保顯示基本的根目錄重新匯出片段(
export * from "<package-name>";),讓使用者知道如何在他們的根目錄index.ts中公開函式。 - 避免不必要的樣板:如果設定在程式碼中顯而易見,或已由現有的 README 章節涵蓋,請勿產生冗餘的比較表或一般安裝步驟。
步驟 11:建置與測試驗證
- 驗證原始碼編譯:執行
npm run build(tsc)以確保src/中沒有 TypeScript 編譯錯誤。 - 驗證單元測試套件與型別定義:
- 如果擴充功能中存在現有的單元測試(
__tests__/、test/):- 確保
@types/jest(或原始測試框架型別)存在於devDependencies中,以便測試檔案能乾淨地進行型別檢查。 - 更新升級後的 V2 觸發器的測試呼叫,改為傳遞單一解構的事件物件
(({ change: mockChange, context: mockContext }),而不是位置引數(mockChange, mockContext))。 - 執行
npm test或對測試檔案進行型別檢查(npx tsc --noEmit),以驗證零回歸。
- 確保
- 如果擴充功能中存在現有的單元測試(
參考與官方資源
- 官方 Google 遷移指南:
Prepare Firebase Extensions for migration to Cloud Functions - 發布者支援聯絡方式:
- 支援電子郵件:
firebase-extensions-migrator-support-external@google.com - 支援群組訂閱:
firebase-extensions-migrator-support-external+subscribe@google.com
- 支援電子郵件:
- 解構墊片:請參閱
destructuring-shim.md 以了解事件屬性轉換的詳細資訊。 - 觸發器對應:請參閱
signature-mapping.md 以了解 V1 與 V2 觸發器定義和墊片鍵。 - 設定與參數:請參閱
configuration-migration.md 以了解runWith選項、參數和機密綁定。






