firebase-js-sdk icon indicating copy to clipboard operation
firebase-js-sdk copied to clipboard

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

Open ErionTp opened this issue 1 year ago • 4 comments

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.

ErionTp avatar May 08 '24 01:05 ErionTp

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);
  1. npm run web
  2. Visit http://localhost:8081
  3. See warning once web is bundled:
testapp git:(main) ✗ npm run web

> [email protected] 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.

dlarocque avatar May 09 '24 16:05 dlarocque

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 avatar May 09 '24 17:05 tom-andersen

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

tom-andersen avatar May 10 '24 17:05 tom-andersen

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

hsubox76 avatar May 10 '24 21:05 hsubox76