-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Use script module for emoji-loader with settings exported via JSON #9531
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
Conversation
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
| ) | ||
| ); | ||
|
|
||
| wp_print_inline_script_tag( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you consider a new function for this? Might improve adoption vs. only a new approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely. I'm keeping this PR a draft because I think it should reuse whatever comes out of Core-58873.
adamsilverstein
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
|
I've noticed a back-compat issue for scripts that depend on the global settings object. add_action( 'wp_enqueue_scripts', function() {
// Script that uses _wpemojiSettings. Something GDPR maybe?
wp_register_script( 'pwcc-no-deps-head', false, [], '1.0' );
wp_enqueue_script( 'pwcc-no-deps-head' );
wp_add_inline_script( 'pwcc-no-deps-head', 'console.log( "pwcc-no-deps-head", window._wpemojiSettings );' );
wp_register_script( 'pwcc-no-deps-foot', false, [], '1.0', true );
wp_enqueue_script( 'pwcc-no-deps-foot' );
wp_add_inline_script( 'pwcc-no-deps-foot', 'console.log( "pwcc-no-deps-foot", window._wpemojiSettings );' );
} );On Searching the plugin repo for _wpemojiSettings shows that it's referenced in WooCommerce Payments, 700K active sites in the file registered by this block of code, so I think we'll need some care if this is to change. A few of the other results appear to be false positives. |
|
Here's where Specifically it's I'm a block's edit function in the editor, which would surely be executed after It doesn't seem like the plugin is using a best practice here anyway. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
In regards to other plugins which may be directly referencing the /**
* WP Emoji replaces the flag emoji with an image if it's not natively
* supported by the browser. This behavior is problematic on Windows
* because it renders an <img> tag inside the <option>, which can lead to crashes.
* We need to guarantee that the OS supports flag emojis before rendering it.
*/
const supportsFlagEmoji = window._wpemojiSettings
? window._wpemojiSettings.supports?.flag
: true;So it could simply be modified to do something like: let wpEmojiSettings = window._wpemojiSettings;
if ( ! wpEmojiSettings ) {
const settingsScript = document.getElementById( 'wp-emoji-settings' );
if ( settingsScript ) {
wpEmojiSettings = JSON.parse( settingsScript.text );
}
}
const supportsFlagEmoji = wpEmojiSettings
? wpEmojiSettings.supports?.flag
: true;Note that it is likely that any script attempting to access I think any compatibility issues can be addressed by a dev note and outreach. |
sirreal
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a nice improvement. I tested and didn't find any issues, but I don't actually know how to test the functionality of wpemoji. Do you know what behaviors I should look for? I'm happy to come back and confirm that things are working as expected.
src/wp-includes/formatting.php
Outdated
| ); | ||
|
|
||
| wp_print_inline_script_tag( | ||
| file_get_contents( ABSPATH . WPINC . '/js/wp-emoji-loader' . wp_scripts_get_suffix() . '.js' ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we append "\n//# sourceURL=…" here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I had drafted that as part of #9955
See #9955 (comment)
But it makes sense to add it here since this PR is directly about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 2bc812d
src/js/_enqueues/lib/emoji-loader.js
Outdated
| // For compatibility with other scripts that read from this global. | ||
| window._wpemojiSettings = /** @type {WPEmojiSettings} */ ( | ||
| JSON.parse( document.getElementById( 'wp-emoji-settings' ).textContent ) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this loading as a module, there are some tweaks that could be made. This is just an observation, not a request for additional changes.
- Waiting for a DOMContentReady promise can likely be removed.
- The IIFE could be considered for removal.
Do you happen to know if this is registered as a script or if it's only loaded inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIFE removed in ad2fcfb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In regards to the DOMContentReady, I think we actually should keep it. I realized that by putting the logic into a module the result is that the emoji detection worker task is now not running until after the DOM has fully loaded, whereas before it starts running in the HEAD when the inline script is encountered.
So what I've done now in 2792ead is I've made the script module async so that it can run in the HEAD as before, but since it is a script module it won't block the parser. (I should re-test the benchmarks with high throttling to see if there is still a performance benefit after this change.) with the move to an async module, keeping the DCL promise is important to ensure that the readyCallback doesn't fire before the DOM has finished loaded, which could happen if the emoji test worker finishes executing before the HTML finishes being sent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you happen to know if this is registered as a script or if it's only loaded inline?
I took a quick look and it appears that the script is not registered but is only used as an inline script.
Therefore, it seems safe to switch to a module and to modify its contents accordingly.
| if ( typeof Promise === 'undefined' ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: This can be removed because all browsers that support modules will support Promise.
src/wp-includes/formatting.php
Outdated
| '//# sourceURL=' . includes_url( $emoji_loader_script_path ), | ||
| array( | ||
| 'type' => 'module', | ||
| 'async' => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered making this async, but is that potentially harmful for performance? That will make it start to evaluate earlier, potentially competing with other things like classic scripts.
Is there a benefit to having it evaluate earlier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The benefit is it allows the worker to execute in parallel while the HTML is being parsed. I'll re-test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I see the context in #9531 (comment).
@sirreal Good question. I usually resort to patching the JS to force Twemoji to run and to never use the --- a/src/js/_enqueues/lib/emoji-loader.js
+++ b/src/js/_enqueues/lib/emoji-loader.js
@@ -216,6 +216,7 @@ function emojiRendersEmptyCenterPoint( context, emoji ) {
* @return {boolean} True if the browser can render emoji, false if it cannot.
*/
function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) {
+ return false;
let isIdentical;
switch ( type ) {
@@ -370,11 +371,12 @@ const domReadyPromise = new Promise( ( resolve ) => {
// Obtain the emoji support from the browser, asynchronously when possible.
new Promise( ( resolve ) => {
- let supportTests = getSessionSupportTests();
- if ( supportTests ) {
- resolve( supportTests );
- return;
- }
+ // let supportTests = getSessionSupportTests();
+ // if ( supportTests ) {
+ // resolve( supportTests );
+ // return;
+ // }
+ let supportTests = null;
if ( supportsWorkerOffloading() ) {
try {
|
| return; | ||
| } | ||
| // Obtain the emoji support from the browser, asynchronously when possible. | ||
| new Promise( ( resolve ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to use await instead, but top-level await modules, but support for it doesn't have universal support: https://caniuse.com/mdn-javascript_operators_await_top_level
Compared with modules generally: https://caniuse.com/es6-module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could re-introduce the IIFE as an async function if we wanted to use await
|
OK, I've re-run the metrics. First, the re-obtaining results comparing
And then here is are the results comparing
Raw ResultsThese results were obtained via multiple calls to with different states of npm run research -- benchmark-web-vitals --url http://localhost:8000/sample-page/?enable_plugins=none --output=csv --number=100 --throttle-cpu=20trunk: module (with defer): module with async: So in terms of LCP (and FCP), it does seem like it would be better to actually remove |
Co-authored-by: Jon Surrell <[email protected]>
| wp_print_inline_script_tag( | ||
| sprintf( 'window._wpemojiSettings = %s;', wp_json_encode( $settings ) ) . "\n" . | ||
| file_get_contents( ABSPATH . WPINC . '/js/wp-emoji-loader' . wp_scripts_get_suffix() . '.js' ) | ||
| wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, the JSON flags were missed in [60681]. Good to get it here 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: Did you know that r has been added as an autolink reference prefix? So r60681 automatically links to the SVN changeset.
Agreed. The fact that the script waited for DOMContentLoaded also suggests it was a good candidate for |
sirreal
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done some more testing to verify the functionality and I believe this works without regressions.
|
I just noticed an issue: because the IIFE was removed, the minification process wasn't able to reduce the length of the top-level symbols because it isn't aware that it is a module. Before!function(s,n){var o,i,e;function c(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function p(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data),a=(e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0),new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data));return t.every(function(e,t){return e===a[t]})}function u(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);for(var n=e.getImageData(16,16,1,1),a=0;a<n.data.length;a++)if(0!==n.data[a])return!1;return!0}function f(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\udedf")}return!1}function g(e,t,n,a){var r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):s.createElement("canvas"),o=r.getContext("2d",{willReadFrequently:!0}),i=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(function(e){i[e]=t(o,e,n,a)}),i}function t(e){var t=s.createElement("script");t.src=e,t.defer=!0,s.head.appendChild(t)}"undefined"!=typeof Promise&&(o="wpEmojiSettingsSupports",i=["flag","emoji"],n.supports={everything:!0,everythingExceptFlag:!0},e=new Promise(function(e){s.addEventListener("DOMContentLoaded",e,{once:!0})}),new Promise(function(t){var n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+g.toString()+"("+[JSON.stringify(i),f.toString(),p.toString(),u.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"}),r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=function(e){c(n=e.data),r.terminate(),t(n)})}catch(e){}c(n=g(i,f,p,u))}t(n)}).then(function(e){for(var t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=function(){n.DOMReady=!0}}).then(function(){return e}).then(function(){var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))}))}((window,document),window._wpemojiSettings);JS script byte length: 3,055 bytes After:const settings=JSON.parse(document.getElementById("wp-emoji-settings").textContent),sessionStorageKey=(window._wpemojiSettings=settings,"wpEmojiSettingsSupports"),tests=["flag","emoji"];function supportsWorkerOffloading(){return"undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob}function getSessionSupportTests(){try{var t=JSON.parse(sessionStorage.getItem(sessionStorageKey));if("object"==typeof t&&"number"==typeof t.timestamp&&(new Date).valueOf()<t.timestamp+604800&&"object"==typeof t.supportTests)return t.supportTests}catch(t){}return null}function setSessionSupportTests(t){try{var e={supportTests:t,timestamp:(new Date).valueOf()};sessionStorage.setItem(sessionStorageKey,JSON.stringify(e))}catch(t){}}function emojiSetsRenderIdentically(t,e,s){t.clearRect(0,0,t.canvas.width,t.canvas.height),t.fillText(e,0,0);e=new Uint32Array(t.getImageData(0,0,t.canvas.width,t.canvas.height).data);t.clearRect(0,0,t.canvas.width,t.canvas.height),t.fillText(s,0,0);const n=new Uint32Array(t.getImageData(0,0,t.canvas.width,t.canvas.height).data);return e.every((t,e)=>t===n[e])}function emojiRendersEmptyCenterPoint(t,e){t.clearRect(0,0,t.canvas.width,t.canvas.height),t.fillText(e,0,0);var s=t.getImageData(16,16,1,1);for(let t=0;t<s.data.length;t++)if(0!==s.data[t])return!1;return!0}function browserSupportsEmoji(t,e,s,n){switch(e){case"flag":return s(t,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!s(t,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!s(t,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!n(t,"\ud83e\udedf")}return!1}function testEmojiSupports(t,e,s,n){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),i=(o.textBaseline="top",o.font="600 32px Arial",{});return t.forEach(t=>{i[t]=e(o,t,s,n)}),i}function addScript(t){var e=document.createElement("script");e.src=t,e.defer=!0,document.head.appendChild(e)}settings.supports={everything:!0,everythingExceptFlag:!0},new Promise(e=>{let s=getSessionSupportTests();if(!s){if(supportsWorkerOffloading())try{var t="postMessage("+testEmojiSupports.toString()+"("+[JSON.stringify(tests),browserSupportsEmoji.toString(),emojiSetsRenderIdentically.toString(),emojiRendersEmptyCenterPoint.toString()].join(",")+"));",n=new Blob([t],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(n),{name:"wpTestEmojiSupports"});return void(r.onmessage=t=>{setSessionSupportTests(s=t.data),r.terminate(),e(s)})}catch(t){}setSessionSupportTests(s=testEmojiSupports(tests,browserSupportsEmoji,emojiSetsRenderIdentically,emojiRendersEmptyCenterPoint))}e(s)}).then(t=>{for(const e in t)settings.supports[e]=t[e],settings.supports.everything=settings.supports.everything&&settings.supports[e],"flag"!==e&&(settings.supports.everythingExceptFlag=settings.supports.everythingExceptFlag&&settings.supports[e]);settings.supports.everythingExceptFlag=settings.supports.everythingExceptFlag&&!settings.supports.flag,settings.DOMReady=!1,settings.readyCallback=()=>{settings.DOMReady=!0}}).then(()=>{var t;settings.supports.everything||(settings.readyCallback(),(t=settings.source||{}).concatemoji?addScript(t.concatemoji):t.wpemoji&&t.twemoji&&(addScript(t.twemoji),addScript(t.wpemoji)))});
//# sourceURL=http://localhost:8000/wp-includes/js/wp-emoji-loader.min.jsJS script byte length: 3,672 |
|
OK, I've got it. With 589e11c I've forced UglifyJS to consider the const n=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=n,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\udedf")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function t(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}n.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=()=>{n.DOMReady=!0}}).then(()=>{var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))});
//# sourceURL=http://localhost:8000/wp-includes/js/wp-emoji-loader.min.jsAside: We should separately look into moving this script module to be printed at --- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -5912,7 +5912,11 @@ function print_emoji_detection_script() {
$printed = true;
- _print_emoji_detection_script();
+ if ( did_action( 'wp_print_footer_scripts' ) ) {
+ _print_emoji_detection_script();
+ } else {
+ add_action( 'wp_print_footer_scripts', '_print_emoji_detection_script' );
+ }
}
/**This would cut out the following 3,403 bytes of HTML from being needlessly in the <script id="wp-emoji-settings" type="application/json">
{"baseUrl":"https://s.w.org/images/core/emoji/16.0.1/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/16.0.1/svg/","svgExt":".svg","source":{"concatemoji":"http://localhost:8000/wp-includes/js/wp-emoji-release.min.js?ver=6.9-alpha-60093-src"}}
</script>
<script type="module">
/*! This file is auto-generated */
const n=JSON.parse(document.getElementById("wp-emoji-settings").textContent),o=(window._wpemojiSettings=n,"wpEmojiSettingsSupports"),s=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const a=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===a[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,a){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!a(e,"\ud83e\udedf")}return!1}function f(e,t,n,a){let r;const o=(r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),s=(o.textBaseline="top",o.font="600 32px Arial",{});return e.forEach(e=>{s[e]=t(o,e,n,a)}),s}function t(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}n.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),c.toString(),p.toString()].join(",")+"));",a=new Blob([e],{type:"text/javascript"});const r=new Worker(URL.createObjectURL(a),{name:"wpTestEmojiSupports"});return void(r.onmessage=e=>{i(n=e.data),r.terminate(),t(n)})}catch(e){}i(n=f(s,u,c,p))}t(n)}).then(e=>{for(const t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=()=>{n.DOMReady=!0}}).then(()=>{var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))});
//# sourceURL=http://localhost:8000/wp-includes/js/wp-emoji-loader.min.js
</script> |
|
With the changes in this PR, I applied this patch: --- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -5912,7 +5912,11 @@ function print_emoji_detection_script() {
$printed = true;
- _print_emoji_detection_script();
+ if ( isset( $_GET['print_emoji_detection_script_position'] ) && 'footer' === $_GET['print_emoji_detection_script_position'] ) {
+ add_action( 'wp_print_footer_scripts', '_print_emoji_detection_script' );
+ } else {
+ _print_emoji_detection_script();
+ }
}
/**I then obtained the metrics for 100 requests for the script being printed in npm run research -- benchmark-web-vitals --url="http://localhost:8000/sample-page/?enable_plugins=none&print_emoji_detection_script_position=head" --url="http://localhost:8000/sample-page/?enable_plugins=none&print_emoji_detection_script_position=footer" --output=md --number=100 --network-conditions="Fast 4G" --diffThe results show a modest yet clear ~1% improvement to LCP and ~2% improvement to FCP:
|
|
I did for same while emulating Slow 3G and got the following results, 100 for
|
| } ) | ||
| // Once the browser emoji support has been obtained from the session, finalize the settings. | ||
| .then( function ( supportTests ) { | ||
| /* | ||
| * Tests the browser support for flag emojis and other emojis, and adjusts the | ||
| * support settings accordingly. | ||
| */ | ||
| for ( var test in supportTests ) { | ||
| settings.supports[ test ] = supportTests[ test ]; | ||
|
|
||
| settings.supports.everything = | ||
| settings.supports.everything && settings.supports[ test ]; | ||
|
|
||
| if ( 'flag' !== test ) { | ||
| settings.supports.everythingExceptFlag = | ||
| settings.supports.everythingExceptFlag && | ||
| settings.supports[ test ]; | ||
| } | ||
| } | ||
|
|
||
| settings.supports.everythingExceptFlag = | ||
| settings.supports.everythingExceptFlag && | ||
| ! settings.supports.flag; | ||
|
|
||
| // Sets DOMReady to false and assigns a ready function to settings. | ||
| settings.DOMReady = false; | ||
| settings.readyCallback = function () { | ||
| settings.DOMReady = true; | ||
| }; | ||
| } ) | ||
| .then( function () { | ||
| return domReadyPromise; | ||
| } ) | ||
| .then( function () { | ||
| // When the browser can not render everything we need to load a polyfill. | ||
| if ( ! settings.supports.everything ) { | ||
| settings.readyCallback(); | ||
|
|
||
| var src = settings.source || {}; | ||
|
|
||
| if ( src.concatemoji ) { | ||
| addScript( src.concatemoji ); | ||
| } else if ( src.wpemoji && src.twemoji ) { | ||
| addScript( src.twemoji ); | ||
| addScript( src.wpemoji ); | ||
| } | ||
| .then( () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This secondary then() can be removed since there is no intervening domReadyPromise to wait to resolve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trac ticket: https://core.trac.wordpress.org/ticket/63842
This splits up the inline script output in
_print_emoji_detection_script()into two:scriptof typeapplication/jsonwhich contains the_wpemojiSettingsdata.scriptof typemodulewhich containswp-emoji-loader.js, which parses_wpemojiSettingsout of the JSON.The result is this inline script is eliminated from blocking the HTML parser from processing the page (and rendering the page) while waiting for the JavaScript to execute. The
wp-emoji-loader.jsscript does not need to run in theheadbecause it does not actually proceed with loading emoji (if needed) untilDOMContentLoaded.I used the
benchmark-web-vitalscommand from GoogleChromeLabs/wpp-research to analyze the performance impact of this on a vanilla WordPress install on the Sample Page. For example:On a high-end machine (e.g. MacBook Pro with M4 Pro chip), the difference is negligible:
However, when the CPU is throttled (via
--throttle-cpu) to emulate a low-tier mobile phone, then there is a clear impact. I used CPU throttle factor of 20 because this value is close to how Chrome DevTools calibrates my CPU to emulate such a device:The command I run on
trunkand again on this branch:The results show a >5% improvement to LCP:
This is the diff or a rendered page with Prettier formatting applied and
SCRIPT_DEBUGenabled:This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.