extension-to-functions-codebase

extension-to-functions-codebase

熱門

將已安裝的 Firebase 擴充功能(或擴充功能原始碼)轉換為獨立的 Cloud Functions for Firebase 程式碼庫或可發布的 npm 套件,包括將觸發器從 V1 升級到 V2,以及設定生命週期鉤子和宣告式安全性的技能。

394星標
79分支
更新於 2026/7/30
SKILL.md
唯讀
名稱
extension-to-functions-codebase
描述

將已安裝的 Firebase 擴充功能(或擴充功能原始碼)轉換為獨立的 Cloud Functions for Firebase 程式碼庫或可發布的 npm 套件,包括將觸發器從 V1 升級到 V2,以及設定生命週期鉤子和宣告式安全性的技能。

擴充功能轉換為 Functions 程式碼庫與 npm 套件遷移

概述

本技能引導代理程式將 Firebase 擴充功能儲存庫或實例遷移為以下任一形式:

  1. 獨立的 Cloud Functions for Firebase 程式碼庫firebase-functions,供終端使用者應用程式整合),或
  2. 可發布的 npm 套件/可分享的開源套件(供擴充功能發布者發布可重複使用的 V2 函式)。

它利用 Cloud Functions for Firebase 的正式版(GA)功能,以程式碼原生處理權限、相依性和生命週期鉤子,並提供使用解構相容性墊片(Destructuring Compatibility Shim)將舊版 V1 觸發器現代化為 V2 的指示。


觸發條件

當使用者要求以下事項時,啟動此技能:

  • 將已安裝的 Firebase 擴充功能遷移或轉換為獨立的 functions 程式碼庫。
  • 將擴充功能儲存庫轉換為可發布的 npm 套件(可分享的開源套件)。
  • 將擴充功能觸發器從 V1 升級到 V2。

目標遷移工作流程

開始前,請與開發者確認目標目的地:

  • 目標 A:本機 Functions 程式碼庫(終端使用者應用程式整合)

    • 輸出:原始碼放置在專案的 functions/src/ 資料夾中。
    • 設定:透過 defineStringdefineSecret 等定義參數,並放在 .env 中。
    • 部署:直接透過 firebase deploy --only functions 部署。
  • 目標 B:可發布的 npm 套件/可分享的開源套件

    • 輸出:包含匯出的 V2 函式的可重用 npm 套件。
    • 設定:package.json 中包含 exports 對應,並在 dependencies(或 peerDependencies)中宣告 firebase-functions
    • 使用方式:終端使用者安裝套件(npm i <package-name>),並在他們的 index.ts 中重新匯出函式。

開始與 Git 安全

  1. Git 狀態:開始前,確認工作區的 git 狀態是乾淨的。
  2. 原地複製:如果要在同一儲存庫內的新子目錄中複製程式碼:
    • 使用 git cp(或複製檔案並提交)將擴充功能的原始碼目錄複製到目標目錄。
    • 立即提交:
      "Copying [extension-name] extension to [directory] in preparation for rewrite"

規則與限制

1. 零本機開銷(Cloud Functions 整合)

假設 Cloud Functions for Firebase 工作負載身分、宣告式安全性和 SDK 生命週期鉤子已完全正式發布(GA)。

  • 請勿輸出指示或腳本,要求使用者執行手動的 gcloud IAM 指令或建立服務帳戶。
  • 請勿撰寫程式碼註解,指示使用者在雲端主控台中手動啟用 Google API。
  • 相反,請使用 SDK 中的宣告式 requiresAPIrequiresRole 匯入。

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:盤點擴充功能

完整盤點擴充功能宣告、發布和記錄的所有內容,確保遷移過程中不會遺失任何東西:

  1. 盤點 extension.yaml
    • params:轉換為 Functions 參數(defineStringdefineSecret 等)。
    • apis:轉換為 requiresAPI(...) 宣告。
    • roles:轉換為 requiresRole(...) 宣告。
    • lifecycleEventsonInstallonUpdateonConfigure):轉換為 afterFirstDeployafterRedeploy 鉤子。
    • resources:記錄所有要從第 1 代(firebase-functions/v1)轉換為第 2 代(firebase-functions/v2)的函式觸發器,包括標準事件觸發器、HTTP 處理器和任務佇列(onTaskDispatched)。
  2. 盤點檔案與工具
    • functions/:原始碼、觸發器、輔助函式和任務佇列處理器。
    • 文件:README.mdPREINSTALL.mdPOSTINSTALL.md
    • scripts/:記錄擴充功能隨附的任何回填、匯入或輔助腳本。

步驟 2:建立/更新 package.json

為遷移後的擴充功能程式碼建立 npm 套件(在專案根目錄或專用的工作區目錄中):

  • 設定可發布的套件名稱(name: "<package-name>")。
  • 保留開發與測試相依性:保留舊版擴充功能(functions/package.json 或根目錄 package.json)中所有現有的 devDependencies、測試執行器(jestts-jest@types/jestmocha@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/ 資料夾中:

  1. 保留正常的 Firebase 觸發器匯出。套件必須公開可部署的函式,讓終端使用者可以從他們的進入點(index.ts)重新匯出。
  2. 記錄消費者必須透過重新匯出來部署套件函式:
    export * from "<package-name>";
    // 或具名匯出:
    // export { syncV2, initBigQuerySync } from "<package-name>";
    
    注意:僅匯入(import "<package-name>")是不夠的。Firebase CLI 只會部署從使用者的根進入檔匯出的函式。

步驟 4:將函式從第 1 代升級到第 2 代

將每個匯出的第 1 代函式觸發器(onWriteonRequesttasks.taskQueue().onDispatch)轉換為對應的第 2 代版本(onDocumentWrittenonRequestonTaskDispatched,來自 firebase-functions/v2/...):

  1. 簽名與解構墊片:使用解構相容性墊片({ shimmedKey, context })保留 V1 的商業邏輯,而無需重寫函式主體。請參閱 signature-mapping.mddestructuring-shim.md
  2. 驗證觸發器例外:如果您的擴充功能使用 v1 驗證觸發器(auth.user().onCreate()auth.user().onDelete()),指示代理程式即時檢查已安裝的 firebase-functions 套件或即時文件中是否存在第 2 代替代方案。如果這些事件尚不支援第 2 代驗證觸發器,請清楚警告使用者,並暫停或拒絕這些特定觸發器的遷移,直到 V2 替代方案可用。

步驟 5:將擴充功能參數替換為 Functions 參數

extension.yaml 中的每個參數都變成參數化設定呼叫:

  1. 對應參數原始型別和屬性:
    • 型別 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 } } })
  2. 在處理器內部使用 paramName.value() 讀取參數值。
  3. 保持確切的參數名稱:永遠不要更改參數名稱(COLLECTION_PATHDATASET_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 的參數:

  1. 明確宣告機密:
    import { defineSecret } from "firebase-functions/params";
    const apiKey = defineSecret("API_KEY");
    
  2. 在觸發器選項中綁定機密:
    export const fn = onRequest({ secrets: [apiKey] }, handler);
    
  3. 保持確切的機密名稱不變,以便現有的 Secret Manager 綁定正常運作。

步驟 8:宣告所需的 API 和 IAM 角色

extension.yaml 中的 apisroles 替換為進入檔中的宣告式程式碼:

import { requiresAPI, requiresRole } from "firebase-functions";

requiresAPI("bigquery.googleapis.com", "需要寫入變更日誌列");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");

部署時,Firebase CLI 會自動將這些角色授予受管理的執行時期服務帳戶,並啟用所需的 API。

步驟 9:轉換生命週期鉤子(afterFirstDeployafterRedeploy

lifecycleEventsonInstallonUpdateonConfigure)替換為 SDK 生命週期鉤子:

  1. 將任務佇列處理器(initBigQuerySyncsetupBigQuerySync)轉換為來自 firebase-functions/v2/tasks 的 V2 onTaskDispatched(移除舊版的 getExtensions().runtime().setProcessingState(...) 呼叫)。
  2. 在程式碼中註冊生命週期鉤子:
    import { afterFirstDeploy, afterRedeploy } from "firebase-functions/v2";
    
    // 取代 onInstall:
    afterFirstDeploy({
      task: {
        function: "runInitialSetup",
        body: {}
      }
    });
    
    // 取代 onUpdate 和 onConfigure:
    afterRedeploy({
      task: {
        function: "runInitialSetup",
        body: { reconcile: true }
      }
    });
    
  3. 確保處理器具有冪等性,並記錄手動重新執行的指令:
    firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
    firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
    

步驟 10:為您的使用者記錄設定(README.md

保留原始擴充功能在 README.md(以及 PREINSTALL.mdPOSTINSTALL.md)中已記錄的任何文件、機密和設定指示,並視需要更新:

  1. 更新設定參考:將參考舊版 extension.yaml 安裝提示或 ext-*.env 檔案的指示,替換為符合 defineString 參數的標準 .env 參數化設定。
  2. 包含重新匯出片段:確保顯示基本的根目錄重新匯出片段(export * from "<package-name>";),讓使用者知道如何在他們的根目錄 index.ts 中公開函式。
  3. 避免不必要的樣板:如果設定在程式碼中顯而易見,或已由現有的 README 章節涵蓋,請勿產生冗餘的比較表或一般安裝步驟。

步驟 11:建置與測試驗證

  1. 驗證原始碼編譯:執行 npm run buildtsc)以確保 src/ 中沒有 TypeScript 編譯錯誤。
  2. 驗證單元測試套件與型別定義
    • 如果擴充功能中存在現有的單元測試(__tests__/test/):
      • 確保 @types/jest(或原始測試框架型別)存在於 devDependencies 中,以便測試檔案能乾淨地進行型別檢查。
      • 更新升級後的 V2 觸發器的測試呼叫,改為傳遞單一解構的事件物件
        ({ change: mockChange, context: mockContext }),而不是位置引數 (mockChange, mockContext))。
      • 執行 npm test 或對測試檔案進行型別檢查(npx tsc --noEmit),以驗證零回歸。

參考與官方資源