firebase / firebase-js-sdk

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

Firebase v9 loads a large iframe.js file but only on mobile #4946

Open ludwigbacklund opened 3 years ago

ludwigbacklund commented 3 years ago

[REQUIRED] Describe your environment

[REQUIRED] Describe the problem

After upgrading from v8 to v9 a request for an iframe (https://[projectname].firebaseapp.com/__/auth/iframe.js) has started appearing on every page load, but only on mobile (via Chrome's simulated Device mode at least, and when Lighthouse is auditing the website).

An older Lighthouse report of our application, from before the upgrade to v9, did not mention this file at all so I can only assume it wasn't loaded back then.

This iframe file is big and seems unnecessary to our application since we never use any kind of iframing of Firebase functionality and the only auth login method we use is email login.

Is there a way to disable the loading of this iframe?

image

looptheloop88 commented 3 years ago

Hi @ludwigbacklund, thanks for the report. This iframe from Firebase Auth is used for third party login and eagerly loaded on mobile in certain situations. Currently, there’s no way to disable it. You may refer to this for your reference.

ludwigbacklund commented 3 years ago

@looptheloop88 I see, thank you. However, I think you're a bit quick to close this. 263 kb of entirely unused code is a lot to download, parse and evaluate for nothing when our application has no use for it. I can imagine there are many other applications where this is true as well. For now we've had to use patch-package to manually disable the loading of this file which improved our Lighthouse score.

looptheloop88 commented 3 years ago

Hi @ludwigbacklund, thanks for the feedback. We are exploring for potential solutions, however we can't provide definite timelines or details. I have reopened this issue and added a feature request label for the meantime.

mark922 commented 3 years ago

@ludwigbacklund Can you share the snippet of your solution?

ludwigbacklund commented 3 years ago

@mark922 Open node_modules/@firebase/auth/dist/esm2017/index-somehash.js. Find the _shouldInitProactively() function. Change return _isMobileBrowser() || _isSafari() || _isIOS(); to return false. Follow the patch-package README instructions to generate the patch and commit it.

nikhilag commented 3 years ago

Shouldn't this issue be given higher priority, since the benefit of firebase js package becoming smaller is nullified by this issue?

sjbuysse commented 3 years ago

@ludwigbacklund there are multiple index-somehash.js files, which one should I take? Or all of them? Why are there multiple? (they all look the same)

ludwigbacklund commented 3 years ago

@ludwigbacklund there are multiple index-somehash.js files, which one should I take? Or all of them? Why are there multiple? (they all look the same)

Search in the @firebase/auth node_modules directory for _shouldInitProactively and you'll find it.

nikhilag commented 3 years ago

@looptheloop88 Could you please share if there's any update on this? If this issue is fixed, it will help us meet the core web vitals for an ecommerce site that we built for our client. As per pagespeed insights, if this is fixed, it will improve our LCP quite a bit (saw a very big improvement for desktop after moving to v9 but for mobile the score became worse).

sam-gc commented 3 years ago

Hi folks, the iframe code is in fact required if you're doing signInWithPassword or signInWithRedirect. If you're not using those methods (i.e. you're not using any of the OAuth providers), you can initialize Auth in a way that does not cause this code to load.

The default in getAuth() pulls in all dependencies you might need, including browserPopupRedirectResolver. You can tailor which dependencies are pulled in by using initializeAuth instead of getAuth.

The notes in the reference doc for initializeAuth explain in more detail, but at a high level you can mimic the behavior of getAuth() without pulling in the iframe code by using this code instead:

// initializeAuth throws an error if it's called more than once for the same app; save the reference.
const auth =initializeAuth(app, {
  persistence: [indexedDBLocalPersistence, browserLocalPersistence]
});

You can verify this by looking at the code: https://github.com/firebase/firebase-js-sdk/blob/6564e99ec16612960c7ea49a368fc18197a5f2d1/packages-exp/auth-exp/index.ts#L137-L140 As you can see, the snippet I wrote earlier just omits the popupRedirectResolver dependency. This will prevent the iframe code from loading. But remember, this will prevent you from using signInWithPopup and signInWithRedirect.

ArnaudD commented 3 years ago

Hi @sam-gc, thank you for your response. Is it possible to delay the iframe code from loading until signInWithPopup or signInWithRedirect are called ?

sam-gc commented 3 years ago

In general it does delay. It only loads early in a few cases: https://github.com/firebase/firebase-js-sdk/blob/7028c1159b9153f14c22a81fdd09df68ffcfcfb6/packages-exp/auth-exp/src/platform_browser/popup_redirect.ts#L166-L169

nikhilag commented 3 years ago

@sam-gc Is there no way to fix this such that lighthouse doesn't penalize performance score for using firebase sdk?

ivanvanderbyl commented 2 years ago

Any updates on this? We'd definitely like to disable it on mobile until it's really needed.

sam-gc commented 2 years ago

Hi folks, it is indeed possible to delay loading that code until the sign in methods are called. The popup and redirect family of methods take an optional third parameter that is the popupRedirectResolver. For example, the signature for signInWithPopup() is as follows:

export declare function signInWithPopup(auth: Auth, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise<UserCredential>;

So instead of using getAuth() (or initializeAuth with the popupRedirectResolver set---these are the same), you can call initializeAuth() without the popupRedirectResolver option, then pass it in to the sign in functions as you need them. For example, this code below will always delay loading the iframe code until it is used:

import {initializeAuth, indexedDBLocalPersistence, browserLocalPersistence, browserSessionPersistence, browserPopupRedirectResolver, signInWithPopup, GoogleAuthProvider} from 'firebase/auth';
const auth = initializeAuth(app, {
 persistence: [
    indexedDBLocalPersistence,
    browserLocalPersistence,
    browserSessionPersistence
  ],
});

async function signIn() {
  const result = await signInWithPopup(auth, new GoogleAuthProvider(), browserPopupRedirectResolver);
}
mdathersajjad commented 2 years ago

If anyone is looking for a patch. https://gist.github.com/mdathersajjad/628c53913c10f7e090d52871faf1c373. And add browserPopupRedirectResolver as given in above comment

abdo643-HULK commented 2 years ago

Hi folks, it is indeed possible to delay loading that code until the sign in methods are called. The popup and redirect family of methods take an optional third parameter that is the popupRedirectResolver. For example, the signature for signInWithPopup() is as follows:

export declare function signInWithPopup(auth: Auth, provider: AuthProvider, resolver?: PopupRedirectResolver): Promise<UserCredential>;

So instead of using getAuth() (or initializeAuth with the popupRedirectResolver set---these are the same), you can call initializeAuth() without the popupRedirectResolver option, then pass it in to the sign in functions as you need them. For example, this code below will always delay loading the iframe code until it is used:

import {initializeAuth, indexedDBLocalPersistence, browserLocalPersistence, browserSessionPersistence, browserPopupRedirectResolver, signInWithPopup, GoogleAuthProvider} from 'firebase/auth';
const auth = initializeAuth(app, {
 persistence: [
    indexedDBLocalPersistence,
    browserLocalPersistence,
    browserSessionPersistence
  ],
});

async function signIn() {
  const result = await signInWithPopup(auth, new GoogleAuthProvider(), browserPopupRedirectResolver);
}

The only problem is that you'll get a pop-up blocked error on the first try to login with a provider on Safari. If you try again it works because by than the iframe has loaded.

sam-gc commented 2 years ago

@abdo643-HULK yep, that's one of the reasons why the _shouldInitProactively functionality exists

abdo643-HULK commented 2 years ago

@abdo643-HULK yep, that's one of the reasons why the _shouldInitProactively functionality exists

Where can I find this function ? I am looking through the source code of the auth but can't seem to find it. And thnks for your help

sam-gc commented 2 years ago

It's this code here: https://github.com/firebase/firebase-js-sdk/blob/49b0406abb9b211c5b75325b0383539ac03358d1/packages/auth/src/platform_browser/popup_redirect.ts#L166-L169

By omitting the popupRedirectResolver optional dependency in initializeAuth(), that function is never called

Sdqumar commented 2 years ago

Here goes my solution, i only dynamic import firebase auth, when I need it. So I don't call it in the entire application, only when am making request to firebase. If you have small to medium application, it should be to much work. Tip* to keep the state of current user, use cookies Screenshot_2021-09-28-06-02-55-097_com android chrome~2 Screenshot_2021-09-28-06-02-42-884_com android chrome~2

vandres commented 2 years ago

Using the examples from the comments, I get an error "TypeError: class constructors must be invoked with 'new'"

        provideAuth(() => {
            const auth: Auth = initializeAuth(getApp(), {
                persistence: [
                    indexedDBLocalPersistence,
                    browserLocalPersistence,
                    browserSessionPersistence
                ],
                popupRedirectResolver: undefined
            });
            if (environment.emulator) {
                connectAuthEmulator(auth, 'http://localhost:9099', {disableWarnings: true});
            }

            return auth;
        }),

 const credential: UserCredential = await signInWithPopup(this.auth, new GoogleAuthProvider(), browserPopupRedirectResolver);
vandres commented 2 years ago

Ah, the problem lies within AngularFire. Will open a ticket there (https://github.com/angular/angularfire/issues/3038)

Parakoos commented 2 years ago

Another reason why this can be problematic is if you try to put the whole Auth module into a web worker. It works at first, until you test it on mobile, when suddenly it breaks because the web worker has no access to window.

The use of initializeAuth instead of getAuth (as outlined by sam-gc) fixed the issue for me.

// initializeAuth throws an error if it's called more than once for the same app; save the reference.
const auth =initializeAuth(app, {
  persistence: [indexedDBLocalPersistence, browserLocalPersistence]
});

PS. Having auth in a web worker already means that you cannot use signInWithPopup or Redirect, so that is not a drawback. To make that work for me using Google, I basically use Google Auth scripts to get a token, then send that token to my web worker auth script where I use signInWithCredential instead. :-)

nasgnat commented 2 years ago

I suspect this is what causes firebase to stop in PWA in offline mode. #5720

kodai3 commented 2 years ago

https://github.com/firebase/firebase-js-sdk/issues/4946#issuecomment-927800881

Is there nothing we could do to load iframe when we need ?

like after initialization and beforesignInWithPopup

buesing commented 2 years ago

@sam-gc Your solution works great, except for on Safari. I have tried different hacks to get the script to load when I actually need it (on sign in/sign up pages) but couldn't get anything to work reliably. A method to initialize the loading of the script would be perfect, so we don't have to load it on every page.

goellner commented 2 years ago

Pagespeed Insights even says that it is not gzipped. This could shave off some loading time and could improve this issue while we wait for a proper solution.

rejhgadellaa commented 2 years ago

+1

If there's any way to get rid of the js, that would be sweet.

In the meantime, it should really get gzipped, it's a pretty bad hit on perf. The file is 263 kb unzipped while it ~could~ should be ~83 kb (~69% smaller). according to Pagespeed.

image

EricSteinberger commented 2 years ago

Hi all, thanks for the work to fix this. Are there any updates on the timeline? This is likely hurting SEO and load times for many users out there and gzipping would fix 69% of the issue, so would be a high ROI change to make when you get around to it.

TomBeckett commented 2 years ago

It's strange that Google's own tool (PageSpeed Insights) recommends a solution (set a lifetime cache) for Firebase which is extremely popular on mobile. Is there anyone internally that can pick this up and unblock?

There are various solutions above for lazy-loading Firebase Auth but it's not the recommended approach as per documentation since auth should ideally be initialised once and shared as much as possible. I suspect this may need to be a Google hosting fix and not something the community can PR for.

It's sincerely been fantastic how far Firebase SDK has come to tackle bloat in the last year so this feels like an oversight?

Charith47 commented 2 years ago

Since lazy loading iframe.js is not a viable option for now, can we at least get compressed files served?

In the current state, my SPA gets a ~1s performance hit from this issue.

TomBeckett commented 2 years ago

Paging @sam-gc @looptheloop88.

Is there any way to get the iframe.js compressed? Or would you recommend fixing the auth library itself? Has this been assigned to anyone internally?

lewisd1996 commented 2 years ago

Desperate for a update on this, its killing my performance/lighthouse score

lewisd1996 commented 2 years ago

Here are some comparisons to highlight:

Desktop:

Screenshot 2022-04-20 at 16 18 06

Mobile:

Screenshot 2022-04-20 at 16 18 37
sanny-io commented 2 years ago

I was so shocked when I saw my measurements. This really needs a fix, haha.

rejhgadellaa commented 2 years ago

I've spun the compression issue into a separate ticket, see #6169. Now let's hope someone picks it up..

SamTech37 commented 2 years ago

Man I hope this gets fix quickly. The impact on performance is pretty severe.

jbalidiong commented 2 years ago

Hi all, thanks for the active discussion regarding this issue. We are unable to promise any timeline for this, adding a +1 on this issue can help us prioritize adding this to the roadmap.

rejhgadellaa commented 2 years ago

Hey looks like the gzip compression is enabled? That's great!

image
diegoag64 commented 2 years ago

Any updates on this? I am finalizing a project ready to be put on production but its impossible to make the mobile pagespeed go above 70 with this iframe loading. I am still loading a 263kb package loaded on mobile every time..

Parakoos commented 2 years ago

Any updates on this? I am finalizing a project ready to be put on production but its impossible to make the mobile pagespeed go above 70 with this iframe loading. I am still loading a 263kb package loaded on mobile every time..

You can do what I did and load all of firebase asynchronously into a web worker. So the initial load isn't affected at all by the size of firebase. Of course, you won't get any functionality until it is loaded but at least it is off the main thread and you can show whatever you need before the functionality is there.

SanjivG10 commented 2 years ago

Need an update on this.

webdev51 commented 2 years ago

Updates on this guy? Isn't this a very basic thing to do?

pgdev108 commented 2 years ago

Firebase SDK Team, This issue is a blocking for me to improve PageSpeed Insight. This issue has been open for sooooooooo long. Please assign this as urgent priority to fix it.

jeremytenjo commented 2 years ago

Hi @dwyfrequency

Anyway you can help the firebase team prioritize this issue?

Thank you

hanomain99 commented 2 years ago

Any update on this issue?

diegoag64 commented 2 years ago

Im also still waiting for an update on this

SanjivG10 commented 2 years ago

I solved my issue with customize your v9 auth dependency from firebase .

What I did:

Replace getAuth() on the _app.tsx (I am using NextJS) with auth from the app config

const auth  = initializeAuth(app, {
persistence: [
indexedDBLocalPersistence,
browserLocalPersistence,
browserSessionPersistence,
],
});
 <AuthProvider sdk={auth}>   // import above auth 
      <FirestoreProvider sdk={firestore}>
        <StorageProvider sdk={storage}>{children}</StorageProvider>
      </FirestoreProvider>
    </AuthProvider>

Add third parameter to the place where I was using signInWithPopup for both Google and Facebook Provider. Now, the code is like this:

   await signInWithPopup(
   auth,
   facebookProvider,
   browserPopupRedirectResolver);  // import this from firebase/auth

Boom! The pre-loading of auth/iframe is now gone.

kodai3 commented 2 years ago

@SanjivG10

But that's not working well on Safari right ?

same as this https://github.com/firebase/firebase-js-sdk/issues/4946#issuecomment-927800881