Skip to content

MAINT - Defer FontAwesome script to improve Lighthouse performance score #2209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 15, 2025

Conversation

gabalafou
Copy link
Collaborator

@gabalafou gabalafou commented May 13, 2025

This pull request:

  • Adds "defer" attribute to the script tag that loads the FontAwesome library
  • Consolidates our two custom icon files into one and also loads them with defer
  • Updates documentation to reflect the above two changes
  • Excludes images.html from the list of Kitchen Sink pages to check with Lighthouse.

Defer

Warning

When this pull request gets released in the next version of the PyData Sphinx Theme, it will require theme users to update their custom icon entries in html_js_files to include the defer attribute (see the update to the docs page in this pull request).

Without the defer attribute, the entire page has to wait for the FontAwesome script to be downloaded and executed. Lighthouse identified this script as the worst offender. With the defer attribute, the page should be able to load faster.

Excluding images.html

The reason I'm excluding images.html from Lighthouse is because it contains images that are hotlinked from Picsum. I think that this is the reason the Lighthouse performance score comes in too low (below 80). Because when I examined the Lighthouse report for that page, I saw the main reason the page failed was because of a slow Largest Contentful Paint, and when digging into that, the report showed the following table:

Phase % of LCP Timing
TTFB 8% 460 ms
Load Delay 89% 5,350 ms
Load Time 2% 110 ms
Render Delay 2% 100 ms

I assume that most of that load delay has to do with hotlinking from Picsum and nothing that we can fix without modifying the Kitchen Sink pages, which we're not supposed to do since those are generated from a script.

Using the Lighthouse score calculator, if I substract 5,000 ms from Largest Contentful Paint, the performance score climbs to 99, which puts it in line with the new performance scores for the other Kitchen Sink pages.

On the flip side, even though this PR excludes images.html from Lighthouse, it actually adds more Kitchen Sink pages to the Lighthouse audit because previously only 5 pages were being checked because the default for maxAutodiscoverUrls is 5.

How I checked this PR

  • Looked at the results on the lighthouse-audit CI job
  • Inspected the two custom icons in the preview build
    • Brave browser, desktop mode
    • Brave browser, mobile mode
    • Safari, desktop mode
    • Firefox, desktop mode
  • Checked the preview build to make sure there were no new JavaScript or other errors in the browser dev tools console log.
  • Checked the updated Header Links docs page in the preview build

@gabalafou gabalafou added tag: javascript Pull requests that update Javascript code kind: maintenance Improving maintainability and reducing technical debt labels May 13, 2025
@gabalafou gabalafou changed the title Defer FontAwesome script to improve Lighthouse performance score MAINT - Defer FontAwesome script to improve Lighthouse performance score May 13, 2025
@gabalafou gabalafou marked this pull request as ready for review May 13, 2025 18:33
@gabalafou
Copy link
Collaborator Author

"All checks have passed" 🎉

@gabalafou
Copy link
Collaborator Author

Lighthouse performance scores for Kitchen Sink pages after this PR:

Page Score
admonitions.html 99
api.html 99
blocks.html 99
generic.html 95
images.html excluded (see note above)
lists.html 99
really-long.html 99
structure.html 99
tables.html 99
typography.html 99

@gabalafou
Copy link
Collaborator Author

Consistent success of the link-check CI job depends on #2207

@gabalafou gabalafou requested a review from trallard May 14, 2025 07:27
Copy link
Collaborator Author

@gabalafou gabalafou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done self-reviewing

"/api.html",
"/blocks.html",
"/generic.html",
"/lists.html",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

images.html is explicitly excluded because the performance score is likely the result of Picsum taking too long to load images

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This a is a good observation, and it is likely this is the root cause.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into custom-icon.js.

The main reason I did this was that when I tried implementing this with two separate JS files, deferred fontawesome.js, and conf.py like so:

html_js_files = [
    ("custom-icon.js", {"defer": "defer"}),
    ("pydata-icon.js", {"defer": "defer"}),
]

Then for some reason only one of the two custom icons would load correctly on localhost:8000.

I really do not understand why it was working locally before without defer but then failed with defer.

But I also didn't want to spend a long time trying to debug this and digging into the FontAwesome source code. Plus it's better anyway to have fewer network requests for the custom icons (each js file in html_js_files is a separate network request). So I decided to consolidate them into one file and update the docs.

Copy link
Collaborator Author

@gabalafou gabalafou May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was never able to figure out why, before this PR, the icon files could be split in two and everything worked, whereas after this PR, splitting the icon files into two results in only the first one working.

That said, I wrapped the consolidated file in various setTimeouts—0, 1, 10, 100, 1000, and 10000 ms—to try to rule out the possibility of a strange race condition.

Well, there probably is some kind of strange race condition, but it probably has to do with subsequent calls to FontAwesome.library.add(). (One clue that this is the case is that if you wrap the code in pydata-icon.js on main in a long enough timeout, say 100 ms, then the icon fails to load.) Based on my testing, so long as add() gets called once with all of the custom icon definitions, instead of for each icon definition, then it seems like everything works fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this PR probably also makes custom icon loading more robust, yay!

docs/conf.py Outdated
@@ -257,7 +257,9 @@
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = ["custom.css"]
html_js_files = ["pydata-icon.js", "custom-icon.js"]
html_js_files = [
("custom-icon.js", {"defer": "defer"}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See note in PR description. Since this PR changes fontawesome.js to defer, it must also change custom-icon.js (or any code that depends on fontawesome.js to defer)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to custom-icons to reinforce the idea that all custom icons should be loaded in a single file

@gabalafou
Copy link
Collaborator Author

Just noting that in the course of working on this PR I have noticed some of the CI jobs taking a really long time (over 1 hour) and never finishing.

@gabalafou gabalafou added this to the 0.17.0 milestone May 14, 2025
...
]

.. versionchanged:: v0.17.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't release this PR with v0.17.0, then we will need to update this to the actual version that incorporates the changes in this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added this PR to the v0.17.0 milestone but should I also label it as block-release?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to add as block-release, the PR seems pretty much ready now.

Comment on lines 1 to 2
// This file is referenced in the docs, so if you make changes to it be sure to
// update the docs too :)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this file is not minified on our docs site, I assume it's not minified on theme-adopter sites. So I'm going to remove all unnecessary comments and just link to the docs page instead.

You can see it's not minified by visiting the URL: https://pydata-sphinx-theme.readthedocs.io/en/latest/_static/custom-icon.js?v=74687d30

trallard
trallard previously approved these changes May 14, 2025
Copy link
Collaborator

@trallard trallard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gabalafou this looks fantastic to me and it is a huge improvement re performance!

I think we are ready to merge so will go ahead with this.

"/api.html",
"/blocks.html",
"/generic.html",
"/lists.html",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This a is a good observation, and it is likely this is the root cause.

...
]

.. versionchanged:: v0.17.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to add as block-release, the PR seems pretty much ready now.

@trallard trallard merged commit 3e74810 into pydata:main May 15, 2025
30 of 31 checks passed
@gabalafou gabalafou deleted the fix-lighthouse-performance-score branch May 20, 2025 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: maintenance Improving maintainability and reducing technical debt tag: javascript Pull requests that update Javascript code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants