Questa guida mostra come eseguire la migrazione delle estensioni dall'ambiente deprecato Firebase Extensions a una funzione che gli utenti installano ed eseguono il deployment nel proprio Cloud Functions per Firebase (2ª gen.) codebase.
Questo è il percorso di migrazione consigliato. Firebase manterrà un elenco di estensioni con equivalenti npm ufficiali; questa guida ti illustrerà la procedura per crearne una.
In questa guida, l'estensione Stream Firestore to BigQuery (firestore-bigquery-export) viene utilizzata come esempio. Ogni sezione termina con un esempio pratico che mostra l'aspetto dell'estensione prima della migrazione e l'aspetto dopo, come pacchetto @firebase/firestore-bigquery-export.
Iscriviti per ricevere maggiori informazioni e assistenza sulla migrazione delle estensioni
Se hai domande su come eseguire la migrazione da Firebase Extensions, puoi contattarci all'indirizzo firebase-extensions-migrator-support-external@google.com. Invieremo un'email a questo gruppo anche quando aggiorneremo la guida con ulteriori informazioni su packaging, test e distribuzione delle funzioni di 2ª gen.
Per partecipare a questo gruppo, invia un messaggio a firebase-extensions-migrator-support-external+subscribe@google.com, che risponderà con un'email di richiesta di iscrizione. Devi rispondere a questa email, non fare clic sul pulsante "Partecipa a questo gruppo".
Prima di iniziare
Per completare questa migrazione, utilizzerai le seguenti funzionalità di Cloud Functions:
Configurazione con parametri. Ogni parametro dichiarato in
extension.yamldiventa un parametro definito nel codice del pacchetto.Ruoli IAM dichiarativi e API richieste. Ogni ruolo dichiarato in
extension.yamldiventa una chiamatarequiresRole(...)e ogni API diventa una chiamatarequiresAPI(...)nel codice della funzione. Al momento del deployment, la Firebase CLI concede i ruoli dichiarati a un account di servizio di runtime gestito e abilita le API dichiarate per tuo conto.Eventi del ciclo di vita per i codebase Cloud Functions. Cloud Functions i codebase ora supportano gli eventi del ciclo di vita analoghi a Firebase Extensions. Dichiara la configurazione al momento dell'installazione e dell'aggiornamento con gli hook del ciclo di vita
afterFirstDeploy(...)eafterRedeploy(...). Questi sostituisconolifecycleEventsdichiarati inextension.yaml.
Inventario dell'estensione
Inizia creando un inventario dell'estensione: un elenco completo di tutto ciò che l'estensione dichiara, distribuisce e documenta, in modo che ogni comportamento abbia una destinazione definita nella funzione di 2ª gen. e che non vada perso nulla durante la migrazione.
Esamina ciascuno dei seguenti elementi e prendi nota di ciò che trovi:
extension.yaml, che dichiara i parametri, le funzioni, gli eventi, i ruoli IAM, le API richieste, i secret e gli hook del ciclo di vita.functions/, che contiene il codice della funzione, le dipendenze, la configurazione della build, i trigger e le funzioni della coda di attività.README.md,PREINSTALL.mdePOSTINSTALL.md, che contengono passaggi di configurazione, avvisi e note di fatturazione.scripts/, che contiene tutte le utilità di importazione, backfill, IAM, riparazione o migrazione e qualsiasi altro strumento fornito insieme all'estensione.
Poi, per ogni elemento in
extension.yaml, decidi dove
inserirlo nel pacchetto npm:
Converti la configurazione utente in parametri Cloud Functions (sezione 5).
Converti i secret in Cloud Functions secret (sezione 6).
Converti i ruoli IAM in dichiarazioni
requiresRole(...)(sezione 8).Converti le API di Google richieste in dichiarazioni
requiresAPI(...)ove appropriato (sezione 8).Converti gli hook di installazione e aggiornamento in dichiarazioni
afterFirstDeploy(...)eafterRedeploy(...)(sezione 8).
Esempio pratico: Stream Firestore to BigQuery
La lettura di firestore-bigquery-export/extension.yaml e functions/ produce questo inventario:
| In extension.yaml | Conteggio / valore | Dove si trova |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Cloud Functions parametri (sezione 5) |
| apis | bigquery.googleapis.com | requiresAPI(...) (sezione 7) |
| roles | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (sezione 7) |
| resources | 1 trigger evento (fsexportbigquery) + funzioni della coda di attività (initBigQuerySync, setupBigQuerySync) | Funzioni del pacchetto esportato (sezione 3) |
| lifecycleEvents | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (sezione 9) |
| scripts/ | import/ (backfill), gen-schema-view/ | Mantenuti come script (non trattati qui) |
L'estensione non dichiara alcun tipo: parametri secret, quindi non è necessario eseguire la migrazione nella sezione 6 di questa guida. Il trigger evento è già di 2ª gen.; solo le funzioni della coda di attività sono ancora di 1ª gen. (rilevanti nella sezione 3).
Aggiorna package.json
Aggiorna il file package.json dell'estensione. Se stai eseguendo la migrazione di un'estensione, può essere la package.json principale. Se stai eseguendo la migrazione di molte estensioni in un repository, assegna a ogni estensione il proprio pacchetto.
Versioni SDK minime. Dichiara firebase-functions >= 7.3 e firebase-admin >= 14.2.0 come dipendenze. Dichiara anche la tua versione di firebase-functions come dipendenza peer.
{
"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"
}
}
Dichiara firebase-functions come dipendenza peer oltre alla dipendenza normale, in modo che il progetto Cloud Functions degli utenti abbia la stessa versione dell'SDK con cui è stata scritta la libreria.
Esempio pratico: Stream Firestore to BigQuery
Prima. Il file functions/package.json dell'estensione è privato, nomina l'ID dell'estensione e dichiara firebase-functions come dipendenza diretta:
{
"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"
}
}
Dopo. Un pacchetto pubblicabile: nome con ambito, una mappa di esportazioni e firebase-functions spostato in 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"
}
}
Esegui l'upgrade delle funzioni da 1ª gen. a 2ª gen.
Se l'estensione esporta ancora funzioni di 1ª gen., converti ogni funzione nel suo equivalente di 2ª gen. Importa dai moduli firebase-functions/... e passa le impostazioni di runtime nelle opzioni della funzione.
Puoi ridurre al minimo gli sforzi di riscrittura con la destrutturazione degli eventi con patch di 2ª gen. ed evitare di riscrivere la logica della funzione perché l'SDK di 2ª gen. ora espone i parametri V1 come campi nell'oggetto evento, consentendoti di utilizzare parametri destrutturati/denominati e mantenere invariata la logica di business .
Prima. 1ª gen.:
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);
});
Dopo. 2ª gen.:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
Per un elenco completo delle differenze tra le funzioni di 1ª gen. e 2ª gen., consulta il confronto tra le versioniCloud Functions.
Converti i parametri e i secret dell'estensione
Converti i parametri
Ogni parametro dichiarato in extension.yaml diventa un Cloud Functions
parametro.
Converti le letture dirette dell'ambiente:
const collectionPath = process.env.COLLECTION_PATH;
in parametri di 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);
}
);
Utilizza collectionPath.value() per leggere la stringa all'interno di un gestore; utilizza
collectionPath direttamente dove è previsto un segnaposto, ad esempio un percorso di trigger della funzione.
La Firebase CLI rileva i parametri e legge i relativi valori da .env, .env.projectId, o richiede agli utenti durante il deployment. Mantieni gli stessi nomi dei parametri, in modo che i valori di un'installazione esistente vengano trasferiti.
È importante non modificare in alcun modo i nomi dei parametri dichiarati nel codice. La migrazione dell'estensione manterrà automaticamente il valore del parametro dell'utente finale esistente, ma solo se i nomi rimangono invariati.
Esempio pratico: Stream Firestore to BigQuery
Prima. Un parametro dichiarato in extension.yaml, letto come variabile di ambiente non elaborata in config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Dopo. Un defineString; la CLI lo rileva e legge da .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} }
}),
Il nome del parametro non è cambiato, quindi un file .env esistente continua a funzionare.
Converti i secret
In extension.yaml, dichiara i secret con type: secret. Il runtime di Extensions li archivia e li associa, in modo che il codice dell'estensione possa leggere direttamente process.env.PARAM_NAME. In un codebase Cloud Functions tipico, dichiari e associ ogni secret in modo esplicito:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
Una volta eseguita la migrazione dell'estensione a un pacchetto/kit npm, i riferimenti ai secret verranno gestiti nel file .env dell'utente finale. È importante non modificare in alcun modo i nomi dei secret dichiarati nel codice. Durante la migrazione, i secret dell'utente finale verranno migrati di conseguenza.
Esempio pratico: Trigger Email From Cloud Firestore
Prima. MAIL_COLLECTION e SMTP_PASSWORD letti come variabili di ambiente non elaborate in 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,
Dopo. Un defineString e un defineSecret; la CLI li rileva entrambi e legge da .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();
// ...
}
);
Esegui la migrazione delle chiamate interne alla coda di attività
Alcune estensioni accodano il lavoro alle proprie code di attività dall'interno del loro codice funzione, utilizzando Firebase Admin SDK. Questo è diverso dalla ricezione di un'attività inviata (trattata nelle sezioni Esegui l'upgrade delle funzioni e Converti gli hook del ciclo di vita). Qui il codice è il produttore che chiama queue.enqueue(...).
Le versioni precedenti di Admin SDK richiedevano che le estensioni passassero il proprio ID istanza dell'estensione come secondo parametro per indirizzare una funzione della coda di attività in nella stessa estensione. A partire da `firebase-admin` 14.2.0, questo non è né richiesto né consigliato. L'API Task Queues ora indirizzerà le code di attività nello stesso contesto (ad es. estensione) per impostazione predefinita. È sicuro e consigliato rimuovere questo parametro nel codice sia come estensione sia come funzioni autonome. La rimozione di questo parametro garantisce la portabilità e la compatibilità con le versioni successive.
Tutto il resto della chiamata enqueue, il percorso della risorsa locations/region/functions/name, il payload dell'attività e la logica di ripetizione, rimangono invariati.
Per ulteriori dettagli sull'accodamento delle funzioni con Cloud Tasks, consulta /docs/functions/task-functions.
Prima. Estensione di 1ª gen.
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);
Dopo. Estensione di 2ª gen.
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);
Se la chiamata enqueue indirizza un codebase con prefisso, anche il nome della funzione rilevato ha il prefisso (ad esempio, orders-syncBigQuery).
Dichiara le API e i ruoli IAM richiesti
Sposta i requisiti IAM e API dell'estensione da extension.yaml al codice:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
Con la sicurezza dichiarativa, la Firebase CLI crea o aggiorna un account di servizio di runtime gestito per il codebase e gli concede l'unione di tutti i ruoli dichiarati. Documenta per gli utenti che tutte le funzioni nel codebase vengono eseguite con questi ruoli, a meno che l'API finale non supporti un modello più ristretto.
Esempio pratico: Stream Firestore to BigQuery
Prima. Dichiarato in extension.yaml; il runtime di Extensions ha abilitato l'API e ha concesso i ruoli a un account gestito:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Dopo. Dichiarato nel codice con requiresAPI e 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");
Converti gli hook del ciclo di vita
Se l'estensione chiama getExtensions().runtime(), ad esempio
setProcessingState o setFatalError, elimina queste chiamate, perché genereranno
un errore se chiamate da una funzione di 2ª gen. con deployment normale. Lo stato del ciclo di vita è ora determinato da afterFirstDeploy e afterRedeploy dove non viene utilizzato questo monitoraggio dello stato.
Firebase Extensions può eseguire la configurazione quando un utente installa, aggiorna o riconfigura un'estensione. Nel pacchetto npm, dichiara le azioni del ciclo di vita equivalenti nel codice.
Per la configurazione una tantum:
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: {}
}
});
Per gli aggiornamenti di configurazione o codice:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Rendi idempotenti le azioni del ciclo di vita. Gli utenti potrebbero doverle eseguire di nuovo manualmente se l'invio o l'esecuzione non riesce:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Esempio pratico: Stream Firestore to BigQuery
Prima. lifecycleEvents in extension.yaml, gestiti dal runtime di Extensions:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Dopo. Dichiarato nel codice; l'attività esegue il provisioning di BigQuery al primo deployment:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
Il provisioning è idempotente, quindi una nuova esecuzione riconcilia il set di dati, la tabella e le viste. Gli utenti possono eseguire di nuovo manualmente con firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Documenta la configurazione per gli utenti
Scrivi un file
READMEdel pacchetto che spieghi, come minimo:I valori
.envrichiesti dal pacchetto.I secret richiesti dal pacchetto e come eseguire la migrazione dei valori dei secret esistenti.
I ruoli IAM dichiarati dal pacchetto con
requiresRole(...).Le API di Google abilitate o richieste dal pacchetto.
Gli hook del ciclo di vita dichiarati dal pacchetto e come eseguirli di nuovo manualmente.
Note di fatturazione.
Cosa è cambiato rispetto all'estensione originale.
Esempio pratico: Stream Firestore to BigQuery
Il file README del pacchetto include una tabella concreta "Cosa è cambiato":
| Problema | Come estensione | Come @firebase/firestore-bigquery-export |
|---|---|---|
| Configurazione | Parametri dell'estensione | Parametri delle funzioni tramite .env |
| IAM | Concesso da Extensions | requiresRole(...), applicato al deployment |
| Provisioning | Attività del ciclo di vita di Extensions | Attività afterFirstDeploy / afterRedeploy |
| Nomi delle funzioni | ext-instanceId-fsexportbigquery | fsexportbigquery (con prefisso facoltativo) |
Testa la funzione di 2ª gen.
Ora dovresti avere una funzione di 2ª gen. che, una volta eseguito il deployment, si comporterà in modo identico a una nuova installazione dell'estensione. L'ultimo passaggio consiste nel verificare e correggere eventuali problemi introdotti accidentalmente durante la procedura.
Assicurati di utilizzare
firebase-tools
>= 15.24.0 ed esegui il deployment della funzione di 2ª gen. convertita in un progetto di test
con le risorse appropriate per testarne il comportamento. Se hai già configurato un progetto di test per testare l'estensione, utilizza il comando:
firebase deploy --only functions
Dopo aver inserito questo comando, compila la procedura guidata risultante che ti chiede i valori dei parametri nello stesso modo in cui avresti compilato il modulo di installazione nella Firebase console per l'estensione.
Esempio pratico: Stream Firestore to BigQuery
Verifichiamo la sincronizzazione end-to-end di Cloud Firestore con BigQuery:
- Nella console Cloud Firestore, crea la raccolta impostata come COLLECTION_PATH (users) se non esiste già.
- Crea un documento denominato bigquery-mirror-test contenente tutti i campi con tutti i valori.
- Nella console BigQuery, esegui una query della tabella del log delle modifiche non elaborato. Dovrebbe contenere una singola riga che registra la creazione del documento:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Esegui una query della vista più recente, che dovrebbe restituire l'evento di modifica più recente per
l'unico documento presente:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Elimina il documento
bigquery-mirror-testin Cloud Firestore. Scompare dalla vista più recente e alla tabella del log delle modifiche non elaborato viene aggiunto un eventoDELETE.
Puoi ispezionare la cronologia completa di un singolo documento con:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Differenze rispetto al test dell'estensione:
- Il trigger viene eseguito il deployment come
fsexportbigquery(con prefisso codebase facoltativo), nonext-<instanceId>-fsexportbigquery. Cerca questo nome nella Cloud Functions dashboard e nei log. - Il codice verrà ora eseguito in Firebase Local Emulator Suite come funzioni normali. Puoi impostare il valore dei parametri da utilizzare nell'emulatore con
.env.local. Puoi anche eseguire test delle unità del codice utilizzando l' SDK firebase-functions-test come descritto in Test delle unità di Cloud Functions - Il provisioning non è più gestito dal runtime di Extensions. Se la tabella del log delle modifiche
non è presente dopo il deployment, esegui di nuovo manualmente l'attività di configurazione:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. L'attività è idempotente, quindi la sua esecuzione di nuovo riconcilia il set di dati, la tabella e le viste. - I valori dei parametri provengono da
.envanziché dal modulo di installazione, quindi le esecuzioni di nuovo difirebase deploynon sono interattive una volta completato.env.