WordPress.org

Make WordPress Core

Opened 4 years ago

Closed 4 years ago

Last modified 3 years ago

#31701 closed defect (bug) (fixed)

Emoji: Avoid enqueueing JS by default

Reported by: obenland Owned by: pento
Milestone: 4.2 Priority: normal
Severity: major Version: 4.2
Component: Emoji Keywords: emoji has-patch commit
Focuses: javascript, performance Cc:

Description

Emoji support enqueues two script files and an inline script on every page load. AFAIK this is the first time that WP enqueues any scripts on the front-end by default.

We should try to find a way to limit it to sites where we know emojis are actually used, and/or find other ways to optimize that.

Attachments (13)

31701.diff (6.5 KB) - added by pento 4 years ago.
31701.2.diff (6.8 KB) - added by pento 4 years ago.
31701.3.diff (7.1 KB) - added by pento 4 years ago.
31701.4.diff (8.6 KB) - added by pento 4 years ago.
31701.5.diff (9.1 KB) - added by pento 4 years ago.
31701.6.diff (11.2 KB) - added by pento 4 years ago.
31701.7.diff (12.0 KB) - added by pento 4 years ago.
31701.8.diff (13.0 KB) - added by pento 4 years ago.
31701.9.diff (12.8 KB) - added by pento 4 years ago.
31701.10.diff (13.1 KB) - added by azaozz 4 years ago.
31701.11.diff (12.4 KB) - added by pento 4 years ago.
31701.12.diff (13.7 KB) - added by azaozz 4 years ago.
31701.13.diff (1.3 KB) - added by azaozz 4 years ago.

Download all attachments as: .zip

Change History (49)

#1 @obenland
4 years ago

  • Keywords emoji added

@pento

#2 @pento
4 years ago

