firebase / firebase-js-sdk

Firebase Javascript SDK
https://firebase.google.com/docs/web/setup
Other
4.83k stars 888 forks source link

Require cycle: ../../node_modules/@grpc/grpc-js/build/src/channel.js #8231

Open ErionTp opened 4 months ago

ErionTp commented 4 months ago

Operating System

Mac Sonoma 14.4.1

Browser Version

Chrome/ Version 124.0.6367.119 (Official Build) (arm64)

Firebase SDK Version

10.11.1

Firebase SDK Product:

Auth, Firestore

Describe your project's tooling

React Native Expo, Web with metro.config.js

Describe the problem

Im trying to add a context provider so I can handle the status of the user. In the native app everything works fine. When I try to open the app on web it warns me like:

WARN  Require cycle: ../../node_modules/@grpc/grpc-js/build/src/channel.js -> ../../node_modules/@grpc/grpc-js/build/src/internal-channel.js -> ../../node_modules/@grpc/grpc-js/build/src/subchannel-pool.js -> ../../node_modules/@grpc/grpc-js/build/src/subchannel.js -> ../../node_modules/@grpc/grpc-js/build/src/channelz.js -> ../../node_modules/@grpc/grpc-js/build/src/make-client.js -> ../../node_modules/@grpc/grpc-js/build/src/client.js -> ../../node_modules/@grpc/grpc-js/build/src/channel.js

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. 
  factory (../../node_modules/@grpc/grpc-js/build/src/client.js:21:19)
  factory (../../node_modules/@grpc/grpc-js/build/src/make-client.js:20:18)
  factory (../../node_modules/@grpc/grpc-js/build/src/channelz.js:25:23)
  factory (../../node_modules/@grpc/grpc-js/build/src/subchannel.js:26:20)

Steps and code to reproduce issue

I have the firebase config:

import { initializeApp } from "firebase/app";
import {
  initializeAuth,
  connectAuthEmulator,
  getReactNativePersistence,
  browserSessionPersistence,
} from "firebase/auth";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
import { getStorage, connectStorageEmulator } from "firebase/storage";
import { connectFunctionsEmulator, getFunctions } from "firebase/functions";

import AsyncStorage from "@react-native-async-storage/async-storage";

import device from "../device";

const firebaseConfig = {
  apiKey: process.env.EXPO_PUBLIC_API_KEY ?? "",
  authDomain: process.env.EXPO_PUBLIC_AUTH_DOMAIN ?? "",
  projectId: process.env.EXPO_PUBLIC_PROJECT_ID ?? "",
  storageBucket: process.env.EXPO_PUBLIC_STORAGE_BUCKET ?? "",
  messagingSenderId: process.env.EXPO_PUBLIC_MESSAGING_SENDER_ID ?? "",
  appId: process.env.EXPO_PUBLIC_APP_ID ?? "",
  measurementId: process.env.EXPO_PUBLIC_MEASUREMENT_ID ?? "",
};

const app = initializeApp(firebaseConfig);

const auth = initializeAuth(app, {
  persistence: device.isWeb ? browserSessionPersistence : getReactNativePersistence(AsyncStorage),
});

const firestore = getFirestore(app);
const storage = getStorage(app);
const functions = getFunctions(app);

if (__DEV__) {
  connectFirestoreEmulator(firestore, process.env.EXPO_PUBLIC_IP ?? "", 8080);
  connectStorageEmulator(storage, process.env.EXPO_PUBLIC_IP ?? "", 9199);
  connectAuthEmulator(auth, `http://${process.env.EXPO_PUBLIC_IP}:9099`, { disableWarnings: true });
  connectFunctionsEmulator(functions, process.env.EXPO_PUBLIC_IP ?? "", 5001);
}

export { app, auth, firestore, storage, functions };

Then I call this in a context provider:

import React, {
  createContext,
  useContext,
  useMemo,
  PropsWithChildren,
  useState,
  useEffect,
} from "react";
import { auth } from "@/src/resources/config/firebase";
import { User, onAuthStateChanged } from "firebase/auth";

type tContext = {
  user: User | undefined | null;
  setUser: React.Dispatch<React.SetStateAction<User | null | undefined>>;
};

const MainContext = createContext<Partial<tContext>>({
  user: undefined,
  setUser: () => Function,
});

export type tMainProviderProps = {};

export const MainProvider = ({ children }: PropsWithChildren<tMainProviderProps>) => {

  const [user, setUser] = useState<User | null | undefined>(undefined);

  const authStateChanged = (authUser: User | null | undefined) => {
    setUser(authUser);
  };

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, authStateChanged);
    return () => {
      unsubscribe();
    };
  }, []);

  const memoValue = useMemo(() => ({ user, setUser }), [user]);

  return <MainContext.Provider value={memoValue}>{children}</MainContext.Provider>;
};

export default function useMainProvider() {
  const context = useContext(MainContext);
  if (!context) throw new Error("useMainProvider should be within MainProvider");
  return context;
}

