本文是 Expo SDK 53 Firebase Cloud Messaging (FCM) 完整修復指南 的延續版本,提供完全自動化的解決方案。
前言:從手動到自動化的進化
在我之前的文章中,我們解決了 Expo SDK 53 中 Firebase FCM 的配置問題,但那個方案需要手動修改原生文件。這種方法雖然有效,但存在以下問題:
- ❌ 不可重現:每次
expo prebuild
都會覆蓋手動修改 - ❌ 團隊協作困難:其他開發者需要記住手動步驟
- ❌ 版本控制混亂:原生文件變更難以追蹤
- ❌ 維護成本高:容易忘記修改步驟
今天,我將分享一個完全自動化的解決方案,讓你告別手動修改的煩惱!
💡 核心問題回顧
正如原文所述,Expo SDK 53 中 Firebase FCM 無法正常工作的根本原因是:
- CocoaPods modular headers 衝突
- Firebase 初始化時機不正確
- @react-native-firebase/app 插件無法識別 Expo 的 AppDelegate 結構
🚀 完全自動化解決方案
步驟 1: 正確配置 app.json
{
"expo": {
"plugins": [
// ... 你的其他插件 (expo-router, expo-font 等)
[
"@react-native-firebase/app",
{
"googleServicesFile": "./GoogleService-Info.plist",
"ios": {
"bundleId": "com.yourapp.bundle"
}
}
],
"@react-native-firebase/messaging",
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "static"
}
}
]
]
}
}
關鍵改進:
- ✅ 使用
expo-build-properties
的useFrameworks: "static"
從根本解決 modular headers 問題 - ✅ 明確指定
googleServicesFile
路徑讓插件能找到配置文件 - ✅ 只添加必要的 Firebase 相關插件,不強制使用其他非必需插件
步驟 2: 創建自動化插件
創建 plugins/
資料夾並添加以下插件:
plugins/withFirebaseAppDelegate.js
const { withAppDelegate } = require('@expo/config-plugins');
function withFirebaseAppDelegate(config) {
return withAppDelegate(config, config => {
if (config.modResults.language === 'swift') {
let contents = config.modResults.contents;
// 自動注入 Firebase 初始化
if (!contents.includes('FirebaseApp.configure()')) {
const functionStart = contents.indexOf('didFinishLaunchingWithOptions launchOptions:');
if (functionStart !== -1) {
const openBrace = contents.indexOf('{', functionStart);
if (openBrace !== -1) {
const insertPoint = openBrace + 1;
const firebaseConfig = '\n FirebaseApp.configure()\n ';
contents = contents.slice(0, insertPoint) + firebaseConfig + contents.slice(insertPoint);
config.modResults.contents = contents;
}
}
}
// 自動添加 FCM delegate
if (!contents.includes('UNUserNotificationCenter.current().delegate = self')) {
const superCall = contents.indexOf('return super.application(application, didFinishLaunchingWithOptions: launchOptions)');
if (superCall !== -1) {
const insertPoint = superCall;
const delegateConfig = '\n if #available(iOS 10.0, *) {\n UNUserNotificationCenter.current().delegate = self\n }\n\n ';
contents = contents.slice(0, insertPoint) + delegateConfig + contents.slice(insertPoint);
config.modResults.contents = contents;
}
}
}
return config;
});
}
module.exports = withFirebaseAppDelegate;
plugins/withFirebasePodfile.js
const { withPodfile } = require('@expo/config-plugins');
function withFirebasePodfile(config) {
return withPodfile(config, config => {
const podfileContents = config.modResults.contents;
// 檢查是否已經有 Firebase modular headers 配置
if (!podfileContents.includes('pod \'GoogleUtilities\', :modular_headers => true')) {
// 在 target 'YourApp' do 後添加 Firebase modular headers
const targetRegex = /target\s+['"][^'"]+['"]\s+do/;
const match = podfileContents.match(targetRegex);
if (match) {
const insertIndex = podfileContents.indexOf(match[0]) + match[0].length;
const firebaseConfig = `
# Firebase modular headers fix for Expo SDK 53+
pod 'GoogleUtilities', :modular_headers => true
pod 'FirebaseCore', :modular_headers => true
pod 'FirebaseCoreInternal', :modular_headers => true
pod 'FirebaseMessaging', :modular_headers => true
pod 'FirebaseInstallations', :modular_headers => true
`;
config.modResults.contents =
podfileContents.slice(0, insertIndex) +
firebaseConfig +
podfileContents.slice(insertIndex);
}
}
return config;
});
}
module.exports = withFirebasePodfile;
步驟 3: 更新完整的 app.json
{
"expo": {
"plugins": [
// ... 你現有的插件 (根據項目需求)
[
"@react-native-firebase/app",
{
"googleServicesFile": "./GoogleService-Info.plist",
"ios": {
"bundleId": "com.yourapp.bundle"
}
}
],
"@react-native-firebase/messaging",
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "static"
}
}
],
"./plugins/withFirebaseAppDelegate.js",
"./plugins/withFirebasePodfile.js"
]
}
}
重要說明: 只需要添加與 Firebase FCM 相關的插件,如果你原本就用到 expo-router
或 expo-font
等插件,請在你原本的設定後面加上。
步驟 4: 完全自動化部署
# 一鍵重建整個專案
rm -rf ios
npx expo prebuild --platform ios
npx expo run:ios
✨ 自動化方案的優勢
🎯 完全可重現
- 任何團隊成員執行
expo prebuild
都會得到相同配置 - 不需要記憶任何手動步驟
🚀 零維護成本
- 插件會在每次 prebuild 時自動運行
- 配置邏輯版本控制化,易於維護
👥 團隊友好
- 新團隊成員只需
npm install
+expo prebuild
- 所有配置都在代碼庫中
🛡️ 版本升級保險
- Expo SDK 升級時,只需調整插件邏輯
- 不會意外覆蓋手動修改
🔧 進階配置選項
條件性配置
// 只在特定環境下啟用某些功能
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment) {
// 開發環境特定配置
contents = contents.replace(
'aps-environment": "production"',
'aps-environment": "development"'
);
}
多環境支持
// 根據不同環境使用不同的 Firebase 配置
const environment = process.env.EXPO_PUBLIC_ENVIRONMENT || 'development';
const googleServicesFile = `./GoogleService-Info-${environment}.plist`;
🚨 常見問題排除
問題 1: 插件沒有執行
解決方案: 確保插件路徑正確,且文件有正確的 module.exports
問題 2: Firebase 初始化位置不對
解決方案: 檢查 AppDelegate 結構,調整插件中的文本匹配邏輯
問題 3: CocoaPods 衝突
解決方案: 確保 expo-build-properties
的 useFrameworks: "static"
配置正確
📊 方案比較
特性 | 手動方案 | 自動化方案 |
---|---|---|
設置複雜度 | 簡單 | 中等 |
維護成本 | 高 | 低 |
團隊協作 | 困難 | 簡單 |
可重現性 | 無 | 完美 |
錯誤風險 | 高 | 低 |
🎉 結論
這個自動化方案徹底解決了 Expo SDK 53 Firebase FCM 的配置問題,同時提供了:
- ✅ 完全自動化:無需任何手動步驟
- ✅ 完全可重現:任何時候都能重建相同環境
- ✅ 團隊友好:新成員零學習成本
- ✅ 未來兼容:易於適應 SDK 升級
告別手動修改的時代,擁抱自動化配置的未來!
技術背景
理論上,只配置 @react-native-firebase/app
和 expo-build-properties
就應該能自動適配所有必要設定。但在當前版本中,由於 @react-native-firebase/app
插件對 Expo 的Swift AppDelegate
結構識別有限制,我們需要額外的自定義插件來補足這個缺陷。隨著 Expo 和 Firebase 插件的版本更新,未來可能會簡化這個流程。