We have a handful of options for dealing with this:

  1. The current method, which detects browser support for emoji in JS. We can potentially optimize the JS, for example by combining wp-emoji.js and twemoji.js during the build process, removing a HTTP request.
  2. There's potential for a require.js based solution - having a tiny shim for checking for emoji support, then lazy loading the rest if needed. This will mean a huge wait for emoji to actually be rendered, though, and it won't cut down the number of HTTP requests (it will reduce the download size by a few KB for browsers that support emoji, increase it by ~15KB for browsers that don't).
  3. Checking for emoji in the post content (and other content areas, like widgets), and only loading the emoji JS if they're found. I've tried solutions like this in the past, and they always seem to have painful edge cases. For example, VideoPress tries to do exactly that, but it can't detect videos embedded in themes, for example.
  4. Using server side browser sniffing to decide if we should load the emoji JS. We've moved away from browser/OS version sniffing, because it's inherently unreliable - we have to keep updating the support matrix as browsers add emoji support. We could potentially make it a dynamic matrix, maybe by having the W.org API return an up-to-date copy for sites to use, but I'm still quite wary of the idea.

I'm leaning towards option 1 as the sanest solution. By combining the two scripts, we could potentially set the script to async, as it has no other dependencies.

#3 @dd32
4 years ago

When I first heard of this, I was hopeful that we'd be able to simply use a lightweight emoji detection inline script, unfortunately I then realised that the emoji detection code is covering a bunch of edge cases, which adds some significant size..

  1. require.js .. (it will reduce the download size by a few KB for browsers that support emoji, increase it by ~15KB for browsers that don't).

I assume we wouldn't use require.js, but rather wp-emoji.js + a small amount of code to load twemoji, which shouldn't be too complex.. We'd also want it to run before DOMReady, to hopefully get twomoji loaded up asap..

#4 @pento
4 years ago

The browserSupportsEmoji() function is pretty small - it minifies to only a few hundred bytes. The total shim would be less than 1KB. Compared to Twenty Fifteen's inline styles, it's positively svelte. :-)

If we were to write a custom loader (I did one ages ago for the Likes widget on WP.com, they're pretty simple), along with combining wp-emoji.js and twemoji.js in the build process, we may be on to something. I shall experiment.

@pento
4 years ago

#5 @pento
4 years ago

31701.diff only loads the emoji JS files if they're needed (currently only works with SCRIPT_DEBUG set to true).

Still to do:

  • Load the scripts from the correct URL (it's currently assuming that WP is in the root directory).
  • We've already detected browser emoji support once, we don't need to do it a second time in wp-emoji.js.
  • Combine wp-emoji.js and twemoji.js during the build process, so that we only need to load wp-emoji.min.js.
  • Figure out a nice way to build and maintain the minified version of the inline script.

Anyway, this is a proof of concept. It looks like it might be a viable option.

#6 @pento
4 years ago

Also to do, the scripts need to be given ?ver= parameters, for cache busting.

#7 @DrewAPicture
4 years ago

  • Keywords has-patch added
  • Severity changed from normal to major

#8 @Kelderic
4 years ago

Can theme developers currently manually de-register these scripts?

#9 @pento
4 years ago

Yes. While we're developing things, the method for doing that is in flux - we'll have it locked down before RC. :-)

@pento
4 years ago

#10 @pento
4 years ago

31701.2.diff adds a cache buster, and makes sure the script URLs are correct.

#11 follow-up: @wonderboymusic
4 years ago

Every time wp_print_scripts/styles() runs, we are going to run this JS? This feels strange. I'm all for smilies, but are they really this important?

What happens if I call wp_print_scripts() manually 6 times? Does this code run every time? What if I never want this code to run, do I have to add a filter every time I fire up a WordPress project?

This code makes sense for WordPress.com and Jetpack, I get it, but this seems weird to me as base product code.

@pento
4 years ago

#12 in reply to: ↑ 11 @pento
4 years ago

Replying to wonderboymusic:

Every time wp_print_scripts/styles() runs, we are going to run this JS? This feels strange.

Nice catch. :-) 31701.3.diff adds a static flag to the functions, so they're only ever printed once.

I'm all for smilies, but are they really this important?

Not just smilies, they're emoji.

What if I never want this code to run, do I have to add a filter every time I fire up a WordPress project?

Yep.

This code makes sense for WordPress.com and Jetpack, I get it, but this seems weird to me as base product code.

This goes back to some of the original discussion of emoji. The issue now is how to provide consistent emoji support for everyone.

@pento
4 years ago

#13 @pento
4 years ago

31701.4.diff does some magic.

  • The emoji loader JS is moved to wp-emoji-loader.js, though is still embedded in the header thanks to the magic of readfile(). This means that...
  • wp-emoji-loader.js is minified as part of the build process, so will be embedded as a minified version on release (927 bytes, for reference).
  • wp-emoji.js and twemoji.js are concat'ed as part of the build process, into wp-emoji-release.min.js. I'm not wild about the name, suggestions are welcome. :-)

#14 follow-up: @azaozz
4 years ago

Looking at 31701.4.diff, some ideas:

  • Maybe we can use only one "settings" filter. No need to filter each setting separately (emoji_url, emoji_ext, etc.). Combine them in an array and pass it through one?
  • Sometimes widow.load fires much later than expected. Can take up to 5-10 sec. etc. Usually caused by a slow loading image or script. Would it be better to run the emoji JS on DOM ready? Seems that DOMContentLoaded is supported in all browsers that also support addEventListener, i.e. all except IE8 and older: http://caniuse.com/#feat=domcontentloaded, https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded.
  • If the wp-emoji and twemoji scripts are concatenated together (which is a good idea), we won't need to wait for loading. In theory we can load late in the footer and run as soon as the file is loaded. In practice we can set a var in wp-emoji-loader.js on DOM ready, then when wp-emoji.js loads, we can check that var and either bind to DOMContentLoaded or window.load, or run if that has already fired.
  • May be missing something but looks like waitForTwemoji() inserts another script node every 50ms while waiting.
Last edited 4 years ago by azaozz (previous) (diff)

#15 in reply to: ↑ 14 @pento
4 years ago