There is no other configuration or setup, this project is quite new.

dlarocque commented 4 months ago

Hi @ErionTp, thanks for submitting this issue.

I was able to produce a minimal reproduction:

  1. npx create-expo-app testapp
  2. cd testapp
  3. npm install firebase
  4. Edit app/(tabs)/index.tsx to be:
    
    import { initializeApp } from 'firebase/app';
    import { getFirestore } from "firebase/firestore";

const firebaseConfig = { / Replace me with your firebase project config / }; const app = initializeApp(firebaseConfig);

getFirestore(app);

5. `npm run web`
6. Visit `http://localhost:8081`
7. See warning once web is bundled:

testapp git:(main) ✗ npm run web

testapp@1.0.0 web expo start --web

Starting project at /Users/dlarocque/workspace/repro/8231/testapp Starting Metro Bundler

› Metro waiting on exp://127.0.0.1:8081 › Scan the QR code above with Expo Go (Android) or the Camera app (iOS)

› Web is waiting on http://localhost:8081

› Using Expo Go › Press s │ switch to development build

› Press a │ open Android › Press i │ open iOS simulator › Press w │ open web

› Press j │ open debugger › Press r │ reload app › Press m │ toggle menu › Press o │ open project code in your editor

› Press ? │ show all commands

Logs for your project will appear below. Press Ctrl+C to exit. λ Bundled 10107ms node_modules/expo-router/node/render.js (955 modules) Web Bundled 10277ms node_modules/expo-router/entry.js (848 modules) λ WARN Require cycle: node_modules/@grpc/grpc-js/build/src/channel.js -> node_modules/@grpc/grpc-js/build/src/internal-channel.js -> node_modules/@grpc/grpc-js/build/src/subchannel-pool.js -> node_modules/@grpc/grpc-js/build/src/subchannel.js -> node_modules/@grpc/grpc-js/build/src/channelz.js -> node_modules/@grpc/grpc-js/build/src/make-client.js -> node_modules/@grpc/grpc-js/build/src/client.js -> node_modules/@grpc/grpc-js/build/src/channel.js

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. factory (node_modules/@grpc/grpc-js/build/src/client.js:21:19) factory (node_modules/@grpc/grpc-js/build/src/make-client.js:20:18) λ WARN Require cycle: node_modules/@grpc/grpc-js/build/src/experimental.js -> node_modules/@grpc/grpc-js/build/src/load-balancer-outlier-detection.js -> node_modules/@grpc/grpc-js/build/src/experimental.js

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. factory (node_modules/@grpc/grpc-js/build/src/load-balancer-outlier-detection.js:24:24) factory (node_modules/@grpc/grpc-js/build/src/experimental.js:37:41) λ WARN Require cycle: node_modules/protobufjs/src/util/minimal.js -> node_modules/protobufjs/src/util/longbits.js -> node_modules/protobufjs/src/util/minimal.js

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. factory (node_modules/protobufjs/src/util/longbits.js:4:12) factory (node_modules/protobufjs/src/util/minimal.js:26:17) λ WARN Require cycle: node_modules/protobufjs/src/enum.js -> node_modules/protobufjs/src/namespace.js -> node_modules/protobufjs/src/field.js -> node_modules/protobufjs/src/enum.js

Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle. factory (node_modules/protobufjs/src/field.js:8:13) factory (node_modules/protobufjs/src/namespace.js:8:16) λ WARN props.pointerEvents is deprecated. Use style.pointerEvents "shadow*" style props are deprecated. Use "boxShadow". λ WARN accessibilityRole is deprecated. Use role.

tom-andersen commented 4 months ago

This appears to be from within a dependent library. They have an open issue for dependency cycles within their code:

https://github.com/grpc/grpc-node/issues/1821

tom-andersen commented 4 months ago

Those particular modules are not required for web. I think there might be something wrong with how the bundler is configured.

Firebase Web SDK variants

hsubox76 commented 4 months ago

This looks like a recurring issue with the default Metro config resolving to main or one of the exports subfields that points to the Node bundle, here: https://github.com/firebase/firebase-js-sdk/blob/ab883d016015de0436346f586d8442b5703771b7/packages/firestore/package.json#L87

The Metro config needs to be changed somehow so that it resolves to browser, module, or any of the exports subfields that point to a bundle that doesn't have the word "node" in it. Unfortunately we don't seem to have the expertise in configuring Metro for React Native to come up with a Metro config change that makes this work, and it would be really helpful if someone with expertise in Metro or React Native could come up with a workaround using this information.

I think this is beyond the scope of the JS SDK as all the paths in our package.json seem to follow standards for how to label browser, Node, ESM, and CJS bundle and Metro ought to be able to follow these fields to a web bundle, and not a Node bundle. We can leave this issue open in case anything spots anything wrong with our package.json configuration, but it seems at the moment like the issue is with Metro, or the default Metro config at least.

See previous issue: https://github.com/firebase/firebase-js-sdk/issues/7849#issuecomment-1857889012