#64076 closed enhancement (fixed)
Emoji loader script can be moved to the footer since non-critical
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 6.9 | Priority: | normal |
| Severity: | normal | Version: | 4.2 |
| Component: | Emoji | Keywords: | has-patch |
| Focuses: | performance | Cc: |
Description
This is a follow-up to #63842, which converted the emoji loader script from a blocking inline script to a script module. Since the emoji replacement cannot be done until DOMContentLoaded anyway, switching to a module makes sense since script modules are deferred to run right before DCL. Additionally, an emoji is exceedingly unlikely to be the LCP element, and therefore it doesn't make sense to delay LCP by running emoji detection logic in the head to block rendering. This is especially so since the emoji loader is a fallback solution for when the browser/OS doesn't support the emoji natively. When throttling CPU to emulate a low-tier mobile phone, the performance impact of switching to a script module was seen to result in a ~4% improvement to LCP and ~7% improvement to FCP, over 100 page loads of the Sample Page in Twenty Twenty-Five without any plugins active:
| Metric | Before | After | Diff (ms) | Diff (%) |
|---|---|---|---|---|
| FCP (median) | 322.4 | 300.5 | -21.9 | -6.79% |
| LCP (median) | 407.4 | 391.9 | -15.6 | -3.82% |
| TTFB (median) | 58.4 | 59.0 | 0.6 | 1.11% |
| LCP-TTFB (median) | 348.7 | 333.4 | -15.4 | -4.40% |
But the script module is still in the head, even though it is not part of the critical rendering path. The emoji loader script involves about 3KB of markup when minified. Since the script is not critical, it could be moved from printing at wp_head to print at wp_footer instead, leaving actual critical markup to remain in the head: stylesheets.
When a script module is moved to the footer, an additional ~1.2% improvement is gained in LCP and ~1.8% in FCP when benchmarking without CPU throttling on an emulated Fast 4G connection over 100 page loads:
| Metric | wp_head | wp_footer | Diff (ms) | Diff (%) |
|---|---|---|---|---|
| FCP (median) | 429.1 | 421.4 | -7.70 | -1.8% |
| LCP (median) | 526.3 | 519.9 | -6.40 | -1.2% |
| TTFB (median) | 59.35 | 58.5 | -0.85 | -1.4% |
| LCP-TTFB (median) | 466.35 | 461.8 | -4.55 | -1.0% |
There is also an improvement when benchmarking with a Slow 3G network emulation:
| Metric | wp_head | wp_footer | Diff (ms) | Diff (%) |
|---|---|---|---|---|
| FCP (median) | 4902.4 | 4870.9 | -31.55 | -0.6% |
| LCP (median) | 4997.2 | 4972.9 | -24.30 | -0.5% |
| TTFB (median) | 60.6 | 60.6 | +0.1 | +0.1% |
| LCP-TTFB (median) | 4936.1 | 4912.7 | -23.40 | -0.5% |
(I may have obtained these benchmarks with SCRIPT_DEBUG enabled, so amount of bytes in involved would be more since no minification would be applied in production. So the impact to LCP/FCP may be less.)
In any case, there seems to be no downside to moving the emoji loader script to print at wp_footer and potentially small improvements to LCP and FCP.
Change History (10)
This ticket was mentioned in PR #10145 on WordPress/wordpress-develop by @westonruter.
6 weeks ago
#1
- Keywords has-patch added
@westonruter commented on PR #10145:
6 weeks ago
#2
- Are we allowed to use the
constformat in core JS exposed to the front-end? My code editor gives me the errorParsing error: The keyword 'const' is reserved.
Yes, because core blocks use const. I see the same from my IDE, and I think it's just because we don't have an ESLint config yet in the project. See Core-31823.
- What if the consumer is loading some script in the head tag and is referencing
window._wpemojiSettingsthere?
As part of Core-63842, we did a search for any plugins using this global: https://github.com/WordPress/wordpress-develop/pull/9531#issuecomment-3208965118
There just seems to be one relevant: WooPayments. And yet, it doesn't seem to be impacted in my testing, and I concluded with this: https://github.com/WordPress/wordpress-develop/pull/9531#issuecomment-3349873810.
@westonruter commented on PR #10145:
6 weeks ago
#3
I added this .eslintrc.js to the root of the repo:
module.exports = { extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ], };
That makes that error with const go away, but then it starts catching a bunch of other things that haven't yet been enforced before for core, like:
ESLint: Invalid JSDoc @type type "object"; prefer: "Object". (jsdoc/check-types)
@westonruter commented on PR #10145:
6 weeks ago
#4
Ah, I've just added /* eslint-env es6 */ comment which makes that error go away.
@wildworks commented on PR #10145:
6 weeks ago
#5
We can probably ignore the warnings in the code editor for now.
@westonruter commented on PR #10145:
6 weeks ago
#6
I'll go ahead and commit this now to fix that E2E test in Gutenberg, and we can continue iterating if there is any additional code review feedback.
#7
@
6 weeks ago
- Owner set to westonruter
- Resolution set to fixed
- Status changed from new to closed
In 60902:
#8
follow-up:
↓ 9
@
5 weeks ago
This makes me wonder why this emoji script is an inline script instead of fetching a remote script. Do you know why it has special treatment?
#9
in reply to:
↑ 8
@
5 weeks ago
Replying to jonsurrell:
This makes me wonder why this emoji script is an inline script instead of fetching a remote script. Do you know why it has special treatment?
That's a great question. I think it was probably originally inlined to avoid the blocking external JS request. Also, perhaps it is to reduce the chaining, since the emoji detection script loads another script which actually then does the loading/rendering of the emoji (via Twemoji). So inlining it eliminates a loader of a loader.
Also, perhaps it was inlined because there was already a need to inline some JS to export the settings, and so adding the emoji-loader inline as would just be part of that.
We should investigate further to confirm and then consider whether this would be an opportunity to register wp-emoji-loader as a script module with fetchpriority=low (#61734) and in_footer=true (#63486).
It looks like the script was introduced in [31875] to fix #31701, to avoid loading an external script unless it was needed. Back then, there was no support for defer (or script modules), so every script was a blocking script, so this was an optimization to avoid external blocking scripts.
Trac ticket: https://core.trac.wordpress.org/ticket/64076