Este guia mostra como migrar suas extensões do ambiente Firebase Extensions descontinuado para uma função que os usuários instalam e implantam na própria base de código do Cloud Functions para Firebase (2ª geração).
Esse é o caminho de migração recomendado. Firebase vai manter uma lista de extensões com equivalentes oficiais do npm. Este guia vai orientar você na criação da sua.
Neste guia, a extensão Stream Firestore to BigQuery (firestore-bigquery-export) é usada como exemplo. Cada seção termina com um exemplo prático que mostra como era a extensão antes da migração e como ela fica depois, como o pacote @firebase/firestore-bigquery-export.
Inscreva-se para receber mais informações e ajuda na migração de extensões
Se você tiver dúvidas sobre como migrar de Firebase Extensions, entre em contato pelo e-mail firebase-extensions-migrator-support-external@google.com. Também vamos enviar e-mails para esse grupo à medida que atualizarmos o guia com mais informações sobre como empacotar, testar e distribuir suas funções de 2ª geração.
Para participar desse grupo, envie uma mensagem para firebase-extensions-migrator-support-external+subscribe@google.com, que vai responder com um e-mail de solicitação de participação. Responda a esse e-mail, não clique no botão "Participar deste grupo".
Antes de começar
Para concluir essa migração, use os seguintes recursos de Cloud Functions:
Configuração parametrizada. Cada parâmetro declarado em
extension.yamlse torna um parâmetro definido no código do pacote.Papéis declarativos do IAM e APIs necessárias. Cada papel declarado em
extension.yamlse torna uma chamadarequiresRole(...), e cada API se torna uma chamadarequiresAPI(...)no código da função. No momento da implantação, a Firebase CLI concede os papéis declarados a uma conta de serviço de ambiente de execução gerenciada e ativa as APIs declaradas em seu nome.Eventos de ciclo de vida para bases de código Cloud Functions. Cloud Functions bases de código agora oferecem suporte a eventos de ciclo de vida análogos a Firebase Extensions. Declare a configuração de instalação e atualização com os hooks de ciclo de vida
afterFirstDeploy(...)eafterRedeploy(...). Eles substituem oslifecycleEventsdeclarados emextension.yaml.
Inventário da extensão
Comece fazendo um inventário da extensão: uma lista completa de tudo o que a extensão declara, envia e documenta, para que cada comportamento tenha um destino definido na função de 2ª geração e nada seja perdido na migração.
Analise cada um dos itens a seguir e anote o que encontrar:
extension.yaml, que declara parâmetros, funções, eventos, papéis do IAM, APIs necessárias, secrets e hooks de ciclo de vida.functions/, que contém o código da função, dependências, configuração de build, acionadores e funções da fila de tarefas.README.md,PREINSTALL.mdePOSTINSTALL.md, que contêm etapas de configuração, avisos e observações de faturamento.scripts/, que contém utilitários de importação, preenchimento, IAM, reparo ou migração e outras ferramentas enviadas com a extensão.
Em seguida, para cada item em
extension.yaml, decida para onde ele
vai no pacote npm:
Converter a configuração do usuário em Cloud Functions parâmetros (seção 5).
Converter secrets em Cloud Functions secrets (seção 6).
Converter papéis do IAM em declarações
requiresRole(...)(seção 8).Converter as APIs do Google necessárias em declarações
requiresAPI(...), quando apropriado (seção 8).Converter hooks de instalação e atualização em declarações
afterFirstDeploy(...)eafterRedeploy(...)(seção 8).
Exemplo prático: Stream Firestore to BigQuery
A leitura de firestore-bigquery-export/extension.yaml e functions/ produz este inventário:
| Em extension.yaml | Contagem / valor | Para onde ele vai |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Cloud Functions parâmetros (seção 5) |
| apis | bigquery.googleapis.com | requiresAPI(...) (seção 7) |
| roles | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (seção 7) |
| resources | 1 acionador de evento (fsexportbigquery) + funções da fila de tarefas (initBigQuerySync, setupBigQuerySync) | Funções de pacote exportadas (seção 3) |
| lifecycleEvents | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (seção 9) |
| scripts/ | import/ (preenchimento), gen-schema-view/ | Mantido como scripts (fora do escopo aqui) |
A extensão não declara nenhum parâmetro de tipo: secret. Portanto, não há nada para migrar na seção 6 deste guia. O acionador de eventos já é de 2ª geração. Somente as funções da fila de tarefas ainda são de 1ª geração (relevante na seção 3).
Atualizar package.json
Atualize o arquivo package.json da extensão. Se você estiver migrando uma extensão, esse poderá ser o package.json raiz. Se você estiver migrando muitas extensões em um repositório, atribua a cada extensão um pacote próprio.
Versões mínimas do SDK. Declare firebase-functions >= 7.3 e firebase-admin >= 14.2.0 como dependências. Declare também sua versão do firebase-functions como uma dependência de mesmo nível.
{
"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"
}
}
Declare o firebase-functions como uma dependência de mesmo nível, além da dependência normal , para que o projeto Cloud Functions dos usuários tenha a mesma versão do SDK com que sua biblioteca foi escrita.
Exemplo prático: Stream Firestore to BigQuery
Antes. O functions/package.json da extensão é particular, nomeia o ID da extensão e declara o firebase-functions como uma dependência direta:
{
"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"
}
}
Depois. Um pacote publicável: nome com escopo, um mapa de exportações e firebase-functions movido para 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"
}
}
Fazer upgrade de funções da 1ª para a 2ª geração
Se a extensão ainda exportar funções de 1ª geração, converta cada função para o equivalente de 2ª geração. Importe dos módulos firebase-functions/... e transmita as configurações de ambiente de execução nas opções de função.
É possível minimizar os esforços de reescrita com a desestruturação de eventos corrigidos de 2ª geração e evitar a reescrita da lógica de função porque o SDK de 2ª geração agora expõe os parâmetros da V1 como campos no objeto de evento, permitindo que você use parâmetros desestruturados/nomeados e mantenha a lógica de negócios inalterada.
Antes. 1ª geração:
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);
});
Depois. 2ª geração:
import { onDocumentWritten } from "firebase-functions/firestore";
export const syncV2 = onDocumentWritten(
{ document: "{collectionId}/{documentId}" },
async ({change,context}) =>
await handleWrite(change.before, change.after, context.params);
);
Consulte a Cloud Functions comparação de versões para conferir uma lista abrangente de diferenças entre as funções de 1ª e 2ª geração.
Converter parâmetros e secrets de extensão
Converter parâmetros
Cada parâmetro declarado em extension.yaml se torna um Cloud Functions
parâmetro.
Converter leituras diretas do ambiente:
const collectionPath = process.env.COLLECTION_PATH;
em Cloud Functions parâmetros:
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);
}
);
Use collectionPath.value() para ler a string dentro de um gerenciador. Use
collectionPath diretamente quando um marcador for esperado, como um caminho de acionador de função
.
A Firebase CLI descobre os parâmetros e lê os valores deles em .env, .env.projectId, ou solicita aos usuários durante a implantação. Mantenha os mesmos nomes de parâmetros para que os valores de uma instalação atual sejam transferidos.
É importante não alterar os nomes de parâmetros declarados no código de forma alguma. A migração de extensão vai preservar o valor do parâmetro do usuário final automaticamente, mas apenas quando os nomes estiverem inalterados.
Exemplo prático: Stream Firestore to BigQuery
Antes. Um parâmetro declarado em extension.yaml, lido como uma variável de ambiente bruta em config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Depois. Um defineString; a CLI o descobre e lê de .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} }
}),
O nome do parâmetro não foi alterado, então um .env atual continua funcionando.
Converter secrets
Em extension.yaml, declare secrets com type: secret. O ambiente de execução das extensões os armazena e vincula, para que o código da extensão possa ler process.env.PARAM_NAME diretamente. Em uma base de código típica do Cloud Functions você declara e vincula cada secret explicitamente:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
Depois que a extensão for migrada para um pacote/kit npm, as referências de secret serão gerenciadas no arquivo .env do usuário final. É importante não
alterar os nomes de secret declarados no código de forma alguma. Durante a migração, os secrets do usuário final serão migrados de acordo.
Exemplo prático: acionar e-mail de Cloud Firestore
Antes. MAIL_COLLECTION e SMTP_PASSWORD lidos como variáveis de ambiente brutas em 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,
Depois. Um defineString e um defineSecret; a CLI descobre os dois e lê de .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();
// ...
}
);
Migrar chamadas internas da fila de tarefas
Algumas extensões enfileiram o trabalho nas próprias filas de tarefas de dentro do código da função, usando o Firebase Admin SDK. Isso é diferente de receber uma tarefa enviada (abordada nas seções Fazer upgrade de funções e Converter hooks de ciclo de vida). Aqui, o código é o produtor que chama queue.enqueue(...).
As versões anteriores do Admin SDK exigiam que as extensões transmitissem o próprio ID da instância de extensão como um segundo parâmetro para segmentar uma função da fila de tarefas em a mesma extensão. A partir do `firebase-admin` 14.2.0, isso não é necessário nem recomendado. A API Task Queue agora vai segmentar filas de tarefas no mesmo contexto (por exemplo, extensão) por padrão. É seguro e recomendado remover esse parâmetro no código como uma extensão e como funções independentes. A remoção desse parâmetro garante a portabilidade e a compatibilidade com versões futuras.
Todo o resto sobre a chamada de enfileiramento (o caminho do recurso de locations/region/functions/name, o payload da tarefa e a lógica de repetição) permanece o mesmo.
Consulte /docs/functions/task-functions para mais detalhes sobre funções de enfileiramento com Cloud Tasks.
Antes. Extensão de 1ª geração
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);
Depois. Extensão de 2ª geração
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 a chamada de enfileiramento segmentar uma base de código prefixada, o nome da função descoberta também será prefixado (por exemplo, orders-syncBigQuery).
Declarar APIs e papéis do IAM necessários
Mova os requisitos de IAM e API da extensão de extension.yaml para o código:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
Com a segurança declarativa, a Firebase CLI cria ou atualiza uma conta de serviço de ambiente de execução gerenciada para a base de código e concede a ela a união de todos os papéis declarados. Documente para os usuários que todas as funções na base de código são executadas com esses papéis, a menos que a API final ofereça suporte a um modelo mais restrito.
Exemplo prático: Stream Firestore to BigQuery
Antes. Declarado em extension.yaml; o ambiente de execução das extensões ativou a API e concedeu os papéis a uma conta gerenciada:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Depois. Declarado no código com 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");
Converter hooks de ciclo de vida
Se a extensão chamar getExtensions().runtime(), por exemplo
setProcessingState ou setFatalError, exclua essas chamadas, porque elas vão gerar
um erro se forem chamadas de uma função de 2ª geração implantada normalmente. O estado do ciclo de vida agora é controlado por afterFirstDeploy e afterRedeploy, em que esse rastreamento de estado não é usado.
Firebase Extensions podem executar a configuração quando um usuário instala, atualiza ou reconfigura uma extensão. No pacote npm, declare ações de ciclo de vida equivalentes no código.
Para configuração única:
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: {}
}
});
Para atualizações de configuração ou código:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Torne as ações de ciclo de vida idempotentes. Os usuários podem precisar executá-las novamente manualmente se o envio ou a execução falharem:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Exemplo prático: Stream Firestore to BigQuery
Antes. lifecycleEvents em extension.yaml, controlado pelo ambiente de execução das extensões:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Depois. Declarado no código; a tarefa provisiona BigQuery na primeira implantação:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
O provisionamento é idempotente, então uma nova execução reconcilia o conjunto de dados, a tabela e as visualizações. Os usuários podem executar novamente manualmente com firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Documentar a configuração para os usuários
Escreva um
READMEdo pacote que explique, no mínimo:Os valores
.envexigidos pelo pacote.Os secrets exigidos pelo pacote e como migrar os valores de secret atuais.
Os papéis do IAM declarados pelo pacote com
requiresRole(...).As APIs do Google que o pacote ativa ou exige.
Os hooks de ciclo de vida declarados pelo pacote e como executá-los novamente manualmente.
Observações de faturamento.
O que mudou em comparação com a extensão original.
Exemplo prático: Stream Firestore to BigQuery
O README do pacote envia uma tabela concreta "o que mudou":
| Problema | Como a extensão | Como @firebase/firestore-bigquery-export |
|---|---|---|
| Configuração | Parâmetros de extensão | Parâmetros de funções via .env |
| IAM | Concedido por extensões | requiresRole(...), aplicado na implantação |
| Provisionamento | Tarefa de ciclo de vida por extensões | Tarefa afterFirstDeploy / afterRedeploy |
| Nomes de funções | ext-instanceId-fsexportbigquery | fsexportbigquery (prefixado opcionalmente) |
Testar a função de 2ª geração
Agora você tem uma função de 2ª geração que, quando implantada, se comporta de maneira idêntica a uma nova instalação da extensão. A última etapa é verificar e corrigir problemas introduzidos acidentalmente ao longo do caminho.
Verifique se você está usando
firebase-tools
>= 15.24.0 e implante a função de 2ª geração convertida em um projeto de teste
com os recursos adequados para testar o comportamento dela. Se você já tiver uma configuração de projeto de teste para testar a extensão, use o comando:
firebase deploy --only functions
Depois de inserir esse comando, preencha o assistente resultante solicitando valores de parâmetro da mesma forma que você teria preenchido o formulário de instalação no Firebase console para a extensão.
Exemplo prático: Stream Firestore to BigQuery
Verificamos a sincronização do Cloud Firestore com o BigQuery de ponta a ponta:
- No console do Cloud Firestore, crie a coleção definida como COLLECTION_PATH (usuários) se ela ainda não existir.
- Crie um documento chamado bigquery-mirror-test que contenha campos com valores definidos por você.
- No console do BigQuery, consulte a tabela de registro de alterações bruta. Ela precisa conter uma única linha registrando a criação do documento:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Consulte a visualização mais recente, que retornará o evento de alteração mais recente para
o único documento atual:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Exclua o documento
bigquery-mirror-testem Cloud Firestore. Ele desaparece da visualização mais recente, e um eventoDELETEé anexado à tabela de registro de alterações bruta.
É possível inspecionar o histórico completo de um único documento com:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Diferenças do teste da extensão:
- O acionador é implantado como
fsexportbigquery(prefixado opcionalmente pela base de código), nãoext-<instanceId>-fsexportbigquery. Procure esse nome no Cloud Functions painel e nos registros. - O código agora será executado no Firebase Local Emulator Suite como funções normais. É possível definir o valor dos parâmetros a serem usados no emulador com
.env.local. Também é possível testar o código da unidade usando o SDK firebase-functions-test, conforme descrito em Teste de unidade do Cloud Functions - O provisionamento não é mais controlado pelo ambiente de execução das extensões. Se a tabela de registro de alterações
estiver ausente após a implantação, execute a tarefa de configuração manualmente:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. A tarefa é idempotente, então a execução reconcilia o conjunto de dados, a tabela e as visualizações. - Os valores de parâmetro vêm de
.envem vez do formulário de instalação. Portanto, as novas execuções defirebase deploynão são interativas quando.envestá concluído.