自定义npm包无法复用主应用Firestore对象读取数据库的问题及解决
问题场景与错误表现
我正在开发基于Firestore的自定义npm包,想直接传入主应用已初始化好的Firestore对象来实例化包内的类:
import { firestoreTest } from 'my-package' const app = initializeApp(firebaseConfig) const db = getFirestore(app) const test = new firestoreTest(db)
调用await test.getDoc('pathto/doc')时触发错误:
Uncaught (in promise) FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
尝试多种初始化方式后,只有在包内用firebaseConfig完整初始化Firebase应用才能读取数据,但无法继承主应用的认证上下文,只能访问公开集合。如果直接传入主应用的DocumentReference到包中,会出现:
Uncaught (in promise) FirebaseError: Type does not match the expected instance. Did you pass a reference from a different Firestore SDK?
主应用和包使用同版本Firebase,且已将Firebase设为peer dependency,预期能无缝复用主应用的Firebase对象。包内测试代码如下:
export class firestoreTest { private db: Firestore private app: FirebaseApp private dbFromApp: Firestore private localApp: FirebaseApp private localDb: Firestore constructor(firebaseConfig: FirebaseOptions, app: FirebaseApp, db: Firestore) { this.db = db this.app = app this.dbFromApp = getFirestore(app) this.localApp = initializeApp(firebaseConfig) this.localDb = getFirestore(this.localApp) } public async getDoc(docPath: string, ref?: DocumentReference<DocumentData, DocumentData>) { /* 能读取但无认证上下文,仅访问公开集合 */ // const data = await getDoc(doc(this.localDb, docPath).withConverter(converter<any>())) /* 触发参数类型错误 */ // const data = await getDoc(doc(this.dbFromApp, docPath).withConverter(converter<any>())) /* 触发参数类型错误 */ const data = await getDoc(doc(this.db, docPath).withConverter(converter<any>())) /* 触发SDK实例不匹配错误 */ // const data = ref && (await getDoc(ref.withConverter(converter<any>()))) console.log(data?.data()) } }
核心原因分析
问题出在开发阶段本地依赖加载的重复实例:即使把Firebase设为peer dependency,用npm link或本地源安装包时,主应用和包会各自加载一份独立的Firestore SDK。Firebase内部的类型校验会严格检查实例来源,不同SDK实例生成的对象无法互通,从而触发各种类型错误。
生产环境推送到GitHub后安装正常,是因为此时包会复用主应用的Firebase依赖,不会重复加载SDK实例。
解决方案
1. 开发阶段用relative-deps解决依赖重复问题
relative-deps可以让本地开发的npm包直接复用主应用的依赖,避免重复加载SDK:
- 在自定义包的
package.json中配置relative-deps,指向主应用的node_modules或具体Firebase依赖 - 安装并配置后,包会直接使用主应用的Firestore SDK实例,类型校验和认证上下文继承都能正常工作。
2. 确保peer dependency配置正确
在包的package.json中明确Firebase相关依赖为peer dependency,避免生产环境重复安装:
"peerDependencies": { "firebase": "^9.0.0" // 匹配主应用使用的版本 }, "devDependencies": { "firebase": "^9.0.0" // 仅开发时用于类型校验 }
3. 简化包内初始化逻辑
包内不需要重新初始化Firebase应用,直接使用主应用传入的Firestore实例即可,去掉不必要的本地初始化代码:
export class firestoreTest { private db: Firestore constructor(db: Firestore) { this.db = db // 直接使用主应用传入的实例 } public async getDoc(docPath: string) { const docRef = doc(this.db, docPath).withConverter(converter<any>()) const data = await getDoc(docRef) console.log(data?.data()) } }
内容的提问来源于stack exchange,提问作者KristapsR

