Panduan ini menunjukkan cara memigrasikan ekstensi dari lingkungan Firebase Extensions yang tidak digunakan lagi ke fungsi yang diinstal dan di-deploy oleh pengguna di codebase Cloud Functions untuk Firebase (generasi ke-2) mereka sendiri.
Ini adalah jalur migrasi yang direkomendasikan. Firebase akan mempertahankan daftar ekstensi dengan npm resmi yang setara. Panduan ini akan memandu Anda membuat ekstensi Anda sendiri.
Di seluruh panduan ini, ekstensi Stream Firestore to BigQuery (firestore-bigquery-export) digunakan sebagai contoh. Setiap bagian diakhiri dengan Contoh yang berfungsi yang menunjukkan tampilan ekstensi tersebut sebelum migrasi, dan tampilannya setelah migrasi, sebagai paket @firebase/firestore-bigquery-export.
Mendaftar untuk mendapatkan informasi dan bantuan selengkapnya tentang migrasi ekstensi
Jika ada pertanyaan tentang cara bermigrasi dari Firebase Extensions, Anda dapat menghubungi kami di firebase-extensions-migrator-support-external@google.com. Kami juga akan mengirim email ke grup ini saat kami memperbarui panduan dengan informasi selengkapnya tentang pengemasan, pengujian, dan distribusi fungsi generasi ke-2 Anda.
Untuk bergabung dengan grup ini, kirim pesan ke firebase-extensions-migrator-support-external+subscribe@google.com, yang akan merespons dengan email permintaan keanggotaan. Anda harus membalas email tersebut, bukan mengklik tombol "Join This Group".
Sebelum memulai
Untuk menyelesaikan migrasi ini, Anda akan menggunakan fitur berikut dari Cloud Functions
Konfigurasi Berparameter. Setiap parameter yang Anda deklarasikan di
extension.yamlakan menjadi parameter yang ditentukan dalam kode paket Anda.Peran IAM deklaratif dan API yang diperlukan. Setiap peran yang Anda deklarasikan di
extension.yamlakan menjadi panggilanrequiresRole(...), dan setiap API akan menjadi panggilanrequiresAPI(...)dalam kode fungsi Anda. Saat waktu deployment, Firebase CLI akan memberikan peran yang dideklarasikan ke akun layanan runtime terkelola dan mengaktifkan API yang dideklarasikan atas nama Anda.Peristiwa siklus proses untuk codebase Cloud Functions. Cloud Functions Codebase kini mendukung peristiwa siklus proses yang analog dengan Firebase Extensions. Deklarasikan penyiapan waktu penginstalan dan waktu update dengan hook siklus proses
afterFirstDeploy(...)danafterRedeploy(...). Hook ini menggantikanlifecycleEventsyang Anda deklarasikan diextension.yaml.
Menginventarisasi Ekstensi
Mulailah dengan membuat inventaris ekstensi Anda: daftar lengkap semua yang dideklarasikan, dikirim, dan didokumentasikan oleh ekstensi, sehingga setiap perilaku memiliki tujuan yang ditentukan dalam fungsi generasi ke-2 dan tidak ada yang hilang dalam migrasi.
Tinjau setiap hal berikut, dan catat apa yang Anda temukan:
extension.yaml, yang mendeklarasikan parameter, fungsi, peristiwa, peran IAM, API yang diperlukan, secret, dan hook siklus proses Anda.functions/, yang berisi kode fungsi, dependensi, konfigurasi build, pemicu, dan fungsi task queue Anda.README.md,PREINSTALL.md, danPOSTINSTALL.md, yang berisi langkah-langkah penyiapan, peringatan, dan catatan penagihan.scripts/, yang berisi utilitas impor, backfill, IAM, perbaikan, atau migrasi, dan alat lainnya yang Anda kirim bersama ekstensi.
Kemudian, untuk setiap item di
extension.yaml, tentukan tempatnya di paket npm:
Mengonversi konfigurasi pengguna menjadi Cloud Functions parameter (bagian 5).
Mengonversi secret menjadi Cloud Functions secret (bagian 6).
Mengonversi peran IAM menjadi deklarasi
requiresRole(...)(bagian 8).Mengonversi Google API yang diperlukan menjadi deklarasi
requiresAPI(...)jika sesuai (bagian 8).Mengonversi hook penginstalan dan update menjadi deklarasi
afterFirstDeploy(...)danafterRedeploy(...)(bagian 8).
Contoh yang berfungsi: Stream Firestore to BigQuery
Membaca firestore-bigquery-export/extension.yaml dan functions/ akan menghasilkan inventaris ini:
| Di extension.yaml | Jumlah / nilai | Tempatnya |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Cloud Functions parameter (bagian 5) |
| apis | bigquery.googleapis.com | requiresAPI(...) (bagian 7) |
| roles | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (bagian 7) |
| resources | 1 pemicu peristiwa (fsexportbigquery) + fungsi task queue (initBigQuerySync, setupBigQuerySync) | Fungsi paket yang diekspor (bagian 3) |
| lifecycleEvents | onInstall → initBigQuerySync; onUpdate / onConfigure → setupBigQuerySync | afterFirstDeploy / afterRedeploy (bagian 9) |
| scripts/ | import/ (backfill), gen-schema-view/ | Dipertahankan sebagai skrip (di luar cakupan di sini) |
Ekstensi tidak mendeklarasikan parameter type: secret, sehingga tidak ada yang perlu dimigrasikan di bagian 6 panduan ini. Pemicu peristiwa sudah generasi ke-2; hanya fungsi task queue yang masih generasi ke-1 (relevan di bagian 3).
Memperbarui package.json
Perbarui file package.json ekstensi Anda. Jika Anda memigrasikan satu ekstensi, ini dapat berupa package.json root. Jika Anda memigrasikan banyak ekstensi dalam satu repositori, berikan setiap ekstensi paketnya sendiri.
Versi SDK minimum. Deklarasikan firebase-functions >= 7.3 dan firebase-admin >= 14.2.0 sebagai dependensi. Deklarasikan versi firebase-functions Anda sebagai dependensi peer juga
{
"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"
}
}
Deklarasikan firebase-functions sebagai dependensi peer selain dependensi normal Anda, sehingga project Cloud Functions pengguna Anda memiliki versi SDK yang sama dengan yang digunakan untuk menulis library Anda.
Contoh yang berfungsi: Stream Firestore to BigQuery
Sebelum. functions/package.json ekstensi bersifat pribadi, memberi nama ID ekstensi, dan mendeklarasikan firebase-functions sebagai dependensi langsung:
{
"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"
}
}
Setelah. Paket yang dapat dipublikasikan: nama cakupan, peta ekspor, dan firebase-functions dipindahkan ke 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"
}
}
Mengupgrade fungsi dari generasi ke-1 ke generasi ke-2
Jika ekstensi Anda masih mengekspor fungsi generasi ke-1, konversikan setiap fungsi ke fungsi generasi ke-2 yang setara. Impor dari modul firebase-functions/..., dan teruskan setelan runtime dalam opsi fungsi.
Anda dapat meminimalkan upaya penulisan ulang dengan pembongkaran peristiwa yang ditambal generasi ke-2 dan menghindari penulisan ulang logika fungsi karena SDK generasi ke-2 kini mengekspos parameter V1 sebagai kolom dalam objek peristiwa, sehingga Anda dapat menggunakan parameter yang dibongkar/diberi nama dan mempertahankan logika bisnis Anda tanpa perubahan.
Sebelum. Generasi ke-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);
});
Setelah. Generasi ke-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);
);
Mengonversi parameter dan secret ekstensi
Mengonversi parameter
Setiap parameter yang Anda deklarasikan di extension.yaml akan menjadi Cloud Functions
parameter.
Mengonversi pembacaan lingkungan langsung:
const collectionPath = process.env.COLLECTION_PATH;
menjadi parameter 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);
}
);
Gunakan collectionPath.value() untuk membaca string di dalam pengendali; gunakan
collectionPath secara langsung jika placeholder diharapkan, seperti jalur pemicu fungsi.
The Firebase CLI menemukan parameter Anda dan membaca nilainya dari .env, .env.projectId, atau meminta pengguna Anda selama deployment. Pertahankan nama parameter yang sama, sehingga nilai dari penginstalan yang ada akan dipertahankan.
Anda tidak boleh mengubah nama parameter yang dideklarasikan dalam kode Anda sama sekali. Migrasi ekstensi akan mempertahankan nilai parameter pengguna akhir yang ada secara otomatis, tetapi hanya jika namanya tidak berubah.
Contoh yang berfungsi: Stream Firestore to BigQuery
Sebelum. Parameter yang dideklarasikan di extension.yaml, dibaca sebagai variabel lingkungan mentah di config.ts:
# extension.yaml
- param: COLLECTION_PATH
label: Collection path
type: string
required: true
// functions/src/config.ts
collectionPath: process.env.COLLECTION_PATH,
Setelah. Satu defineString; CLI menemukannya dan membaca dari .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} }
}),
Nama parameter tidak berubah, sehingga .env yang ada akan terus berfungsi.
Mengonversi secret
Di extension.yaml, Anda mendeklarasikan secret dengan type: secret. Runtime Extensions menyimpan dan mengikatnya, sehingga kode ekstensi Anda dapat membaca process.env.PARAM_NAME secara langsung. Dalam codebase Cloud Functions yang umum, Anda mendeklarasikan dan mengikat setiap secret secara eksplisit:
import { defineSecret } from "firebase-functions/params";
import { onRequest } from "firebase-functions/https";
const apiKey = defineSecret("API_KEY");
export const fn = onRequest({ secrets: [apiKey] }, handler);
Setelah ekstensi Anda dimigrasikan ke paket/kit npm, referensi secret akan dikelola dalam file .env pengguna akhir. Anda tidak boleh mengubah nama secret yang dideklarasikan dalam kode Anda sama sekali. Selama migrasi, secret pengguna akhir akan dimigrasikan sebagaimana mestinya.
Contoh yang berfungsi: Trigger Email From Cloud Firestore
Sebelum. MAIL_COLLECTION dan SMTP_PASSWORD dibaca sebagai variabel lingkungan mentah di 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,
Setelah. Satu defineString dan satu defineSecret; CLI menemukan keduanya dan membaca dari .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();
// ...
}
);
Memigrasikan panggilan task queue internal
Beberapa ekstensi mengantrekan pekerjaan ke task queue mereka sendiri dari dalam kode fungsi mereka, menggunakan Firebase Admin SDK. Hal ini berbeda dengan menerima tugas yang dikirim (dibahas di bagian Mengupgrade fungsi dan Mengonversi hook siklus proses). Di sini, kode Anda adalah produsen yang memanggil queue.enqueue(...).
Versi Admin SDK sebelumnya mengharuskan ekstensi untuk meneruskan ID instance ekstensi mereka sendiri sebagai parameter kedua untuk menargetkan fungsi Task Queue di ekstensi yang sama. Mulai `firebase-admin` 14.2.0, hal ini tidak diperlukan maupun direkomendasikan. Task Queue API kini akan menargetkan task queue dalam konteks yang sama (misalnya, ekstensi) secara default. Anda dapat dan sebaiknya menghapus parameter ini dalam kode Anda, baik sebagai ekstensi maupun sebagai fungsi mandiri. Menghapus parameter ini akan memastikan portabilitas dan kompatibilitas penerusan.
Semua hal lainnya tentang panggilan antrean — jalur resource lokasi/region/fungsi/name, payload tugas, dan logika percobaan ulang Anda — tetap sama.
Lihat /docs/functions/task-functions untuk mengetahui detail selengkapnya tentang fungsi antrean dengan Cloud Tasks.
Sebelum. Ekstensi generasi ke-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);
Setelah. Ekstensi generasi ke-2
import { getFunctions } from "firebase-admin/functions";
const queue = getFunctions().taskQueue(
`locations/${process.env.FUNCTION_REGION}/functions/syncBigQuery`);
await queue.enqueue(taskData);
Jika panggilan antrean Anda menargetkan codebase dengan awalan, nama fungsi yang ditemukan juga akan diberi awalan (misalnya, orders-syncBigQuery).
Mendeklarasikan API dan peran IAM yang diperlukan
Pindahkan persyaratan IAM dan API ekstensi Anda dari extension.yaml ke dalam kode:
import { requiresAPI, requiresRole } from "firebase-functions"
requiresAPI("bigquery.googleapis.com", "Needed to write changelog rows");
requiresRole("roles/bigquery.dataEditor");
requiresRole("roles/bigquery.user");
Dengan keamanan deklaratif, Firebase CLI akan membuat atau memperbarui akun layanan runtime terkelola untuk codebase, dan memberikannya gabungan semua peran yang dideklarasikan. Dokumentasikan untuk pengguna Anda bahwa semua fungsi dalam codebase berjalan dengan peran tersebut, kecuali jika API akhir mendukung model yang lebih sempit.
Contoh yang berfungsi: Stream Firestore to BigQuery
Sebelum. Dideklarasikan di extension.yaml; runtime Extensions mengaktifkan API dan memberikan peran ke akun terkelola:
apis:
- apiName: bigquery.googleapis.com
roles:
- role: bigquery.dataEditor
- role: datastore.user
- role: bigquery.user
Setelah. Dideklarasikan dalam kode dengan requiresAPI dan 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");
Mengonversi hook siklus proses
Jika ekstensi Anda memanggil getExtensions().runtime(), misalnya
setProcessingState atau setFatalError, hapus panggilan tersebut, karena panggilan tersebut akan menampilkan
error jika dipanggil dari fungsi generasi ke-2 yang di-deploy secara normal. Status siklus proses kini didorong oleh afterFirstDeploy dan afterRedeploy jika pelacakan status ini tidak digunakan.
Firebase Extensions dapat menjalankan penyiapan saat pengguna menginstal, mengupdate, atau mengonfigurasi ulang ekstensi. Dalam paket npm Anda, deklarasikan tindakan siklus proses yang setara dalam kode.
Untuk penyiapan satu kali:
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: {}
}
});
Untuk update konfigurasi atau kode:
import { afterRedeploy } from "firebase-functions/lifecycle";
afterRedeploy({
task: {
function: "runInitialSetup",
body: { reconcile: true }
}
});
Buat tindakan siklus proses Anda menjadi idempoten. Pengguna Anda mungkin perlu menjalankannya kembali secara manual jika pengiriman atau eksekusi gagal:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME
firebase functions:lifecycle:run afterRedeploy CODEBASE_NAME
Contoh yang berfungsi: Stream Firestore to BigQuery
Sebelum. lifecycleEvents di extension.yaml, didorong oleh runtime Extensions:
lifecycleEvents:
onInstall:
function: initBigQuerySync
processingMessage: Configuring BigQuery Sync.
onUpdate:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
onConfigure:
function: setupBigQuerySync
processingMessage: Configuring BigQuery Sync
Setelah. Dideklarasikan dalam kode; tugas menyediakan BigQuery pada deployment pertama:
import { afterFirstDeploy, afterRedeploy } from "firebase-functions/lifecycle";
afterFirstDeploy({ task: { function: "initBigQuerySync" } });
afterRedeploy({ task: { function: "setupBigQuerySync" } });
Penyediaan bersifat idempoten, sehingga menjalankan ulang akan merekonsiliasi set data, tabel, dan tampilan. Pengguna dapat menjalankan ulang secara manual dengan firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Mendokumentasikan penyiapan untuk pengguna Anda
Tulis
READMEpaket yang menjelaskan, minimal:Nilai
.envyang diperlukan paket.Secret yang diperlukan paket, dan cara memigrasikan nilai secret yang ada.
Peran IAM yang dideklarasikan paket dengan
requiresRole(...).Google API yang diaktifkan atau diperlukan paket.
Hook siklus proses yang dideklarasikan paket, dan cara menjalankannya kembali secara manual.
Catatan penagihan.
Perubahan dibandingkan dengan ekstensi asli.
Contoh yang berfungsi: Stream Firestore to BigQuery
README paket mengirimkan tabel "apa yang berubah" yang konkret:
| Kepedulian | Sebagai ekstensi | Sebagai @firebase/firestore-bigquery-export |
|---|---|---|
| Konfigurasi | Parameter ekstensi | Parameter fungsi melalui .env |
| IAM | Diberikan oleh Extensions | requiresRole(...), diterapkan saat deployment |
| Penyediaan | Tugas siklus proses oleh Extensions | Tugas afterFirstDeploy / afterRedeploy |
| Nama fungsi | ext-instanceId-fsexportbigquery | fsexportbigquery (opsional dengan awalan) |
Menguji fungsi generasi ke-2
Sekarang Anda akan memiliki fungsi generasi ke-2 yang, saat di-deploy, akan berperilaku sama dengan penginstalan baru ekstensi Anda. Langkah terakhir adalah memverifikasi dan memperbaiki masalah yang tidak sengaja muncul di sepanjang proses.
Pastikan Anda menggunakan
firebase-tools
>= 15.25.1, dan deploy fungsi generasi ke-2 yang dikonversi ke project pengujian
dengan resource yang sesuai untuk menguji perilakunya. Jika Anda sudah menyiapkan project pengujian dari pengujian ekstensi, gunakan perintah:
firebase deploy --only functions
Setelah memasukkan perintah ini, isi wizard yang dihasilkan yang meminta nilai parameter dengan cara yang sama seperti saat Anda mengisi formulir penginstalan di konsol Firebase untuk ekstensi.
Contoh yang berfungsi: Stream Firestore to BigQuery
Kami memverifikasi sinkronisasi Cloud Firestore ke BigQuery secara menyeluruh:
- Di konsol Cloud Firestore, buat koleksi yang Anda tetapkan sebagai COLLECTION_PATH (pengguna) jika belum ada.
- Buat dokumen bernama bigquery-mirror-test yang berisi kolom apa pun dengan nilai apa pun.
- Di konsol BigQuery, kueri tabel log perubahan mentah. Tabel ini harus berisi satu baris yang mencatat pembuatan dokumen:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Ajukan kueri untuk tampilan terakhir, yang harus menampilkan peristiwa perubahan terakhir untuk
hanya dokumen yang ada:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Hapus dokumen
bigquery-mirror-testdi Cloud Firestore. Dokumen tersebut akan menghilang dari tampilan terakhir, dan peristiwaDELETEakan ditambahkan ke tabel log perubahan mentah.
Anda dapat memeriksa histori lengkap satu dokumen dengan:
SELECT *
FROM `PROJECT_ID.analytics.users_raw_changelog`
WHERE document_name = "bigquery-mirror-test"
ORDER BY timestamp ASC
Perbedaan dari pengujian ekstensi:
- Pemicu di-deploy sebagai
fsexportbigquery(opsional dengan awalan codebase), bukanext-<instanceId>-fsexportbigquery. Cari nama tersebut di Cloud Functions dasbor dan log. - Kode Anda kini akan berjalan di Firebase Local Emulator Suite sebagai fungsi normal. Anda dapat menetapkan nilai parameter yang akan digunakan di emulator dengan
.env.local. Anda juga dapat menguji unit kode menggunakan firebase-functions-test SDK seperti yang dijelaskan dalam Pengujian unit Cloud Functions - Penyediaan tidak lagi didorong oleh runtime Extensions. Jika tabel log perubahan tidak ada setelah deployment, jalankan kembali tugas penyiapan secara manual:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. Tugas ini bersifat idempoten, sehingga menjalankannya kembali akan merekonsiliasi set data, tabel, dan tampilan. - Nilai parameter berasal dari
.env, bukan formulir penginstalan, sehingga menjalankan ulangfirebase deploybersifat non-interaktif setelah.envselesai.