Make WordPress Core

Opened 6 weeks ago

Closed 6 weeks ago

Last modified 3 weeks ago

#64076 closed enhancement (fixed)

Emoji loader script can be moved to the footer since non-critical

Reported by: westonruter's profile westonruter Owned by: westonruter's profile westonruter
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 const format in core JS exposed to the front-end? My code editor gives me the error Parsing 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._wpemojiSettings there?

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 @westonruter
6 weeks ago

  • Owner set to westonruter
  • Resolution set to fixed
  • Status changed from new to closed

In 60902:

Emoji: Move printing of emoji loader script module from wp_head to wp_print_footer_scripts.

This removes ~3KB of HTML from the critical rendering path of markup in the head, thus marginally improving FCP/LCP in slower connections. It also fixes a Firefox issue with script modules by ensuring the emoji loader script module is printed after the importmap.

Existing plugins that disable emoji by unhooking the action as follows will continue to work as expected:

remove_action( 'wp_head', 'print_emoji_detection_script', 7 );

Additionally, some obsolete DOMReady and readyCallback logic was removed. A script module (as it has a deferred execution) only ever executes when the DOM is fully loaded. This means there was no need for a DOMContentLoaded event which was removed in [60899], and the remaining ready detection logic can be removed.

Follow-up to [60899].

Developed in https://github.com/WordPress/wordpress-develop/pull/10145.

Props westonruter, wildworks.
Fixes #63842.
Fixes #64076.

#8 follow-up: @jonsurrell
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 @westonruter
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.

This ticket was mentioned in Slack in #core by westonruter. View the logs.


3 weeks ago

Note: See TracTickets for help on using tickets.