You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase Functions Gen2部署失败求助:代码分析错误排查

问题描述

已有多个正常运行的Firebase Functions Gen1函数,新增Gen2函数并计划升级所有Gen1函数时,部署出现以下错误:

shutdown requested via /__/quitquitquit
Error: Functions codebase could not be analyzed successfully. It may have a syntax or runtime error

注释掉该Gen2函数后部署可正常完成,推测问题出在Gen2函数代码中。

Gen2函数代码

"use strict";
Object.defineProperty(exports, "__esModule", {value: true});

const {getFirestore} = require("firebase-admin/lib/firestore");

const db = getFirestore();

const {onDocumentWritten} = require("firebase-functions/v2/firestore");

export const crmGroupRecipientsCounter = onDocumentWritten("marketing/crm/recipients/{recipientID}", async (event:any) => {
  // Get an object with the current document va/lues.
  // If the document does not exist, it was deleted
  if (event.data) {
    // const document = event.data.after.data();
    const id = event.data.after.id;
    const counter = await getGroupCount(id);
    updateGroupCounter(id, counter).then((r) => null);
  }
});

const getGroupCount = async (id:string) => {
  const recipients = db.collection("marketing").doc("crm").collection("recipients");
  const snapshot = await recipients.where("guid", "==", id).where("disabled", "==", false).get();
  if (!snapshot.empty) {
    return snapshot.size;
  } else {
    return 0;
  }
};

const updateGroupCounter = async (id:string, counter:number) => {
  await db.collection("marketing").doc("crm").collection("recipients").doc(id).update({
    counter: counter,
  });
};

index.ts文件

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
// export const helloWorld = functions.https.onRequest((request, response) => {
//   functions.logger.info("Hello logs!", {structuredData: true});
//   response.send("Hello from Firebase!");
// });

import {initializeApp, cert} from "firebase-admin/app";

// const {initializeApp, cert} = require("firebase-admin/lib/app");

const serviceAccount = require("../env/serviceAccount.json");

initializeApp({
  credential: cert(serviceAccount),
  databaseURL: "https://xxxxxxxx.firebaseio.com",
});

import {crmGroupRecipientsCounter} from "./actions/marketing-module/countGroupRecipients";
exports.crmGroupRecipientsCounter = crmGroupRecipientsCounter;

const getDailyRate = require("./actions/get-daily-rates");
exports.getDailyRate = getDailyRate.getDailyRate;

const sendMail = require("./actions/send-mails");
exports.postmark = sendMail.postmark;
exports.cronMail = sendMail.cronMail;

const manageUser = require("./actions/manage-guests");
exports.manageUser = manageUser.manageUser;

const bulkReservations = require("./actions/bulk-create-reservations");
exports.bulkCreateReservations = bulkReservations.bulkCreateReservations;

const cFixer = require("./actions/credentials-fixer");
exports.credentialsFixer = cFixer.fixCredentials;

const getWeather = require("./actions/get-weather");
exports.getWeather = getWeather.getWeather;

const reservationTracker = require("./actions/track-last-reservation");
exports.reservationTracker = reservationTracker.reservationTracker;

firebase.json文件

{
  "functions": {
    "source": "functions",
    "codebase": "default",
    "runtime": "nodejs20",
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "public": "dist/pwa",
    "site": "my-site",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "service-worker.js",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=0"
          }
        ]
      },
      {
        "source": "/**",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "no-cache, no-store, must-revalidate"
          }
        ]
      }
    ]
  }
}

package.json文件

{
  "name": "functions",
  "scripts": {
    "lint": "eslint --ext .js,.ts .",
    "build": "tsc",
    "serve": "npm run build && firebase emulators:start --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "20",
    "npm": ">= 6.13.4"
  },
  "main": "lib/index.js",
  "dependencies": {
    "axios": "^1.4.0",
    "cors": "^2.8.5",
    "dayjs": "^1.11.9",
    "es6-promise-pool": "^2.5.0",
    "firebase-admin": "^11.10.1",
    "firebase-functions": "^4.4.1",
    "indicadores-economicos-bccr": "^2.0.1",
    "openweather-apis": "^4.5.0",
    "postmark": "^3.0.18"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^6.1.0",
    "@typescript-eslint/parser": "^6.1.0",
    "eslint": "^8.45.0",
    "eslint-config-google": "^0.14.0",
    "eslint-plugin-import": "^2.27.5",
    "eslint-plugin-promise": "^6.1.1",
    "firebase-functions-test": "^0.3.3",
    "typescript": "^5.1.6"
  },
  "private": true
}

开发环境信息

OS:                        Mac OS Ventura
Computer:                  Macbook Pro M1 Pro
Node version:              v20.4.0
Firebase Tools version:    firebase-tools@12.4.4

此前使用node v18.5时也出现相同问题,根据文档node v20应该兼容,同时希望得到代码结构优化建议。


问题排查与修复

1. 代码语法与导入问题

  • Gen2函数混用require和ES模块导出,且Firebase Admin导入路径错误。统一使用ES模块语法,正确导入方式:

    import { getFirestore } from 'firebase-admin/firestore';
    import { onDocumentWritten } from 'firebase-functions/v2/firestore';
    

    避免从firebase-admin/lib/firestore这类内部路径导入。

  • 增加event.data.after的空值判断:当文档被删除时,event.data.after为null,外层if(event.data)无法覆盖该场景:

    if (event.data?.after) {
      const id = event.data.after.id;
      // 后续逻辑
    }
    
  • 处理updateGroupCounter的Promise,避免函数提前终止:

    await updateGroupCounter(id, counter);
    

    替换原有的.then((r) => null)写法。

2. 依赖版本兼容

当前firebase-functions版本^4.4.1可能存在Node.js 20兼容性问题,升级到最新版本:

npm install firebase-functions@latest

3. 解决无限循环问题

当前函数监听marketing/crm/recipients/{recipientID}的写入事件,又更新该文档的counter字段,会触发函数重复执行。可通过以下方式修复:

  • 更新前检查当前counter值是否与计算值一致,一致则跳过更新;
  • 使用事务确保操作原子性,避免重复触发;
  • 调整监听逻辑,避免更新操作触发自身。

代码结构优化建议
  1. 统一模块语法:所有文件使用ES模块(import/export)替代CommonJS(require/exports),避免混合使用导致编译错误。
  2. 分离Admin初始化:将Firebase Admin的初始化逻辑单独放在admin.ts文件中,其他函数导入初始化后的实例,避免重复初始化。
  3. 类型安全:为event参数指定正确类型(Event<DocumentSnapshotBeforeAndAfter<DocumentData>>),替换any类型,提升代码可维护性。
  4. 错误处理:所有异步操作添加try/catch块,使用Firebase日志工具记录错误:
    import { logger } from 'firebase-functions';
    
    try {
      // 异步操作逻辑
    } catch (error) {
      logger.error('操作失败', error);
      throw error;
    }
    
  5. 减少硬编码:将重复的集合路径定义为常量:
    const RECIPIENTS_COLLECTION = 'marketing/crm/recipients';
    const recipientsRef = db.collection(RECIPIENTS_COLLECTION);
    

内容的提问来源于stack exchange,提问作者NaturalDevCR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 12:17:04