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

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

Open ludwigbacklund opened this issue 3 years ago • 75 comments

[REQUIRED] Describe your environment

  • Operating System version: macOS v11.3
  • Browser version: Chrome 90.0.4430.212
  • Firebase SDK version: [email protected]
  • Firebase Product: auth

[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

ludwigbacklund avatar May 27 '21 14:05 ludwigbacklund

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.

looptheloop88 avatar May 27 '21 18:05 looptheloop88

@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.

ludwigbacklund avatar May 28 '21 09:05 ludwigbacklund

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.

looptheloop88 avatar Jun 01 '21 09:06 looptheloop88

@ludwigbacklund Can you share the snippet of your solution?

mark922 avatar Jun 19 '21 23:06 mark922

@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.

ludwigbacklund avatar Jun 21 '21 08:06 ludwigbacklund

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

nikhilag avatar Jun 29 '21 20:06 nikhilag

@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)

sjbuysse avatar Jul 06 '21 06:07 sjbuysse

@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.

ludwigbacklund avatar Jul 06 '21 08:07 ludwigbacklund

@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).

nikhilag avatar Jul 11 '21 10:07 nikhilag

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.

sam-gc avatar Jul 12 '21 16:07 sam-gc

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

ArnaudD avatar Jul 21 '21 10:07 ArnaudD

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

sam-gc avatar Jul 21 '21 21:07 sam-gc

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

nikhilag avatar Jul 22 '21 20:07 nikhilag

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

ivanvanderbyl avatar Sep 16 '21 18:09 ivanvanderbyl

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);
}

sam-gc avatar Sep 16 '21 18:09 sam-gc

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

mdathersajjad avatar Sep 27 '21 08:09 mdathersajjad

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.

abdo643-HULK avatar Sep 27 '21 11:09 abdo643-HULK

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

sam-gc avatar Sep 27 '21 16:09 sam-gc

@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

abdo643-HULK avatar Sep 27 '21 20:09 abdo643-HULK

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

sam-gc avatar Sep 27 '21 20:09 sam-gc

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

Sdqumar avatar Sep 28 '21 05:09 Sdqumar

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 avatar Oct 30 '21 10:10 vandres

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

vandres avatar Oct 30 '21 10:10 vandres

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. :-)

Parakoos avatar Nov 03 '21 04:11 Parakoos

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

nasgnat avatar Nov 17 '21 07:11 nasgnat

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

kodai3 avatar Dec 02 '21 03:12 kodai3

@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.

buesing avatar Dec 02 '21 13:12 buesing

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.

goellner avatar Dec 09 '21 15:12 goellner

+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

rejhgadellaa avatar Dec 26 '21 14:12 rejhgadellaa

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.

EricSteinberger avatar Jan 07 '22 14:01 EricSteinberger