Replying to azaozz:

Maybe we can use only one "settings" filter. No need to filter each setting separately (emoji_url, emoji_ext, etc.). Combine them in an array and pass it through one?

I'm inclined to keep them as separate filters - there shouldn't be a need to filter the JS file names. I'm totally okay with changing that if there's a use case, though.

Sometimes widow.load fires much later than expected. Can take up to 5-10 sec. etc. Usually caused by a slow loading image or script. Would it be better to run the emoji JS on DOM ready? Seems that DOMContentLoaded is supported in all browsers that also support addEventListener, i.e. all except IE8 and older: http://caniuse.com/#feat=domcontentloaded, https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded.

How about using the readystatechange event? It's supported everywhere, and seems to happen just before DOMContentLoaded, but after the DOM is ready for interaction.

If the wp-emoji and twemoji scripts are concatenated together (which is a good idea), we won't need to wait for loading. In theory we can load late in the footer and run as soon as the file is loaded. In practice we can set a var in wp-emoji-loader.js on DOM ready, then when wp-emoji.js loads, we can check that var and either bind to DOMContentLoaded or window.load, or run if that has already fired.

I don't like putting wp-emoji-loader.js in the footer. Having it in the header means it starts loading the JS file as soon as possible. Adding an extra flag to wp-emoji-loader.js to determine when the DOM is loaded seems like a waste, when document.readyState gives us the information we need.

May be missing something but looks like waitForTwemoji() inserts another script node every 50ms while waiting.

Oops! I'll fix that in the next patch. Thanks. :-)

@pento
4 years ago

@pento
4 years ago

#16 @pento
4 years ago

In 31701.6.diff:

  • Switched the initial emoji parse to the readystatechange event, instead of the load event.
  • After wp-emoji-loader.js determines emoji support, it sets that info on window._wpemojiSettings, which is then reused by wp-emoji.js.
  • As a result, I've removed browserSupportsEmoji() from wp-emoji.js.
  • Fixed waitForTwemoji() incorrectly adding many <script> nodes.

Things for feedback:

  • Is wp-emoji-loader.js the correct name for our loader file?
  • I'm not sure about using readfile() in production - can we guarantee the JS file will be available on the same FS? Maybe it'd be better to have grunt build manually write the minified JS to formatting.php.

@pento
4 years ago

#17 @pento
4 years ago

31701.7.diff writes the contents of wp-emoji-loader.min.js to formatting.php during grunt build.

Another thing for feedback:

  • I'm not wild about the wp-emoji-release.min.js filename for the combined wp-emoji.js and twemoji.js file.

@pento
4 years ago

#18 @pento
4 years ago

31701.8.diff has a fun little change.

As of 31701.7.diff, the wp-emoji-loader.js JavaScript took about 15ms to run - 1.5ms for the JS, and 13.5ms for a style recalculation, which is caused by our use of canvas. I've been experimenting a bit, but it seems that the style recalculation is unavoidable.

To fix this, 31701.8.diff moves the JS to be embedded before wp_print_styles. Now the JS takes 1.6ms to run - 1.5ms for the JS, and 0.1ms for the style recalculation.

So, now the total is:

  • Running wp-emoji-loader.js: 1.6ms
  • Parsing the embedded emoji CSS: <0.1ms

If we need to load Twemoji:

  • Parsing twemoji.js: 0.8ms
  • Parsing and running wp-emoji.js with no emoji: 1ms

The overhead if no emoji exists (and we need to load the extra JS) is about 3.5ms. About 0.01ms is added per emoji replaced. I haven't timed this with the built scripts yet, but I expect everything to be slightly faster.

@wonderboymusic - given your earlier feelings on running this every time, what's your opinion now on a 1.7-3.5ms overhead?

#19 follow-up: @dd32
4 years ago

What makes the big difference in the style calculation time? Is it just because there's more nodes in the document at the time? Is it because it's after the styles are printed?

#20 in reply to: ↑ 19 @pento
4 years ago

