Panduan ini menunjukkan cara memigrasikan ekstensi Anda dari lingkungan Firebase Extensions yang tidak digunakan lagi ke fungsi yang diinstal dan di-deploy pengguna di Cloud Functions mereka sendiri untuk codebase Firebase (generasi ke-2).
Ini adalah jalur migrasi yang direkomendasikan. Firebase akan mempertahankan daftar ekstensi dengan padanan npm resmi; panduan ini akan memandu Anda membuat ekstensi sendiri.
Di seluruh panduan ini, ekstensi Stream Firestore to BigQuery (firestore-bigquery-export) digunakan sebagai contoh. Setiap bagian diakhiri dengan Contoh penggunaan yang menunjukkan tampilan ekstensi tersebut sebelum migrasi, dan tampilannya setelah migrasi, sebagai paket @firebase/firestore-bigquery-export.
Daftar untuk mendapatkan informasi dan bantuan selengkapnya tentang memigrasikan 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 kepada grup ini saat kami memperbarui panduan dengan informasi selengkapnya tentang pengemasan, pengujian, dan pendistribusian 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 "Gabung ke Grup Ini".
Sebelum memulai
Untuk menyelesaikan migrasi ini, Anda akan menggunakan fitur Cloud Functions berikut:
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.yamlmenjadi panggilanrequiresRole(...), dan setiap API menjadi panggilanrequiresAPI(...)dalam kode fungsi Anda. Pada waktu deployment, CLI Firebase memberikan peran yang dideklarasikan ke akun layanan runtime terkelola dan mengaktifkan API yang dideklarasikan atas nama Anda.Peristiwa siklus proses untuk codebase Cloud Functions. Basis kode Cloud Functions kini mendukung peristiwa siklus proses yang serupa dengan Firebase Extensions. Deklarasikan penyiapan waktu penginstalan dan waktu update dengan hook siklus proses
afterFirstDeploy(...)danafterRedeploy(...). Keduanya menggantikanlifecycleEventsyang Anda deklarasikan diextension.yaml.
Inventaris Ekstensi
Mulailah dengan membuat inventaris ekstensi Anda: daftar lengkap semua hal 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.functions/, yang berisi kode fungsi, dependensi, konfigurasi build, pemicu, dan fungsi antrean tugas.README.md,PREINSTALL.md, danPOSTINSTALL.md, yang berisi langkah-langkah penyiapan, peringatan, dan catatan penagihan.scripts/, yang berisi utilitas impor, pengisian ulang, IAM, perbaikan, atau migrasi, dan alat lainnya yang Anda kirimkan bersama ekstensi.
Kemudian, untuk setiap item di
extension.yaml, tentukan tempat
item tersebut dalam paket npm:
Konversi konfigurasi pengguna menjadi parameter Cloud Functions (bagian 5).
Konversi secret menjadi secret Cloud Functions (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 penggunaan: Streaming Firestore ke BigQuery
Membaca firestore-bigquery-export/extension.yaml dan functions/ menghasilkan inventaris berikut:
| Di extension.yaml | Jumlah / nilai | Tujuan data |
|---|---|---|
| params | 25 (COLLECTION_PATH, DATASET_ID, TABLE_ID, DATASET_LOCATION, VIEW_TYPE, …) | Parameter Cloud Functions (bagian 5) |
| api | bigquery.googleapis.com | requiresAPI(...) (bagian 7) |
| roles | bigquery.dataEditor, datastore.user, bigquery.user | requiresRole(...) (bagian 7) |
| resource | 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/ (pengisian ulang), gen-schema-view/ | Dipertahankan sebagai skrip (di luar cakupan di sini) |
Ekstensi tidak mendeklarasikan jenis: parameter rahasia, jadi tidak ada yang perlu dimigrasikan di bagian 6 panduan ini. Pemicu peristiwa sudah generasi ke-2; hanya fungsi antrean tugas yang masih generasi ke-1 (relevan di bagian 3).
Perbarui package.json
Perbarui file package.json ekstensi Anda. Jika Anda memigrasikan satu ekstensi, ekstensi ini dapat menjadi package.json root. Jika Anda memigrasikan banyak ekstensi dalam satu repositori, berikan setiap ekstensi paketnya sendiri.
Versi SDK minimum. Nyatakan 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 penggunaan: Streaming Firestore ke 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 yang diberi 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, konversi setiap fungsi ke
fungsi generasi ke-2 yang setara. Impor dari modul firebase-functions/..., dan teruskan setelan runtime di opsi fungsi.
Anda dapat meminimalkan upaya penulisan ulang dengan penghancuran peristiwa yang di-patch 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 dihancurkan/bernama 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);
);
Lihat perbandingan versi Cloud Functions untuk mengetahui daftar lengkap perbedaan antara fungsi generasi ke-1 dan generasi ke-2.
Mengonversi parameter dan secret ekstensi
Parameter konversi
Setiap parameter yang Anda deklarasikan di extension.yaml akan menjadi parameter Cloud Functions.
Mengonversi pembacaan lingkungan langsung:
const collectionPath = process.env.COLLECTION_PATH;
ke dalam 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 handler; gunakan
collectionPath secara langsung di tempat placeholder diharapkan, seperti jalur
pemicu fungsi.
CLI Firebase menemukan parameter Anda dan membaca nilainya dari .env, .env.projectId, atau meminta pengguna Anda selama deployment. Tetapkan 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 penggunaan: Streaming Firestore ke 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 tetap berfungsi.
Mengonversi secret
Di extension.yaml, Anda mendeklarasikan secret dengan type: secret. Runtime ekstensi 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 rahasia akan dikelola dalam file .env pengguna akhir. Anda tidak boleh mengubah nama rahasia yang dideklarasikan dalam kode Anda sama sekali. Selama migrasi, rahasia pengguna akhir akan dimigrasikan sebagaimana mestinya.
Contoh penggunaan: Memicu Email Dari 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 tugas ke antrean tugas sendiri dari dalam kode fungsinya, 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 produser yang memanggil queue.enqueue(...).
Versi Admin SDK sebelumnya mengharuskan ekstensi 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 antrean tugas dalam konteks yang sama (misalnya, ekstensi) secara default. Anda dapat menghapus parameter ini dengan aman dan sebaiknya dilakukan dalam kode Anda, baik sebagai ekstensi maupun sebagai fungsi mandiri. Menghapus parameter ini memastikan portabilitas dan kompatibilitas dengan versi baru.
Semua hal lain tentang panggilan antrean — jalur resource locations/region/functions/name, payload tugas, dan logika percobaan ulang — tetap sama.
Lihat /docs/functions/task-functions untuk mengetahui detail selengkapnya tentang mengantrekan fungsi 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";
import { region } from "firebase-functions/params";
const queue = getFunctions().taskQueue(
`locations/${region.value()}/functions/syncBigQuery`);
await queue.enqueue(taskData);
Jika panggilan antrean Anda menargetkan codebase yang diberi awalan, nama fungsi yang ditemukan juga 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, CLI Firebase membuat atau memperbarui akun layanan runtime terkelola untuk codebase, dan memberikan 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 penggunaan: Streaming Firestore ke BigQuery
Sebelum. Dideklarasikan dalam extension.yaml; runtime Ekstensi 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 akan memunculkan
error jika dipanggil dari fungsi gen 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, 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 bersifat 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 penggunaan: Streaming Firestore ke BigQuery
Sebelum. lifecycleEvents di extension.yaml, didorong oleh runtime Ekstensi:
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 penayangan ulang akan merekonsiliasi set data, tabel, dan tampilan. Pengguna dapat menjalankan ulang secara manual dengan firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME.
Menyiapkan dokumen untuk pengguna Anda
Tulis paket
READMEyang menjelaskan, setidaknya: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 oleh paket.
Hook siklus proses yang dideklarasikan paket, dan cara menjalankannya ulang secara manual.
Catatan penagihan.
Apa yang berubah dibandingkan dengan ekstensi asli.
Contoh penggunaan: Streaming Firestore ke 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 Ekstensi | requiresRole(...), diterapkan saat men-deploy |
| Penyediaan | Tugas siklus proses menurut Ekstensi | Tugas afterFirstDeploy / afterRedeploy |
| Nama fungsi | ext-instanceId-fsexportbigquery | fsexportbigquery (dapat diawali dengan awalan) |
Menguji fungsi generasi ke-2
Sekarang Anda akan memiliki fungsi generasi ke-2 yang, saat di-deploy, akan berperilaku identik dengan penginstalan baru ekstensi Anda. Langkah terakhir adalah memverifikasi dan memperbaiki masalah yang tidak sengaja muncul selama proses.
Pastikan Anda menggunakan
firebase-tools
>= 15.24.0, dan men-deploy fungsi generasi ke-2 yang dikonversi ke dalam 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 penggunaan: Streaming Firestore ke BigQuery
Kami memverifikasi sinkronisasi Cloud Firestore ke BigQuery secara end-to-end:
- 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. Log ini harus berisi satu baris yang mencatat pembuatan dokumen:
SELECT * FROM `PROJECT_ID.analytics.users_raw_changelog`
- Ajukan kueri untuk tampilan terbaru, yang akan menampilkan peristiwa perubahan terbaru hanya untuk dokumen yang ada:
bigquery-mirror-test
SELECT * FROM `PROJECT_ID.analytics.users_raw_latest`
- Hapus dokumen
bigquery-mirror-testdi Cloud Firestore. Perubahan tersebut akan hilang dari tampilan terbaru, dan peristiwaDELETEditambahkan 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(dengan awalan codebase secara opsional), bukanext-<instanceId>-fsexportbigquery. Cari nama tersebut di dasbor dan log Cloud Functions. - Kode Anda kini akan berjalan di fungsi Firebase Local Emulator Suite seperti biasa. Anda dapat menetapkan nilai parameter yang akan digunakan di emulator dengan
.env.local. Anda juga dapat melakukan pengujian unit pada kode menggunakan firebase-functions-test SDK seperti yang dijelaskan dalam Pengujian unit Cloud Functions - Penyediaan tidak lagi didorong oleh runtime Ekstensi. Jika tabel log perubahan tidak ada setelah deployment, jalankan kembali tugas penyiapan secara manual:
firebase functions:lifecycle:run afterFirstDeploy CODEBASE_NAME. Tugas ini bersifat idempotent, sehingga menjalankannya kembali akan merekonsiliasi set data, tabel, dan tampilan. - Nilai parameter berasal dari
.env, bukan formulir penginstalan, sehinggafirebase deployyang dijalankan ulang tidak interaktif setelah.envselesai.