There are a couple of slowdowns:

  • If the JS is after the styles, it has to wait until all of the CSS has been loaded and parsed before it runs.
  • Because all of the CSS has been loaded when it runs, there's more to recalculate if a recalculation is needed.

@pento
4 years ago

#21 @pento
4 years ago

31701.9.diff reduces wp-emoji.loader.min.js from 1059 to 891 bytes.

@azaozz
4 years ago

#22 @azaozz
4 years ago

In 31701.10.diff:

  • Fix readyState and readystatechange (have to be on document).
  • Some cleanup:
    • No need to output the (cache busting) version string to JS and append it from there. Can append in PHP.
    • Renamed the "handle" for the concatenated file in an attempt to make the loading a bit more readable. May need better name :)
    • Shaved few more bytes off wp-emoji-loader.js.

@pento
4 years ago

#23 follow-up: @pento
4 years ago

  • Keywords commit added

Nice one, @azaozz. :-)

31701.11.diff has a few small changes:

  • Uncomments some code accidentally commented out 31701.8.diff.
  • Moves the print_emoji_detection_script hook back to wp_head, which was accidentally changed back to wp_print_scripts in 31701.9.diff.
  • Removes the now unnecessary changes to unit tests, originally needed because of print_emoji_detection_script being attached to wp_print_scripts.

Unless anyone has any better filename suggestions, I'm pretty happy with the state of this patch. Barring any objections, I'll commit it tomorrow.

#24 in reply to: ↑ 23 @azaozz
4 years ago

Replying to pento:

Oh, another thing. Not a huge deal but.. Currently with SCRIPT_DEBUG on we are waiting for twemoji.js to fully load before starting to load wp-emoji.js. We should load both at the same time and then wait to initialize wp-emoji when twemoji.js is fully loaded.

So waitForTwemoji() should actually be in wp-emoji.js, after it checks document.readyState. Patch coming up.

Last edited 4 years ago by azaozz (previous) (diff)

@azaozz
4 years ago

#25 @azaozz
4 years ago

In 31701.12.diff:

  • When waiting for twemoji.js to load, break after 30 sec.
  • When SCRIPT_DEBUG, add wp-emoji and twemoji at the same time, move the "loaded" check to wp-emoji.

Thinking this is ready. The scripts load as fast as possible and there is almost no slowdown in debug mode or when running from /src.

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


4 years ago

#27 @pento
4 years ago

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

In 31875:

Emoji: Instead of loading the emoji JS files automatically, we now include a small JS shim in the header, to test if the user's browser needs Twemoji. It then loads the emoji JS files only if they're needed.

Props pento, azaozz.

Fixes #31701.

#28 @azaozz
4 years ago

In 31876:

Set EOL style for wp-emoji-loader.js. See #31701.

#29 @pento
4 years ago

In 31877:

Emoji: Print the emoji support shim in wp-admin, too.

See #31701.

#30 @pento
4 years ago

In 31879:

Emoji: Revert [31877], and print the emoji shim and styles during admin_print_scripts and admin_print_styles, instead. This is a few milliseconds slower, but easier to reuse in Press This, and any other code that uses admin scripts and styles, without using admin-header.php.

See #31701.

#31 @pento
4 years ago

In 31881:

Emoji: Add a comment to print_emoji_detection_script() explaining how the include statement works as part of the grunt build process.

See #31701.

@azaozz
4 years ago

#32 @azaozz
4 years ago

Seems IE8 "lies" that DOM is ready when document.readyState === 'interactive'. We need to wait until it is complete there.

#33 @azaozz
4 years ago

  • Resolution fixed deleted
  • Status changed from closed to reopened

#34 @azaozz
4 years ago

  • Resolution set to fixed
  • Status changed from reopened to closed

In 31898:

Fix initialization of wp-emoji in IE8.
Props pento, azaozz. Fixes #31701.

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


4 years ago

#36 @pento
3 years ago

  • Component changed from General to Emoji
Note: See TracTickets for help on using tickets.